From ba7723d9057b0af6d404f0ab2a993541d5ba37cf Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 11 Dec 2012 14:01:03 -0800 Subject: [PATCH] Modernize Macro application Summary: Adds feed, email, notifications, comments, partial editing, subscriptions, enable/disable, flags and crumbs to Macro. Test Plan: {F26839} {F26840} {F26841} {F26842} {F26843} {F26844} {F26845} Reviewers: vrana, btrahan, chad Reviewed By: vrana CC: aran Maniphest Tasks: T2157, T175, T2104 Differential Revision: https://secure.phabricator.com/D4141 --- conf/default.conf.php | 9 +- resources/sql/patches/20121209.xmacroadd.sql | 54 ++++ .../sql/patches/20121209.xmacromigrate.php | 18 ++ .../sql/patches/20121209.xmacromigratekey.sql | 2 + src/__phutil_library_map__.php | 27 +- src/aphront/AphrontRequest.php | 5 + .../PhabricatorApplicationMacro.php | 7 +- .../PhabricatorMacroTransactionType.php | 9 + .../PhabricatorMacroCommentController.php | 49 ++++ .../controller/PhabricatorMacroController.php | 32 ++- .../PhabricatorMacroDeleteController.php | 42 ---- .../PhabricatorMacroDisableController.php | 55 +++++ .../PhabricatorMacroEditController.php | 214 ++++++++++++---- .../PhabricatorMacroListController.php | 22 +- .../PhabricatorMacroViewController.php | 193 +++++++++++++++ .../macro/editor/PhabricatorMacroEditor.php | 123 ++++++++++ .../mail/PhabricatorMacroReplyHandler.php | 40 +++ .../PhabricatorMacroTransactionQuery.php | 10 + .../storage/PhabricatorFileImageMacro.php | 17 +- .../storage/PhabricatorMacroTransaction.php | 231 ++++++++++++++++++ .../PhabricatorMacroTransactionComment.php | 11 + .../phid/PhabricatorPHIDConstants.php | 1 + .../handle/PhabricatorObjectHandleData.php | 34 +++ ...habricatorApplicationTransactionEditor.php | 2 - .../PhabricatorApplicationTransaction.php | 19 +- ...abricatorApplicationTransactionComment.php | 2 + .../PhabricatorApplicationTransactionView.php | 81 ++++++ .../edges/constants/PhabricatorEdgeConfig.php | 1 + .../PhabricatorRemarkupRuleImageMacro.php | 3 +- .../patch/PhabricatorBuiltinPatchList.php | 12 + src/view/layout/PhabricatorHeaderView.php | 15 ++ webroot/rsrc/css/core/remarkup.css | 5 + .../css/layout/phabricator-header-view.css | 5 + 33 files changed, 1233 insertions(+), 117 deletions(-) create mode 100644 resources/sql/patches/20121209.xmacroadd.sql create mode 100644 resources/sql/patches/20121209.xmacromigrate.php create mode 100644 resources/sql/patches/20121209.xmacromigratekey.sql create mode 100644 src/applications/macro/constants/PhabricatorMacroTransactionType.php create mode 100644 src/applications/macro/controller/PhabricatorMacroCommentController.php delete mode 100644 src/applications/macro/controller/PhabricatorMacroDeleteController.php create mode 100644 src/applications/macro/controller/PhabricatorMacroDisableController.php create mode 100644 src/applications/macro/controller/PhabricatorMacroViewController.php create mode 100644 src/applications/macro/editor/PhabricatorMacroEditor.php create mode 100644 src/applications/macro/mail/PhabricatorMacroReplyHandler.php create mode 100644 src/applications/macro/query/PhabricatorMacroTransactionQuery.php create mode 100644 src/applications/macro/storage/PhabricatorMacroTransaction.php create mode 100644 src/applications/macro/storage/PhabricatorMacroTransactionComment.php create mode 100644 src/applications/transactions/view/PhabricatorApplicationTransactionView.php diff --git a/conf/default.conf.php b/conf/default.conf.php index f97b4bfc79..5f00978e2d 100644 --- a/conf/default.conf.php +++ b/conf/default.conf.php @@ -383,13 +383,20 @@ return array( // distinguish between testing and development installs, for example. 'metamta.maniphest.subject-prefix' => '[Maniphest]', - // See 'metamta.pholio.reply-handler-domain'. This does the same thing, but + // See 'metamta.maniphest.reply-handler-domain'. This does the same thing, but // affects Pholio. 'metamta.pholio.reply-handler-domain' => null, // Prefix prepended to mail sent by Pholio. 'metamta.pholio.subject-prefix' => '[Pholio]', + // See 'metamta.maniphest.reply-handler-domain'. This does the same thing, but + // affects Macro. + 'metamta.macro.reply-handler-domain' => null, + + // Prefix prepended to mail sent by Macro. + 'metamta.macro.subject-prefix' => '[Macro]', + // See 'metamta.maniphest.reply-handler-domain'. This does the same thing, // but allows email replies via Differential. 'metamta.differential.reply-handler-domain' => null, diff --git a/resources/sql/patches/20121209.xmacroadd.sql b/resources/sql/patches/20121209.xmacroadd.sql new file mode 100644 index 0000000000..98237f1703 --- /dev/null +++ b/resources/sql/patches/20121209.xmacroadd.sql @@ -0,0 +1,54 @@ +CREATE TABLE {$NAMESPACE}_file.macro_transaction ( + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + phid VARCHAR(64) NOT NULL COLLATE utf8_bin, + authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, + objectPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, + viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, + editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, + commentPHID VARCHAR(64) COLLATE utf8_bin, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) NOT NULL COLLATE utf8_bin, + oldValue LONGTEXT NOT NULL COLLATE utf8_bin, + newValue LONGTEXT NOT NULL COLLATE utf8_bin, + contentSource LONGTEXT NOT NULL COLLATE utf8_bin, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + + UNIQUE KEY `key_phid` (phid), + KEY `key_object` (objectPHID) + +) ENGINE=InnoDB, COLLATE utf8_general_ci; + +CREATE TABLE {$NAMESPACE}_file.macro_transaction_comment ( + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + phid VARCHAR(64) NOT NULL COLLATE utf8_bin, + transactionPHID VARCHAR(64) COLLATE utf8_bin, + authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, + viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, + editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, + commentVersion INT UNSIGNED NOT NULL, + content LONGTEXT NOT NULL COLLATE utf8_bin, + contentSource LONGTEXT NOT NULL COLLATE utf8_bin, + isDeleted BOOL NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + + UNIQUE KEY `key_phid` (phid), + UNIQUE KEY `key_version` (transactionPHID, commentVersion) + +) ENGINE=InnoDB, COLLATE utf8_general_ci; + +ALTER TABLE {$NAMESPACE}_file.file_imagemacro + ADD dateCreated INT UNSIGNED NOT NULL; + +ALTER TABLE {$NAMESPACE}_file.file_imagemacro + ADD dateModified INT UNSIGNED NOT NULL; + +ALTER TABLE {$NAMESPACE}_file.file_imagemacro + ADD phid VARCHAR(64) NOT NULL COLLATE utf8_bin AFTER id; + +ALTER TABLE {$NAMESPACE}_file.file_imagemacro + ADD isDisabled BOOL NOT NULL; + +ALTER TABLE {$NAMESPACE}_file.file_imagemacro + ADD KEY `key_disabled` (isDisabled); diff --git a/resources/sql/patches/20121209.xmacromigrate.php b/resources/sql/patches/20121209.xmacromigrate.php new file mode 100644 index 0000000000..d31f40fd92 --- /dev/null +++ b/resources/sql/patches/20121209.xmacromigrate.php @@ -0,0 +1,18 @@ +getPHID()) { + continue; + } + + echo "."; + + queryfx( + $macro->establishConnection('r'), + 'UPDATE %T SET phid = %s WHERE id = %d', + $macro->getTableName(), + $macro->generatePHID(), + $macro->getID()); +} +echo "\nDone.\n"; diff --git a/resources/sql/patches/20121209.xmacromigratekey.sql b/resources/sql/patches/20121209.xmacromigratekey.sql new file mode 100644 index 0000000000..3285c77f09 --- /dev/null +++ b/resources/sql/patches/20121209.xmacromigratekey.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_file.file_imagemacro + ADD UNIQUE KEY `key_phid` (phid); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 644c43f8d0..f1296f6878 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -604,6 +604,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionEditor' => 'applications/transactions/editor/PhabricatorApplicationTransactionEditor.php', 'PhabricatorApplicationTransactionFeedStory' => 'applications/transactions/feed/PhabricatorApplicationTransactionFeedStory.php', 'PhabricatorApplicationTransactionQuery' => 'applications/transactions/query/PhabricatorApplicationTransactionQuery.php', + 'PhabricatorApplicationTransactionView' => 'applications/transactions/view/PhabricatorApplicationTransactionView.php', 'PhabricatorApplicationTransactions' => 'applications/transactions/application/PhabricatorApplicationTransactions.php', 'PhabricatorApplicationUIExamples' => 'applications/uiexample/application/PhabricatorApplicationUIExamples.php', 'PhabricatorApplicationsListController' => 'applications/meta/controller/PhabricatorApplicationsListController.php', @@ -829,10 +830,18 @@ phutil_register_library_map(array( 'PhabricatorLoginController' => 'applications/auth/controller/PhabricatorLoginController.php', 'PhabricatorLoginValidateController' => 'applications/auth/controller/PhabricatorLoginValidateController.php', 'PhabricatorLogoutController' => 'applications/auth/controller/PhabricatorLogoutController.php', + 'PhabricatorMacroCommentController' => 'applications/macro/controller/PhabricatorMacroCommentController.php', 'PhabricatorMacroController' => 'applications/macro/controller/PhabricatorMacroController.php', - 'PhabricatorMacroDeleteController' => 'applications/macro/controller/PhabricatorMacroDeleteController.php', + 'PhabricatorMacroDisableController' => 'applications/macro/controller/PhabricatorMacroDisableController.php', 'PhabricatorMacroEditController' => 'applications/macro/controller/PhabricatorMacroEditController.php', + 'PhabricatorMacroEditor' => 'applications/macro/editor/PhabricatorMacroEditor.php', 'PhabricatorMacroListController' => 'applications/macro/controller/PhabricatorMacroListController.php', + 'PhabricatorMacroReplyHandler' => 'applications/macro/mail/PhabricatorMacroReplyHandler.php', + 'PhabricatorMacroTransaction' => 'applications/macro/storage/PhabricatorMacroTransaction.php', + 'PhabricatorMacroTransactionComment' => 'applications/macro/storage/PhabricatorMacroTransactionComment.php', + 'PhabricatorMacroTransactionQuery' => 'applications/macro/query/PhabricatorMacroTransactionQuery.php', + 'PhabricatorMacroTransactionType' => 'applications/macro/constants/PhabricatorMacroTransactionType.php', + 'PhabricatorMacroViewController' => 'applications/macro/controller/PhabricatorMacroViewController.php', 'PhabricatorMailImplementationAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationAdapter.php', 'PhabricatorMailImplementationAmazonSESAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationAmazonSESAdapter.php', 'PhabricatorMailImplementationPHPMailerAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationPHPMailerAdapter.php', @@ -1869,6 +1878,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionEditor' => 'PhabricatorEditor', 'PhabricatorApplicationTransactionFeedStory' => 'PhabricatorFeedStory', 'PhabricatorApplicationTransactionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorApplicationTransactionView' => 'AphrontView', 'PhabricatorApplicationTransactions' => 'PhabricatorApplication', 'PhabricatorApplicationUIExamples' => 'PhabricatorApplication', 'PhabricatorApplicationsListController' => 'PhabricatorController', @@ -2024,7 +2034,11 @@ phutil_register_library_map(array( 'PhabricatorFileDataController' => 'PhabricatorFileController', 'PhabricatorFileDeleteController' => 'PhabricatorFileController', 'PhabricatorFileDropUploadController' => 'PhabricatorFileController', - 'PhabricatorFileImageMacro' => 'PhabricatorFileDAO', + 'PhabricatorFileImageMacro' => + array( + 0 => 'PhabricatorFileDAO', + 1 => 'PhabricatorSubscribableInterface', + ), 'PhabricatorFileInfoController' => 'PhabricatorFileController', 'PhabricatorFileLinkListView' => 'AphrontView', 'PhabricatorFileLinkView' => 'AphrontView', @@ -2080,10 +2094,17 @@ phutil_register_library_map(array( 'PhabricatorLoginController' => 'PhabricatorAuthController', 'PhabricatorLoginValidateController' => 'PhabricatorAuthController', 'PhabricatorLogoutController' => 'PhabricatorAuthController', + 'PhabricatorMacroCommentController' => 'PhabricatorMacroController', 'PhabricatorMacroController' => 'PhabricatorController', - 'PhabricatorMacroDeleteController' => 'PhabricatorMacroController', + 'PhabricatorMacroDisableController' => 'PhabricatorMacroController', 'PhabricatorMacroEditController' => 'PhabricatorMacroController', + 'PhabricatorMacroEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorMacroListController' => 'PhabricatorMacroController', + 'PhabricatorMacroReplyHandler' => 'PhabricatorMailReplyHandler', + 'PhabricatorMacroTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorMacroTransactionComment' => 'PhabricatorApplicationTransactionComment', + 'PhabricatorMacroTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorMacroViewController' => 'PhabricatorMacroController', 'PhabricatorMailImplementationAmazonSESAdapter' => 'PhabricatorMailImplementationPHPMailerLiteAdapter', 'PhabricatorMailImplementationPHPMailerAdapter' => 'PhabricatorMailImplementationAdapter', 'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'PhabricatorMailImplementationAdapter', diff --git a/src/aphront/AphrontRequest.php b/src/aphront/AphrontRequest.php index d83bbb27c6..282ed13774 100644 --- a/src/aphront/AphrontRequest.php +++ b/src/aphront/AphrontRequest.php @@ -161,6 +161,11 @@ final class AphrontRequest { return array_key_exists($name, $this->requestData); } + final public function getFileExists($name) { + return isset($_FILES[$name]) && + (idx($_FILES[$name], 'error') !== UPLOAD_ERR_NO_FILE); + } + final public function isHTTPPost() { return ($_SERVER['REQUEST_METHOD'] == 'POST'); } diff --git a/src/applications/macro/application/PhabricatorApplicationMacro.php b/src/applications/macro/application/PhabricatorApplicationMacro.php index 972ae0ed4d..a2f68437e1 100644 --- a/src/applications/macro/application/PhabricatorApplicationMacro.php +++ b/src/applications/macro/application/PhabricatorApplicationMacro.php @@ -26,8 +26,11 @@ final class PhabricatorApplicationMacro extends PhabricatorApplication { return array( '/macro/' => array( '' => 'PhabricatorMacroListController', - 'edit/(?:(?P[1-9]\d*)/)?' => 'PhabricatorMacroEditController', - 'delete/(?P[1-9]\d*)/' => 'PhabricatorMacroDeleteController', + 'create/' => 'PhabricatorMacroEditController', + 'view/(?P[1-9]\d*)/' => 'PhabricatorMacroViewController', + 'comment/(?P[1-9]\d*)/' => 'PhabricatorMacroCommentController', + 'edit/(?P[1-9]\d*)/' => 'PhabricatorMacroEditController', + 'disable/(?P[1-9]\d*)/' => 'PhabricatorMacroDisableController', ), ); } diff --git a/src/applications/macro/constants/PhabricatorMacroTransactionType.php b/src/applications/macro/constants/PhabricatorMacroTransactionType.php new file mode 100644 index 0000000000..fb1b8013aa --- /dev/null +++ b/src/applications/macro/constants/PhabricatorMacroTransactionType.php @@ -0,0 +1,9 @@ +id = idx($data, 'id'); + } + + public function processRequest() { + $request = $this->getRequest(); + $user = $request->getUser(); + + if (!$request->isFormPost()) { + return new Aphront400Response(); + } + + $macro = id(new PhabricatorFileImageMacro())->load($this->id); + if (!$macro) { + return new Aphront404Response(); + } + + $view_uri = $this->getApplicationURI('/view/'.$macro->getID().'/'); + + $xactions = array(); + + $xactions[] = id(new PhabricatorMacroTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) + ->attachComment( + id(new PhabricatorMacroTransactionComment()) + ->setContent($request->getStr('comment'))); + + $editor = id(new PhabricatorMacroEditor()) + ->setActor($user) + ->setContentSource( + PhabricatorContentSource::newForSource( + PhabricatorContentSource::SOURCE_WEB, + array( + 'ip' => $request->getRemoteAddr(), + ))) + ->applyTransactions($macro, $xactions); + + return id(new AphrontRedirectResponse()) + ->setURI($view_uri); + } + +} diff --git a/src/applications/macro/controller/PhabricatorMacroController.php b/src/applications/macro/controller/PhabricatorMacroController.php index 48adf16723..8ae3e7f1bc 100644 --- a/src/applications/macro/controller/PhabricatorMacroController.php +++ b/src/applications/macro/controller/PhabricatorMacroController.php @@ -3,19 +3,39 @@ abstract class PhabricatorMacroController extends PhabricatorController { - protected function buildSideNavView(PhabricatorFileImageMacro $macro = null) { + protected function buildSideNavView($for_app = false, $has_search = false) { $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - $nav->addLabel('Create'); - $nav->addFilter('edit', 'Create Macro'); - - $nav->addSpacer(); + if ($for_app) { + $nav->addLabel('Create'); + $nav->addFilter('', 'Create Macro', $this->getApplicationURI('/create/')); + } $nav->addLabel('Macros'); - $nav->addFilter('', 'All Macros'); + $nav->addFilter('/', 'All Macros'); + if ($has_search) { + $nav->addFilter('search', 'Search', $this->getRequest()->getRequestURI()); + } + return $nav; } + public function buildApplicationMenu() { + return $this->buildSideNavView($for_app = true)->getMenu(); + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $crumbs->addAction( + id(new PhabricatorMenuItemView()) + ->setName(pht('Create Macro')) + ->setHref($this->getApplicationURI('/create/')) + ->setIcon('create')); + + return $crumbs; + } + } diff --git a/src/applications/macro/controller/PhabricatorMacroDeleteController.php b/src/applications/macro/controller/PhabricatorMacroDeleteController.php deleted file mode 100644 index 2f00e92c3b..0000000000 --- a/src/applications/macro/controller/PhabricatorMacroDeleteController.php +++ /dev/null @@ -1,42 +0,0 @@ -id = $data['id']; - } - - public function processRequest() { - - $macro = id(new PhabricatorFileImageMacro())->load($this->id); - if (!$macro) { - return new Aphront404Response(); - } - - $request = $this->getRequest(); - - if ($request->isDialogFormPost()) { - $macro->delete(); - return id(new AphrontRedirectResponse())->setURI( - $this->getApplicationURI()); - } - - $dialog = new AphrontDialogView(); - $dialog - ->setUser($request->getUser()) - ->setTitle('Really delete macro?') - ->appendChild( - '

Really delete the much-beloved image macro "'. - phutil_escape_html($macro->getName()).'"? It will be sorely missed.'. - '

') - ->setSubmitURI($this->getApplicationURI('/delete/'.$this->id.'/')) - ->addSubmitButton('Delete') - ->addCancelButton($this->getApplicationURI()); - - - return id(new AphrontDialogResponse())->setDialog($dialog); - } -} diff --git a/src/applications/macro/controller/PhabricatorMacroDisableController.php b/src/applications/macro/controller/PhabricatorMacroDisableController.php new file mode 100644 index 0000000000..474285d77e --- /dev/null +++ b/src/applications/macro/controller/PhabricatorMacroDisableController.php @@ -0,0 +1,55 @@ +id = $data['id']; + } + + public function processRequest() { + $request = $this->getRequest(); + $user = $request->getUser(); + + $macro = id(new PhabricatorFileImageMacro())->load($this->id); + if (!$macro) { + return new Aphront404Response(); + } + + $view_uri = $this->getApplicationURI('/view/'.$this->id.'/'); + + if ($request->isDialogFormPost() || $macro->getIsDisabled()) { + $xaction = id(new PhabricatorMacroTransaction()) + ->setTransactionType(PhabricatorMacroTransactionType::TYPE_DISABLED) + ->setNewValue($macro->getIsDisabled() ? 0 : 1); + + $editor = id(new PhabricatorMacroEditor()) + ->setActor($user) + ->setContentSource( + PhabricatorContentSource::newForSource( + PhabricatorContentSource::SOURCE_WEB, + array( + 'ip' => $request->getRemoteAddr(), + ))) + ->applyTransactions($macro, array($xaction)); + + return id(new AphrontRedirectResponse())->setURI($view_uri); + } + + $dialog = new AphrontDialogView(); + $dialog + ->setUser($request->getUser()) + ->setTitle('Really disable macro?') + ->appendChild( + '

Really disable the much-beloved image macro "'. + phutil_escape_html($macro->getName()).'"? It will be sorely missed.'. + '

') + ->setSubmitURI($this->getApplicationURI('/disable/'.$this->id.'/')) + ->addSubmitButton('Disable') + ->addCancelButton($view_uri); + + return id(new AphrontDialogResponse())->setDialog($dialog); + } +} diff --git a/src/applications/macro/controller/PhabricatorMacroEditController.php b/src/applications/macro/controller/PhabricatorMacroEditController.php index 7c9cdb4fe9..ff9f9fcbf3 100644 --- a/src/applications/macro/controller/PhabricatorMacroEditController.php +++ b/src/applications/macro/controller/PhabricatorMacroEditController.php @@ -22,56 +22,96 @@ final class PhabricatorMacroEditController $errors = array(); $e_name = true; + $e_file = true; $file = null; $request = $this->getRequest(); $user = $request->getUser(); if ($request->isFormPost()) { + $original = clone $macro; - $macro->setName($request->getStr('name')); + $new_name = null; + if ($request->getBool('name_form') || !$macro->getID()) { + $new_name = $request->getStr('name'); - if (!strlen($macro->getName())) { - $errors[] = 'Macro name is required.'; - $e_name = 'Required'; - } else if (!preg_match('/^[a-z0-9_-]{3,}$/', $macro->getName())) { - $errors[] = 'Macro must be at least three characters long and contain '. - 'only lowercase letters, digits, hyphen and underscore.'; - $e_name = 'Invalid'; - } else { - $e_name = null; + $macro->setName($new_name); + + if (!strlen($macro->getName())) { + $errors[] = 'Macro name is required.'; + $e_name = 'Required'; + } else if (!preg_match('/^[a-z0-9_-]{3,}$/', $macro->getName())) { + $errors[] = 'Macro must be at least three characters long and '. + 'contain only lowercase letters, digits, hyphen and '. + 'underscore.'; + $e_name = 'Invalid'; + } else { + $e_name = null; + } } - if (!$errors) { - + $file = null; + if ($request->getFileExists('file')) { $file = PhabricatorFile::newFromPHPUpload( - idx($_FILES, 'file'), + $_FILES['file'], array( 'name' => $request->getStr('name'), 'authorPHID' => $user->getPHID(), )); - $macro->setFilePHID($file->getPHID()); + } else if ($request->getStr('phid')) { + $file = id(new PhabricatorFile())->loadOneWhere( + 'phid = %s', + $request->getStr('phid')); + } + if ($file) { + if (!$file->isViewableInBrowser()) { + $errors[] = pht('You must upload an image.'); + $e_file = pht('Invalid'); + } else { + $macro->setFilePHID($file->getPHID()); + $e_file = null; + } + } + + if (!$macro->getID() && !$file) { + $errors[] = 'You must upload an image to create a macro.'; + $e_file = pht('Required'); + } + + if (!$errors) { try { - $macro->save(); - return id(new AphrontRedirectResponse())->setURI( - $this->getApplicationURI()); + $xactions = array(); + + if ($new_name !== null) { + $xactions[] = id(new PhabricatorMacroTransaction()) + ->setTransactionType(PhabricatorMacroTransactionType::TYPE_NAME) + ->setNewValue($new_name); + } + + if ($file) { + $xactions[] = id(new PhabricatorMacroTransaction()) + ->setTransactionType(PhabricatorMacroTransactionType::TYPE_FILE) + ->setNewValue($file->getPHID()); + } + + $editor = id(new PhabricatorMacroEditor()) + ->setActor($user) + ->setContentSource( + PhabricatorContentSource::newForSource( + PhabricatorContentSource::SOURCE_WEB, + array( + 'ip' => $request->getRemoteAddr(), + ))) + ->applyTransactions($original, $xactions); + + $view_uri = $this->getApplicationURI('/view/'.$original->getID().'/'); + return id(new AphrontRedirectResponse())->setURI($view_uri); } catch (AphrontQueryDuplicateKeyException $ex) { + throw $ex; $errors[] = 'Macro name is not unique!'; $e_name = 'Duplicate'; } } - } else if ($this->id) { - $file = id(new PhabricatorFile()) - ->loadOneWhere('phid = %s', $macro->getFilePHID()); - } - - $caption = null; - if ($file) { - $caption = phutil_render_tag( - 'img', - array( - 'src' => $file->getViewURI(), - )); } if ($errors) { @@ -82,7 +122,17 @@ final class PhabricatorMacroEditController $error_view = null; } + + $current_file = null; + if ($macro->getFilePHID()) { + $current_file = id(new PhabricatorFile())->loadOneWhere( + 'phid = %s', + $macro->getFilePHID()); + } + $form = new AphrontFormView(); + $form->setFlexible(true); + $form->addHiddenInput('name_form', 1); $form->setUser($request->getUser()); $form @@ -93,35 +143,101 @@ final class PhabricatorMacroEditController ->setName('name') ->setValue($macro->getName()) ->setCaption('This word or phrase will be replaced with the image.') - ->setError($e_name)) - ->appendChild( + ->setError($e_name)); + + if (!$macro->getID()) { + if ($current_file) { + $current_file_view = id(new PhabricatorFileLinkView()) + ->setFilePHID($current_file->getPHID()) + ->setFileName($current_file->getName()) + ->setFileViewable(true) + ->setFileViewURI($current_file->getBestURI()) + ->render(); + $form->addHiddenInput('phid', $current_file->getPHID()); + $form->appendChild( + id(new AphrontFormMarkupControl()) + ->setLabel('Selected File') + ->setValue($current_file_view)); + + $other_label = pht('Change File'); + } else { + $other_label = pht('File'); + } + + $form->appendChild( id(new AphrontFormFileControl()) - ->setLabel('File') + ->setLabel($other_label) ->setName('file') - ->setCaption($caption) - ->setError(true)) + ->setError($e_file)); + } + + + $view_uri = $this->getApplicationURI('/view/'.$macro->getID().'/'); + + if ($macro->getID()) { + $cancel_uri = $view_uri; + } else { + $cancel_uri = $this->getApplicationURI(); + } + + $form ->appendChild( id(new AphrontFormSubmitControl()) - ->setValue('Save Image Macro') - ->addCancelButton($this->getApplicationURI())); + ->setValue(pht('Save Image Macro')) + ->addCancelButton($cancel_uri)); + + $crumbs = $this->buildApplicationCrumbs(); - $panel = new AphrontPanelView(); if ($macro->getID()) { - $title = 'Edit Image Macro'; - } else { - $title = 'Create Image Macro'; - } - $panel->setHeader($title); - $panel->appendChild($form); - $panel->setWidth(AphrontPanelView::WIDTH_FULL); + $title = pht('Edit Image Macro'); + $crumb = pht('Edit'); - $nav = $this->buildSideNavView($macro); - $nav->selectFilter('#', 'edit'); - $nav->appendChild($error_view); - $nav->appendChild($panel); + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setHref($view_uri) + ->setName(pht('Macro "%s"', $macro->getName()))); + } else { + $title = pht('Create Image Macro'); + $crumb = pht('Create'); + } + + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setHref($request->getRequestURI()) + ->setName($crumb)); + + $header = id(new PhabricatorHeaderView()) + ->setHeader($title); + + + $upload = null; + if ($macro->getID()) { + $upload_header = id(new PhabricatorHeaderView()) + ->setHeader(pht('Upload New File')); + + $upload_form = id(new AphrontFormView()) + ->setFlexible(true) + ->setEncType('multipart/form-data') + ->setUser($request->getUser()) + ->appendChild( + id(new AphrontFormFileControl()) + ->setLabel('File') + ->setName('file')) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue('Upload File')); + + $upload = array($upload_header, $upload_form); + } return $this->buildApplicationPage( - $nav, + array( + $crumbs, + $header, + $error_view, + $form, + $upload, + ), array( 'title' => $title, )); diff --git a/src/applications/macro/controller/PhabricatorMacroListController.php b/src/applications/macro/controller/PhabricatorMacroListController.php index 3d73a4e5b7..7047a50c6c 100644 --- a/src/applications/macro/controller/PhabricatorMacroListController.php +++ b/src/applications/macro/controller/PhabricatorMacroListController.php @@ -70,8 +70,11 @@ final class PhabricatorMacroListController $filter_view = new AphrontListFilterView(); $filter_view->appendChild($filter_form); - $nav = $this->buildSideNavView(); - $nav->selectFilter('/'); + $has_search = strlen($filter); + $nav = $this->buildSideNavView( + $for_app = false, + $has_search); + $nav->selectFilter($has_search ? 'search' : '/'); $nav->appendChild($filter_view); @@ -97,7 +100,7 @@ final class PhabricatorMacroListController array(), 'Created on '.$datetime)); } - $item->setURI($this->getApplicationURI('/edit/'.$macro->getID().'/')); + $item->setURI($this->getApplicationURI('/view/'.$macro->getID().'/')); $item->setHeader($macro->getName()); $pinboard->addItem($item); @@ -109,11 +112,20 @@ final class PhabricatorMacroListController $nav->appendChild($list); } - - if ($filter === null) { + if (!strlen($filter)) { $nav->appendChild($pager); + $name = pht('All Macros'); + } else { + $name = pht('Search'); } + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName($name) + ->setHref($request->getRequestURI())); + $nav->setCrumbs($crumbs); + return $this->buildApplicationPage( $nav, array( diff --git a/src/applications/macro/controller/PhabricatorMacroViewController.php b/src/applications/macro/controller/PhabricatorMacroViewController.php new file mode 100644 index 0000000000..dac6c6e25d --- /dev/null +++ b/src/applications/macro/controller/PhabricatorMacroViewController.php @@ -0,0 +1,193 @@ +id = $data['id']; + } + + public function processRequest() { + $request = $this->getRequest(); + $user = $request->getUser(); + + $macro = id(new PhabricatorFileImageMacro())->load($this->id); + if (!$macro) { + return new Aphront404Response(); + } + + $file = id(new PhabricatorFile())->loadOneWhere( + 'phid = %s', + $macro->getFilePHID()); + + $title_short = pht('Macro "%s"', $macro->getName()); + $title_long = pht('Image Macro "%s"', $macro->getName()); + + $subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID( + $macro->getPHID()); + + $this->loadHandles($subscribers); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setHref($this->getApplicationURI('/view/'.$macro->getID().'/')) + ->setName($title_short)); + + $actions = $this->buildActionView($macro); + $properties = $this->buildPropertyView($macro, $file, $subscribers); + + $xactions = id(new PhabricatorMacroTransactionQuery()) + ->setViewer($request->getUser()) + ->withObjectPHIDs(array($macro->getPHID())) + ->execute(); + + $engine = id(new PhabricatorMarkupEngine()) + ->setViewer($user); + foreach ($xactions as $xaction) { + if ($xaction->getComment()) { + $engine->addObject( + $xaction->getComment(), + PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT); + } + } + $engine->process(); + + $timeline = id(new PhabricatorApplicationTransactionView()) + ->setViewer($user) + ->setTransactions($xactions) + ->setMarkupEngine($engine); + + $header = id(new PhabricatorHeaderView()) + ->setHeader($title_long); + + if ($macro->getIsDisabled()) { + $header->addTag( + id(new PhabricatorTagView()) + ->setType(PhabricatorTagView::TYPE_STATE) + ->setName(pht('Macro Disabled')) + ->setBackgroundColor(PhabricatorTagView::COLOR_RED)); + } + + $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); + + $add_comment_header = id(new PhabricatorHeaderView()) + ->setHeader( + $is_serious + ? pht('Add Comment') + : pht('Grovel in Awe')); + + $add_comment_form = id(new AphrontFormView()) + ->setWorkflow(true) + ->setFlexible(true) + ->setAction($this->getApplicationURI('/comment/'.$macro->getID().'/')) + ->setUser($user) + ->appendChild( + id(new PhabricatorRemarkupControl()) + ->setUser($user) + ->setLabel('Comment') + ->setName('comment')) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue( + $is_serious + ? pht('Add Comment') + : pht('Lavish Praise'))); + + return $this->buildApplicationPage( + array( + $crumbs, + $header, + $actions, + $properties, + $timeline, + $add_comment_header, + $add_comment_form, + ), + array( + 'title' => $title_short, + )); + } + + private function buildActionView(PhabricatorFileImageMacro $macro) { + $view = new PhabricatorActionListView(); + $view->setUser($this->getRequest()->getUser()); + $view->setObject($macro); + $view->addAction( + id(new PhabricatorActionView()) + ->setName('Edit Macro') + ->setHref($this->getApplicationURI('/edit/'.$macro->getID().'/')) + ->setIcon('edit')); + + if ($macro->getIsDisabled()) { + $view->addAction( + id(new PhabricatorActionView()) + ->setName('Restore Macro') + ->setHref($this->getApplicationURI('/disable/'.$macro->getID().'/')) + ->setWorkflow(true) + ->setIcon('undo')); + } else { + $view->addAction( + id(new PhabricatorActionView()) + ->setName('Disable Macro') + ->setHref($this->getApplicationURI('/disable/'.$macro->getID().'/')) + ->setWorkflow(true) + ->setIcon('delete')); + } + + return $view; + } + + private function buildPropertyView( + PhabricatorFileImageMacro $macro, + PhabricatorFile $file = null, + array $subscribers) { + + $view = new PhabricatorPropertyListView(); + + $view->addProperty( + pht('Name'), + phutil_escape_html($macro->getName())); + + $view->addProperty( + pht('Status'), + $macro->getIsDisabled() + ? pht('Disabled') + : pht('Enabled')); + + $view->addProperty( + pht('Created'), + phabricator_date( + $macro->getDateCreated(), + $this->getRequest()->getUser())); + + if ($subscribers) { + $sub_view = array(); + foreach ($subscribers as $subscriber) { + $sub_view[] = $this->getHandle($subscriber)->renderLink(); + } + $sub_view = implode(', ', $sub_view); + } else { + $sub_view = ''.pht('None').''; + } + + $view->addProperty( + pht('Subscribers'), + $sub_view); + + if ($file) { + $view->addTextContent( + phutil_render_tag( + 'img', + array( + 'src' => $file->getViewURI(), + 'class' => 'phabricator-image-macro-hero', + ))); + } + + return $view; + } + +} diff --git a/src/applications/macro/editor/PhabricatorMacroEditor.php b/src/applications/macro/editor/PhabricatorMacroEditor.php new file mode 100644 index 0000000000..e10046f8cc --- /dev/null +++ b/src/applications/macro/editor/PhabricatorMacroEditor.php @@ -0,0 +1,123 @@ +getTransactionType()) { + case PhabricatorMacroTransactionType::TYPE_NAME: + return $object->getName(); + case PhabricatorMacroTransactionType::TYPE_DISABLED: + return $object->getIsDisabled(); + case PhabricatorMacroTransactionType::TYPE_FILE: + return $object->getFilePHID(); + } + } + + protected function getCustomTransactionNewValue( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorMacroTransactionType::TYPE_NAME: + case PhabricatorMacroTransactionType::TYPE_DISABLED: + case PhabricatorMacroTransactionType::TYPE_FILE: + return $xaction->getNewValue(); + } + } + + protected function applyCustomInternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorMacroTransactionType::TYPE_NAME: + $object->setName($xaction->getNewValue()); + break; + case PhabricatorMacroTransactionType::TYPE_DISABLED: + $object->setIsDisabled($xaction->getNewValue()); + break; + case PhabricatorMacroTransactionType::TYPE_FILE: + $object->setFilePHID($xaction->getNewValue()); + break; + } + } + + protected function applyCustomExternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + return; + } + + protected function mergeTransactions( + PhabricatorApplicationTransaction $u, + PhabricatorApplicationTransaction $v) { + + $type = $u->getTransactionType(); + switch ($type) { + case PhabricatorMacroTransactionType::TYPE_NAME: + case PhabricatorMacroTransactionType::TYPE_DISABLED: + case PhabricatorMacroTransactionType::TYPE_FILE: + return $v; + } + + return parent::mergeTransactions($u, $v); + } + + protected function supportsMail() { + return true; + } + + protected function buildReplyHandler(PhabricatorLiskDAO $object) { + return id(new PhabricatorMacroReplyHandler()) + ->setMailReceiver($object); + } + + protected function buildMailTemplate(PhabricatorLiskDAO $object) { + $name = $object->getName(); + $name = 'Image Macro "'.$name.'"'; + + return id(new PhabricatorMetaMTAMail()) + ->setSubject($name) + ->addHeader('Thread-Topic', $name); + } + + protected function getMailTo(PhabricatorLiskDAO $object) { + return array( + $this->requireActor()->getPHID(), + ); + } + + protected function buildMailBody( + PhabricatorLiskDAO $object, + array $xactions) { + + $body = parent::buildMailBody($object, $xactions); + $body->addTextSection( + pht('MACRO DETAIL'), + PhabricatorEnv::getProductionURI('/macro/view/'.$object->getID().'/')); + + return $body; + } + + protected function getMailSubjectPrefix() { + return PhabricatorEnv::getEnvConfig('metamta.macro.subject-prefix'); + } + + protected function supportsFeed() { + return true; + } +} diff --git a/src/applications/macro/mail/PhabricatorMacroReplyHandler.php b/src/applications/macro/mail/PhabricatorMacroReplyHandler.php new file mode 100644 index 0000000000..6c932eb709 --- /dev/null +++ b/src/applications/macro/mail/PhabricatorMacroReplyHandler.php @@ -0,0 +1,40 @@ +getDefaultPrivateReplyHandlerEmailAddress($handle, 'MCRO'); + } + + public function getPublicReplyHandlerEmailAddress() { + return $this->getDefaultPublicReplyHandlerEmailAddress('MCRO'); + } + + public function getReplyHandlerDomain() { + return PhabricatorEnv::getEnvConfig( + 'metamta.macro.reply-handler-domain'); + } + + public function getReplyHandlerInstructions() { + if ($this->supportsReplies()) { + // TODO: Implement. + return null; + return "Reply to comment."; + } else { + return null; + } + } + + protected function receiveEmail(PhabricatorMetaMTAReceivedMail $mail) { + // TODO: Implement this. + return null; + } + +} diff --git a/src/applications/macro/query/PhabricatorMacroTransactionQuery.php b/src/applications/macro/query/PhabricatorMacroTransactionQuery.php new file mode 100644 index 0000000000..a2f9d3136a --- /dev/null +++ b/src/applications/macro/query/PhabricatorMacroTransactionQuery.php @@ -0,0 +1,10 @@ + false, + self::CONFIG_AUX_PHID => true, ) + parent::getConfiguration(); } + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + PhabricatorPHIDConstants::PHID_TYPE_MCRO); + } + static public function newFromImageURI($uri, $file_name, $image_macro_name) { $file = PhabricatorFile::newFromFileDownload($uri, $file_name); @@ -25,5 +33,10 @@ final class PhabricatorFileImageMacro extends PhabricatorFileDAO { return $image_macro; } + + public function isAutomaticallySubscribed($phid) { + return false; + } + } diff --git a/src/applications/macro/storage/PhabricatorMacroTransaction.php b/src/applications/macro/storage/PhabricatorMacroTransaction.php new file mode 100644 index 0000000000..5711d7899e --- /dev/null +++ b/src/applications/macro/storage/PhabricatorMacroTransaction.php @@ -0,0 +1,231 @@ +getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case PhabricatorMacroTransactionType::TYPE_FILE: + if ($old !== null) { + $phids[] = $old; + } + $phids[] = $new; + break; + } + + return $phids; + } + + public function shouldHide() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case PhabricatorMacroTransactionType::TYPE_NAME: + return ($old === null); + } + + return parent::shouldHide(); + } + + public function getTitle() { + $author_phid = $this->getAuthorPHID(); + + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case PhabricatorMacroTransactionType::TYPE_NAME: + return pht( + '%s renamed this macro from "%s" to "%s".', + $this->renderHandleLink($author_phid), + phutil_escape_html($old), + phutil_escape_html($new)); + break; + case PhabricatorMacroTransactionType::TYPE_DISABLED: + if ($new) { + return pht( + '%s disabled this macro.', + $this->renderHandleLink($author_phid)); + } else { + return pht( + '%s restored this macro.', + $this->renderHandleLink($author_phid)); + } + break; + case PhabricatorMacroTransactionType::TYPE_FILE: + if ($old === null) { + return pht( + '%s created this macro.', + $this->renderHandleLink($author_phid)); + } else { + return pht( + '%s changed the image for this macro from %s to %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($old), + $this->renderHandleLink($new)); + } + break; + } + + return parent::getTitle(); + } + + public function getTitleForFeed() { + $author_phid = $this->getAuthorPHID(); + $object_phid = $this->getObjectPHID(); + + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case PhabricatorMacroTransactionType::TYPE_NAME: + return pht( + '%s renamed %s from "%s" to "%s".', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid), + phutil_escape_html($old), + phutil_escape_html($new)); + case PhabricatorMacroTransactionType::TYPE_DISABLED: + if ($new) { + return pht( + '%s disabled %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + } else { + return pht( + '%s restored %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + } + case PhabricatorMacroTransactionType::TYPE_FILE: + if ($old === null) { + return pht( + '%s created %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + } else { + return pht( + '%s updated the image for %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + } + } + + return parent::getTitleForFeed(); + } + + public function getActionName() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case PhabricatorMacroTransactionType::TYPE_NAME: + if ($old === null) { + return pht('Created'); + } else { + return pht('Renamed'); + } + case PhabricatorMacroTransactionType::TYPE_DISABLED: + if ($new) { + return pht('Disabled'); + } else { + return pht('Restored'); + } + case PhabricatorMacroTransactionType::TYPE_FILE: + if ($old === null) { + return pht('Created'); + } else { + return pht('Edited Image'); + } + } + + return parent::getActionName(); + } + + public function getActionStrength() { + switch ($this->getTransactionType()) { + case PhabricatorMacroTransactionType::TYPE_DISABLED: + return 2.0; + case PhabricatorMacroTransactionType::TYPE_FILE: + return 1.5; + } + return parent::getActionStrength(); + } + + public function getIcon() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case PhabricatorMacroTransactionType::TYPE_NAME: + return 'edit'; + case PhabricatorMacroTransactionType::TYPE_FILE: + if ($old === null) { + return 'create'; + } else { + return 'edit'; + } + case PhabricatorMacroTransactionType::TYPE_DISABLED: + if ($new) { + return 'delete'; + } else { + return 'undo'; + } + } + + return parent::getIcon(); + } + + public function getColor() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case PhabricatorMacroTransactionType::TYPE_NAME: + return PhabricatorTransactions::COLOR_BLUE; + case PhabricatorMacroTransactionType::TYPE_FILE: + if ($old === null) { + return PhabricatorTransactions::COLOR_GREEN; + } else { + return PhabricatorTransactions::COLOR_BLUE; + } + case PhabricatorMacroTransactionType::TYPE_DISABLED: + if ($new) { + return PhabricatorTransactions::COLOR_BLACK; + } else { + return PhabricatorTransactions::COLOR_SKY; + } + } + + return parent::getColor(); + } + + +} + diff --git a/src/applications/macro/storage/PhabricatorMacroTransactionComment.php b/src/applications/macro/storage/PhabricatorMacroTransactionComment.php new file mode 100644 index 0000000000..565f5040ee --- /dev/null +++ b/src/applications/macro/storage/PhabricatorMacroTransactionComment.php @@ -0,0 +1,11 @@ +execute(); $xactions += mpull($results, null, 'getPHID'); break; + case PhabricatorPHIDConstants::PHID_TYPE_MCRO: + $results = id(new PhabricatorMacroTransactionQuery()) + ->setViewer($this->viewer) + ->withPHIDs($subtype_phids) + ->execute(); + $xactions += mpull($results, null, 'getPHID'); + break; } } foreach ($xactions as $xaction) { $objects[$xaction->getPHID()] = $xaction; } break; + case PhabricatorPHIDConstants::PHID_TYPE_MCRO: + $macros = id(new PhabricatorFileImageMacro())->loadAllWhere( + 'phid IN (%Ls)', + $phids); + $objects += mpull($macros, null, 'getPHID'); + break; } } @@ -601,6 +614,27 @@ final class PhabricatorObjectHandleData { $handles[$phid] = $handle; } break; + case PhabricatorPHIDConstants::PHID_TYPE_MCRO: + $macros = id(new PhabricatorFileImageMacro())->loadAllWhere( + 'phid IN (%Ls)', + $phids); + $macros = mpull($macros, null, 'getPHID'); + foreach ($phids as $phid) { + $handle = new PhabricatorObjectHandle(); + $handle->setPHID($phid); + $handle->setType($type); + if (empty($macros[$phid])) { + $handle->setName('Unknown Macro'); + } else { + $macro = $macros[$phid]; + $handle->setName($macro->getName()); + $handle->setFullName('Image Macro "'.$macro->getName().'"'); + $handle->setURI('/macro/view/'.$macro->getID().'/'); + $handle->setComplete(true); + } + $handles[$phid] = $handle; + } + break; default: $loader = null; if (isset($external_loaders[$type])) { diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index 9b124ec58c..cf386a5303 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -771,8 +771,6 @@ abstract class PhabricatorApplicationTransactionEditor $story_type = $this->getFeedStoryType(); $story_data = $this->getFeedStoryData($object, $xactions); - phlog($subscribed_phids); - id(new PhabricatorFeedStoryPublisher()) ->setStoryType($story_type) ->setStoryData($story_data) diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index 205f09b14a..15f9d81fc4 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -2,9 +2,7 @@ abstract class PhabricatorApplicationTransaction extends PhabricatorLiskDAO - implements PhabricatorPolicyInterface{ - - const MARKUP_FIELD_COMMENT = 'markup:comment'; + implements PhabricatorPolicyInterface { const TARGET_TEXT = 'text'; const TARGET_HTML = 'html'; @@ -17,7 +15,6 @@ abstract class PhabricatorApplicationTransaction protected $commentPHID; protected $commentVersion = 0; - protected $transactionType; protected $oldValue; protected $newValue; @@ -60,6 +57,10 @@ abstract class PhabricatorApplicationTransaction return PhabricatorContentSource::newFromSerialized($this->contentSource); } + public function hasComment() { + return $this->getComment() && strlen($this->getComment()->getContent()); + } + public function getComment() { if ($this->commentNotLoaded) { throw new Exception("Comment for this transaction was not loaded."); @@ -137,6 +138,16 @@ abstract class PhabricatorApplicationTransaction } public function getIcon() { + switch ($this->getTransactionType()) { + case PhabricatorTransactions::TYPE_COMMENT: + return 'comment'; + case PhabricatorTransactions::TYPE_SUBSCRIBERS: + return 'message'; + case PhabricatorTransactions::TYPE_VIEW_POLICY: + case PhabricatorTransactions::TYPE_EDIT_POLICY: + return 'lock'; + } + return null; } diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransactionComment.php b/src/applications/transactions/storage/PhabricatorApplicationTransactionComment.php index 7ca928f85b..f68325e585 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransactionComment.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransactionComment.php @@ -4,6 +4,8 @@ abstract class PhabricatorApplicationTransactionComment extends PhabricatorLiskDAO implements PhabricatorMarkupInterface, PhabricatorPolicyInterface { + const MARKUP_FIELD_COMMENT = 'markup:comment'; + protected $transactionPHID; protected $commentVersion; protected $authorPHID; diff --git a/src/applications/transactions/view/PhabricatorApplicationTransactionView.php b/src/applications/transactions/view/PhabricatorApplicationTransactionView.php new file mode 100644 index 0000000000..1fed5dada0 --- /dev/null +++ b/src/applications/transactions/view/PhabricatorApplicationTransactionView.php @@ -0,0 +1,81 @@ +anchorOffset = $anchor_offset; + return $this; + } + + public function setMarkupEngine(PhabricatorMarkupEngine $engine) { + $this->engine = $engine; + return $this; + } + + public function setTransactions(array $transactions) { + assert_instances_of($transactions, 'PhabricatorApplicationTransaction'); + $this->transactions = $transactions; + return $this; + } + + public function setViewer(PhabricatorUser $viewer) { + $this->viewer = $viewer; + return $this; + } + + public function render() { + $field = PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT; + + if (!$this->engine) { + $engine = id(new PhabricatorMarkupEngine()) + ->setViewer($this->viewer); + foreach ($this->transactions as $xaction) { + if (!$xaction->hasComment()) { + continue; + } + $engine->addObject($xaction->getComment(), $field); + } + $engine->process(); + + $this->engine = $engine; + } + + $view = new PhabricatorTimelineView(); + + $anchor = $this->anchorOffset; + foreach ($this->transactions as $xaction) { + if ($xaction->shouldHide()) { + continue; + } + + $anchor++; + $event = id(new PhabricatorTimelineEventView()) + ->setViewer($this->viewer) + ->setUserHandle($xaction->getHandle($xaction->getAuthorPHID())) + ->setIcon($xaction->getIcon()) + ->setColor($xaction->getColor()) + ->setTitle($xaction->getTitle()) + ->setDateCreated($xaction->getDateCreated()) + ->setContentSource($xaction->getContentSource()) + ->setAnchor($anchor); + + if ($xaction->hasComment()) { + $event->appendChild( + $this->engine->getOutput($xaction->getComment(), $field)); + } + + $view->addEvent($event); + } + + return $view->render(); + } +} + diff --git a/src/infrastructure/edges/constants/PhabricatorEdgeConfig.php b/src/infrastructure/edges/constants/PhabricatorEdgeConfig.php index 558349fae7..a9dc4560fd 100644 --- a/src/infrastructure/edges/constants/PhabricatorEdgeConfig.php +++ b/src/infrastructure/edges/constants/PhabricatorEdgeConfig.php @@ -108,6 +108,7 @@ final class PhabricatorEdgeConfig extends PhabricatorEdgeConstants { PhabricatorPHIDConstants::PHID_TYPE_QUES => 'PonderQuestion', PhabricatorPHIDConstants::PHID_TYPE_ANSW => 'PonderAnswer', PhabricatorPHIDConstants::PHID_TYPE_MOCK => 'PholioMock', + PhabricatorPHIDConstants::PHID_TYPE_MCRO => 'PhabricatorFileImageMacro', ); diff --git a/src/infrastructure/markup/rule/PhabricatorRemarkupRuleImageMacro.php b/src/infrastructure/markup/rule/PhabricatorRemarkupRuleImageMacro.php index 5a59fc41cd..19ff4b6bf0 100644 --- a/src/infrastructure/markup/rule/PhabricatorRemarkupRuleImageMacro.php +++ b/src/infrastructure/markup/rule/PhabricatorRemarkupRuleImageMacro.php @@ -18,7 +18,8 @@ final class PhabricatorRemarkupRuleImageMacro public function markupImageMacro($matches) { if ($this->images === null) { $this->images = array(); - $rows = id(new PhabricatorFileImageMacro())->loadAll(); + $rows = id(new PhabricatorFileImageMacro())->loadAllWhere( + 'isDisabled = 0'); foreach ($rows as $row) { $this->images[$row->getName()] = $row->getFilePHID(); } diff --git a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php index 4f82e93d06..2d1cae1c24 100644 --- a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php +++ b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php @@ -1048,6 +1048,18 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList { 'type' => 'sql', 'name' => $this->getPatchPath('20121209.pholioxactions.sql'), ), + '20121209.xmacroadd.sql' => array( + 'type' => 'sql', + 'name' => $this->getPatchPath('20121209.xmacroadd.sql'), + ), + '20121209.xmacromigrate.php' => array( + 'type' => 'php', + 'name' => $this->getPatchPath('20121209.xmacromigrate.php'), + ), + '20121209.xmacromigratekey.sql' => array( + 'type' => 'sql', + 'name' => $this->getPatchPath('20121209.xmacromigratekey.sql'), + ), ); } diff --git a/src/view/layout/PhabricatorHeaderView.php b/src/view/layout/PhabricatorHeaderView.php index 9ddb8d4f38..169b4b7f57 100644 --- a/src/view/layout/PhabricatorHeaderView.php +++ b/src/view/layout/PhabricatorHeaderView.php @@ -4,6 +4,7 @@ final class PhabricatorHeaderView extends AphrontView { private $objectName; private $header; + private $tags = array(); public function setHeader($header) { $this->header = $header; @@ -15,6 +16,11 @@ final class PhabricatorHeaderView extends AphrontView { return $this; } + public function addTag(PhabricatorTagView $tag) { + $this->tags[] = $tag; + return $this; + } + public function render() { require_celerity_resource('phabricator-header-view-css'); @@ -29,6 +35,15 @@ final class PhabricatorHeaderView extends AphrontView { phutil_escape_html($this->objectName)).' '.$header; } + if ($this->tags) { + $header .= phutil_render_tag( + 'div', + array( + 'class' => 'phabricator-header-tags', + ), + self::renderSingleView($this->tags)); + } + return phutil_render_tag( 'h1', array( diff --git a/webroot/rsrc/css/core/remarkup.css b/webroot/rsrc/css/core/remarkup.css index 3973f65b66..b38a65f82f 100644 --- a/webroot/rsrc/css/core/remarkup.css +++ b/webroot/rsrc/css/core/remarkup.css @@ -312,3 +312,8 @@ .remarkup-assist-right { float: right; } + +.phabricator-image-macro-hero { + margin: 2em auto; + max-width: 90%; +} diff --git a/webroot/rsrc/css/layout/phabricator-header-view.css b/webroot/rsrc/css/layout/phabricator-header-view.css index c3f96e9239..52fd75ba26 100644 --- a/webroot/rsrc/css/layout/phabricator-header-view.css +++ b/webroot/rsrc/css/layout/phabricator-header-view.css @@ -11,3 +11,8 @@ .device-desktop .phabricator-header-view { width: 66%; } + +.phabricator-header-tags { + font-size: 13px; + float: right; +}