diff --git a/resources/sql/autopatches/20161115.phamepost.01.subtitle.sql b/resources/sql/autopatches/20161115.phamepost.01.subtitle.sql new file mode 100644 index 0000000000..c3047bba82 --- /dev/null +++ b/resources/sql/autopatches/20161115.phamepost.01.subtitle.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phame.phame_post + ADD subtitle VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT}; diff --git a/src/applications/phame/editor/PhamePostEditEngine.php b/src/applications/phame/editor/PhamePostEditEngine.php index 382ef3f8ff..e80c1f6d0d 100644 --- a/src/applications/phame/editor/PhamePostEditEngine.php +++ b/src/applications/phame/editor/PhamePostEditEngine.php @@ -1,126 +1,134 @@ blog = $blog; return $this; } public function getEngineApplicationClass() { return 'PhabricatorPhameApplication'; } protected function newEditableObject() { $viewer = $this->getViewer(); if ($this->blog) { $blog = $this->blog; } else { $blog = PhameBlog::initializeNewBlog($viewer); } return PhamePost::initializePost($viewer, $blog); } protected function newObjectQuery() { return new PhamePostQuery(); } protected function getObjectCreateTitleText($object) { return pht('Create New Post'); } protected function getObjectEditTitleText($object) { return pht('Edit %s', $object->getTitle()); } protected function getObjectEditShortText($object) { return $object->getTitle(); } protected function getObjectCreateShortText() { return pht('Create Post'); } protected function getObjectName() { return pht('Post'); } protected function getObjectViewURI($object) { return $object->getViewURI(); } protected function getEditorURI() { return $this->getApplication()->getApplicationURI('post/edit/'); } protected function buildCustomEditFields($object) { $blog_phid = $object->getBlog()->getPHID(); return array( id(new PhabricatorHandlesEditField()) ->setKey('blog') ->setLabel(pht('Blog')) ->setDescription(pht('Blog to publish this post to.')) ->setConduitDescription( pht('Choose a blog to create a post on (or move a post to).')) ->setConduitTypeDescription(pht('PHID of the blog.')) ->setAliases(array('blogPHID')) ->setTransactionType(PhamePostTransaction::TYPE_BLOG) ->setHandleParameterType(new AphrontPHIDListHTTPParameterType()) ->setSingleValue($blog_phid) ->setIsReorderable(false) ->setIsDefaultable(false) ->setIsLockable(false) ->setIsLocked(true), id(new PhabricatorTextEditField()) ->setKey('title') ->setLabel(pht('Title')) ->setDescription(pht('Post title.')) ->setConduitDescription(pht('Retitle the post.')) ->setConduitTypeDescription(pht('New post title.')) ->setTransactionType(PhamePostTransaction::TYPE_TITLE) ->setValue($object->getTitle()), + id(new PhabricatorTextEditField()) + ->setKey('subtitle') + ->setLabel(pht('Subtitle')) + ->setDescription(pht('Post subtitle.')) + ->setConduitDescription(pht('Change the post subtitle.')) + ->setConduitTypeDescription(pht('New post subtitle.')) + ->setTransactionType(PhamePostTransaction::TYPE_SUBTITLE) + ->setValue($object->getSubtitle()), id(new PhabricatorSelectEditField()) ->setKey('visibility') ->setLabel(pht('Visibility')) ->setDescription(pht('Post visibility.')) ->setConduitDescription(pht('Change post visibility.')) ->setConduitTypeDescription(pht('New post visibility constant.')) ->setTransactionType(PhamePostTransaction::TYPE_VISIBILITY) ->setValue($object->getVisibility()) ->setOptions(PhameConstants::getPhamePostStatusMap()), id(new PhabricatorRemarkupEditField()) ->setKey('body') ->setLabel(pht('Body')) ->setDescription(pht('Post body.')) ->setConduitDescription(pht('Change post body.')) ->setConduitTypeDescription(pht('New post body.')) ->setTransactionType(PhamePostTransaction::TYPE_BODY) ->setValue($object->getBody()) ->setPreviewPanel( id(new PHUIRemarkupPreviewPanel()) ->setHeader(pht('Blog Post')) ->setPreviewType(PHUIRemarkupPreviewPanel::DOCUMENT)), ); } } diff --git a/src/applications/phame/editor/PhamePostEditor.php b/src/applications/phame/editor/PhamePostEditor.php index 363f39fb46..156059cae6 100644 --- a/src/applications/phame/editor/PhamePostEditor.php +++ b/src/applications/phame/editor/PhamePostEditor.php @@ -1,284 +1,291 @@ getTransactionType()) { case PhamePostTransaction::TYPE_BLOG: return $object->getBlogPHID(); case PhamePostTransaction::TYPE_TITLE: return $object->getTitle(); + case PhamePostTransaction::TYPE_SUBTITLE: + return $object->getSubtitle(); case PhamePostTransaction::TYPE_BODY: return $object->getBody(); case PhamePostTransaction::TYPE_VISIBILITY: return $object->getVisibility(); } } protected function getCustomTransactionNewValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhamePostTransaction::TYPE_TITLE: + case PhamePostTransaction::TYPE_SUBTITLE: case PhamePostTransaction::TYPE_BODY: case PhamePostTransaction::TYPE_VISIBILITY: case PhamePostTransaction::TYPE_BLOG: return $xaction->getNewValue(); } } protected function applyCustomInternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhamePostTransaction::TYPE_TITLE: return $object->setTitle($xaction->getNewValue()); + case PhamePostTransaction::TYPE_SUBTITLE: + return $object->setSubtitle($xaction->getNewValue()); case PhamePostTransaction::TYPE_BODY: return $object->setBody($xaction->getNewValue()); case PhamePostTransaction::TYPE_BLOG: return $object->setBlogPHID($xaction->getNewValue()); case PhamePostTransaction::TYPE_VISIBILITY: if ($xaction->getNewValue() == PhameConstants::VISIBILITY_DRAFT) { $object->setDatePublished(0); } else if ($xaction->getNewValue() == PhameConstants::VISIBILITY_ARCHIVED) { $object->setDatePublished(0); } else { $object->setDatePublished(PhabricatorTime::getNow()); } return $object->setVisibility($xaction->getNewValue()); } return parent::applyCustomInternalTransaction($object, $xaction); } protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhamePostTransaction::TYPE_TITLE: + case PhamePostTransaction::TYPE_SUBTITLE: case PhamePostTransaction::TYPE_BODY: case PhamePostTransaction::TYPE_VISIBILITY: case PhamePostTransaction::TYPE_BLOG: return; } return parent::applyCustomExternalTransaction($object, $xaction); } protected function validateTransaction( PhabricatorLiskDAO $object, $type, array $xactions) { $errors = parent::validateTransaction($object, $type, $xactions); switch ($type) { case PhamePostTransaction::TYPE_TITLE: $missing = $this->validateIsEmptyTextField( $object->getTitle(), $xactions); if ($missing) { $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Required'), pht('Title is required.'), nonempty(last($xactions), null)); $error->setIsMissingFieldError(true); $errors[] = $error; } break; case PhamePostTransaction::TYPE_BLOG: if ($this->getIsNewObject()) { if (!$xactions) { $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Required'), pht( 'When creating a post, you must specify which blog it '. 'should belong to.'), null); $error->setIsMissingFieldError(true); $errors[] = $error; break; } } foreach ($xactions as $xaction) { $new_phid = $xaction->getNewValue(); $blog = id(new PhameBlogQuery()) ->setViewer($this->getActor()) ->withPHIDs(array($new_phid)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->execute(); if ($blog) { continue; } $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), pht( 'The specified blog PHID ("%s") is not valid. You can only '. 'create a post on (or move a post into) a blog which you '. 'have permission to see and edit.', $new_phid), $xaction); } break; } return $errors; } protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { if ($object->isDraft() || ($object->isArchived())) { return false; } return true; } protected function shouldPublishFeedStory( PhabricatorLiskDAO $object, array $xactions) { if ($object->isDraft() || $object->isArchived()) { return false; } return true; } protected function getMailTo(PhabricatorLiskDAO $object) { $phids = array(); $phids[] = $object->getBloggerPHID(); $phids[] = $this->requireActor()->getPHID(); $blog_phid = $object->getBlogPHID(); if ($blog_phid) { $cc_phids = PhabricatorSubscribersQuery::loadSubscribersForPHID( $blog_phid); foreach ($cc_phids as $cc) { $phids[] = $cc; } } return $phids; } protected function buildMailTemplate(PhabricatorLiskDAO $object) { $phid = $object->getPHID(); $title = $object->getTitle(); return id(new PhabricatorMetaMTAMail()) ->setSubject($title) ->addHeader('Thread-Topic', $phid); } protected function buildReplyHandler(PhabricatorLiskDAO $object) { return id(new PhamePostReplyHandler()) ->setMailReceiver($object); } protected function buildMailBody( PhabricatorLiskDAO $object, array $xactions) { $body = parent::buildMailBody($object, $xactions); // We don't send mail if the object is a draft, and we only want // to include the full body of the post on the either the // first creation or if it was created as a draft, once it goes live. if ($this->getIsNewObject()) { $body->addRemarkupSection(null, $object->getBody()); } else { foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { case PhamePostTransaction::TYPE_VISIBILITY: if (!$object->isDraft() && !$object->isArchived()) { $body->addRemarkupSection(null, $object->getBody()); } break; } } } $body->addLinkSection( pht('POST DETAIL'), PhabricatorEnv::getProductionURI($object->getViewURI())); return $body; } public function getMailTagsMap() { return array( PhamePostTransaction::MAILTAG_CONTENT => pht("A post's content changes."), PhamePostTransaction::MAILTAG_SUBSCRIBERS => pht("A post's subscribers change."), PhamePostTransaction::MAILTAG_COMMENT => pht('Someone comments on a post.'), PhamePostTransaction::MAILTAG_OTHER => pht('Other post activity not listed above occurs.'), ); } protected function getMailSubjectPrefix() { return '[Phame]'; } protected function supportsSearch() { return true; } protected function shouldApplyHeraldRules( PhabricatorLiskDAO $object, array $xactions) { return true; } protected function buildHeraldAdapter( PhabricatorLiskDAO $object, array $xactions) { return id(new HeraldPhamePostAdapter()) ->setPost($object); } } diff --git a/src/applications/phame/storage/PhamePost.php b/src/applications/phame/storage/PhamePost.php index 351bcb5dc0..7eb05e8866 100644 --- a/src/applications/phame/storage/PhamePost.php +++ b/src/applications/phame/storage/PhamePost.php @@ -1,371 +1,373 @@ setBloggerPHID($blogger->getPHID()) ->setBlogPHID($blog->getPHID()) ->attachBlog($blog) ->setDatePublished(PhabricatorTime::getNow()) ->setVisibility(PhameConstants::VISIBILITY_PUBLISHED); return $post; } public function attachBlog(PhameBlog $blog) { $this->blog = $blog; return $this; } public function getBlog() { return $this->assertAttached($this->blog); } public function getMonogram() { return 'J'.$this->getID(); } public function getLiveURI() { $blog = $this->getBlog(); $is_draft = $this->isDraft(); $is_archived = $this->isArchived(); if (strlen($blog->getDomain()) && !$is_draft && !$is_archived) { return $this->getExternalLiveURI(); } else { return $this->getInternalLiveURI(); } } public function getExternalLiveURI() { $id = $this->getID(); $slug = $this->getSlug(); $path = "/post/{$id}/{$slug}/"; $domain = $this->getBlog()->getDomain(); return (string)id(new PhutilURI('http://'.$domain)) ->setPath($path); } public function getInternalLiveURI() { $id = $this->getID(); $slug = $this->getSlug(); $blog_id = $this->getBlog()->getID(); return "/phame/live/{$blog_id}/post/{$id}/{$slug}/"; } public function getViewURI() { $id = $this->getID(); $slug = $this->getSlug(); return "/phame/post/view/{$id}/{$slug}/"; } public function getBestURI($is_live, $is_external) { if ($is_live) { if ($is_external) { return $this->getExternalLiveURI(); } else { return $this->getInternalLiveURI(); } } else { return $this->getViewURI(); } } public function getEditURI() { return '/phame/post/edit/'.$this->getID().'/'; } public function isDraft() { return ($this->getVisibility() == PhameConstants::VISIBILITY_DRAFT); } public function isArchived() { return ($this->getVisibility() == PhameConstants::VISIBILITY_ARCHIVED); } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'configData' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( 'title' => 'text255', + 'subtitle' => 'text64', 'phameTitle' => 'sort64?', 'visibility' => 'uint32', 'mailKey' => 'bytes20', // T6203/NULLABILITY // These seem like they should always be non-null? 'blogPHID' => 'phid?', 'body' => 'text?', 'configData' => 'text?', // T6203/NULLABILITY // This one probably should be nullable? 'datePublished' => 'epoch', ), self::CONFIG_KEY_SCHEMA => array( 'key_phid' => null, 'phid' => array( 'columns' => array('phid'), 'unique' => true, ), 'bloggerPosts' => array( 'columns' => array( 'bloggerPHID', 'visibility', 'datePublished', 'id', ), ), ), ) + parent::getConfiguration(); } public function save() { if (!$this->getMailKey()) { $this->setMailKey(Filesystem::readRandomCharacters(20)); } return parent::save(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorPhamePostPHIDType::TYPECONST); } public function getSlug() { return PhabricatorSlug::normalizeProjectSlug($this->getTitle(), true); } /* -( PhabricatorPolicyInterface Implementation )-------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { // Draft posts are visible only to the author. Published posts are visible // to whoever the blog is visible to. switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: if (!$this->isDraft() && !$this->isArchived() && $this->getBlog()) { return $this->getBlog()->getViewPolicy(); } else if ($this->getBlog()) { return $this->getBlog()->getEditPolicy(); } else { return PhabricatorPolicies::POLICY_NOONE; } break; case PhabricatorPolicyCapability::CAN_EDIT: if ($this->getBlog()) { return $this->getBlog()->getEditPolicy(); } else { return PhabricatorPolicies::POLICY_NOONE; } } } public function hasAutomaticCapability($capability, PhabricatorUser $user) { // A blog post's author can always view it. switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: case PhabricatorPolicyCapability::CAN_EDIT: return ($user->getPHID() == $this->getBloggerPHID()); } } public function describeAutomaticCapability($capability) { return pht('The author of a blog post can always view and edit it.'); } /* -( PhabricatorMarkupInterface Implementation )-------------------------- */ public function getMarkupFieldKey($field) { $hash = PhabricatorHash::digest($this->getMarkupText($field)); return $this->getPHID().':'.$field.':'.$hash; } public function newMarkupEngine($field) { return PhabricatorMarkupEngine::newPhameMarkupEngine(); } public function getMarkupText($field) { switch ($field) { case self::MARKUP_FIELD_BODY: return $this->getBody(); case self::MARKUP_FIELD_SUMMARY: return PhabricatorMarkupEngine::summarize($this->getBody()); } } public function didMarkupText( $field, $output, PhutilMarkupEngine $engine) { return $output; } public function shouldUseMarkupCache($field) { return (bool)$this->getPHID(); } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new PhamePostEditor(); } public function getApplicationTransactionObject() { return $this; } public function getApplicationTransactionTemplate() { return new PhamePostTransaction(); } public function willRenderTimeline( PhabricatorApplicationTransactionView $timeline, AphrontRequest $request) { return $timeline; } /* -( PhabricatorDestructibleInterface )----------------------------------- */ public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { $this->openTransaction(); $this->delete(); $this->saveTransaction(); } /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ public function getUsersToNotifyOfTokenGiven() { return array( $this->getBloggerPHID(), ); } /* -( PhabricatorSubscribableInterface Implementation )-------------------- */ public function isAutomaticallySubscribed($phid) { return ($this->bloggerPHID == $phid); } /* -( PhabricatorConduitResultInterface )---------------------------------- */ public function getFieldSpecificationsForConduit() { return array( id(new PhabricatorConduitSearchFieldSpecification()) ->setKey('title') ->setType('string') ->setDescription(pht('Title of the post.')), id(new PhabricatorConduitSearchFieldSpecification()) ->setKey('slug') ->setType('string') ->setDescription(pht('Slug for the post.')), id(new PhabricatorConduitSearchFieldSpecification()) ->setKey('blogPHID') ->setType('phid') ->setDescription(pht('PHID of the blog that the post belongs to.')), id(new PhabricatorConduitSearchFieldSpecification()) ->setKey('authorPHID') ->setType('phid') ->setDescription(pht('PHID of the author of the post.')), id(new PhabricatorConduitSearchFieldSpecification()) ->setKey('body') ->setType('string') ->setDescription(pht('Body of the post.')), id(new PhabricatorConduitSearchFieldSpecification()) ->setKey('datePublished') ->setType('epoch?') ->setDescription(pht('Publish date, if the post has been published.')), ); } public function getFieldValuesForConduit() { if ($this->isDraft()) { $date_published = null; } else if ($this->isArchived()) { $date_published = null; } else { $date_published = (int)$this->getDatePublished(); } return array( 'title' => $this->getTitle(), 'slug' => $this->getSlug(), 'blogPHID' => $this->getBlogPHID(), 'authorPHID' => $this->getBloggerPHID(), 'body' => $this->getBody(), 'datePublished' => $date_published, ); } public function getConduitSearchAttachments() { return array(); } /* -( PhabricatorFulltextInterface )--------------------------------------- */ public function newFulltextEngine() { return new PhamePostFulltextEngine(); } } diff --git a/src/applications/phame/storage/PhamePostTransaction.php b/src/applications/phame/storage/PhamePostTransaction.php index a1efb584b0..5142e0d594 100644 --- a/src/applications/phame/storage/PhamePostTransaction.php +++ b/src/applications/phame/storage/PhamePostTransaction.php @@ -1,273 +1,294 @@ getTransactionType()) { case self::TYPE_BODY: $blocks[] = $this->getNewValue(); break; } return $blocks; } public function shouldHide() { return parent::shouldHide(); } public function getRequiredHandlePHIDs() { $phids = parent::getRequiredHandlePHIDs(); switch ($this->getTransactionType()) { case self::TYPE_BLOG: $old = $this->getOldValue(); $new = $this->getNewValue(); if ($old) { $phids[] = $old; } if ($new) { $phids[] = $new; } break; } return $phids; } public function getIcon() { $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_CREATE: return 'fa-plus'; break; case self::TYPE_VISIBILITY: if ($new == PhameConstants::VISIBILITY_PUBLISHED) { return 'fa-globe'; } else if ($new == PhameConstants::VISIBILITY_ARCHIVED) { return 'fa-ban'; } else { return 'fa-eye-slash'; } break; } return parent::getIcon(); } public function getMailTags() { $tags = parent::getMailTags(); switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: $tags[] = self::MAILTAG_COMMENT; break; case PhabricatorTransactions::TYPE_SUBSCRIBERS: $tags[] = self::MAILTAG_SUBSCRIBERS; break; case self::TYPE_TITLE: + case self::TYPE_SUBTITLE: case self::TYPE_BODY: $tags[] = self::MAILTAG_CONTENT; break; default: $tags[] = self::MAILTAG_OTHER; break; } return $tags; } public function getTitle() { $author_phid = $this->getAuthorPHID(); $object_phid = $this->getObjectPHID(); $old = $this->getOldValue(); $new = $this->getNewValue(); $type = $this->getTransactionType(); switch ($type) { case PhabricatorTransactions::TYPE_CREATE: return pht( '%s authored this post.', $this->renderHandleLink($author_phid)); case self::TYPE_BLOG: return pht( '%s moved this post from "%s" to "%s".', $this->renderHandleLink($author_phid), $this->renderHandleLink($old), $this->renderHandleLink($new)); case self::TYPE_TITLE: if ($old === null) { return pht( '%s authored this post.', $this->renderHandleLink($author_phid)); } else { return pht( '%s updated the post\'s name to "%s".', $this->renderHandleLink($author_phid), $new); } break; + case self::TYPE_SUBTITLE: + if ($old === null) { + return pht( + '%s set the post\'s subtitle to "%s".', + $this->renderHandleLink($author_phid), + $new); + } else { + return pht( + '%s updated the post\'s subtitle to "%s".', + $this->renderHandleLink($author_phid), + $new); + } + break; case self::TYPE_BODY: return pht( '%s updated the blog post.', $this->renderHandleLink($author_phid)); break; case self::TYPE_VISIBILITY: if ($new == PhameConstants::VISIBILITY_DRAFT) { return pht( '%s marked this post as a draft.', $this->renderHandleLink($author_phid)); } else if ($new == PhameConstants::VISIBILITY_ARCHIVED) { return pht( '%s archived this post.', $this->renderHandleLink($author_phid)); } else { return pht( '%s published this post.', $this->renderHandleLink($author_phid)); } break; } return parent::getTitle(); } public function getTitleForFeed() { $author_phid = $this->getAuthorPHID(); $object_phid = $this->getObjectPHID(); $old = $this->getOldValue(); $new = $this->getNewValue(); $type = $this->getTransactionType(); switch ($type) { case PhabricatorTransactions::TYPE_CREATE: return pht( '%s authored %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); case self::TYPE_BLOG: return pht( '%s moved post "%s" from "%s" to "%s".', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $this->renderHandleLink($old), $this->renderHandleLink($new)); case self::TYPE_TITLE: if ($old === null) { return pht( '%s authored %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } else { return pht( '%s updated the name for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } break; + case self::TYPE_SUBTITLE: + return pht( + '%s updated the subtitle for %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + break; case self::TYPE_BODY: return pht( '%s updated the blog post %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); break; case self::TYPE_VISIBILITY: if ($new == PhameConstants::VISIBILITY_DRAFT) { return pht( '%s marked %s as a draft.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } else if ($new == PhameConstants::VISIBILITY_ARCHIVED) { return pht( '%s marked %s as archived.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } else { return pht( '%s published %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } break; } return parent::getTitleForFeed(); } public function getRemarkupBodyForFeed(PhabricatorFeedStory $story) { $old = $this->getOldValue(); switch ($this->getTransactionType()) { case self::TYPE_BODY: if ($old === null) { return $this->getNewValue(); } break; } return null; } public function getColor() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_CREATE: return PhabricatorTransactions::COLOR_GREEN; } return parent::getColor(); } public function hasChangeDetails() { switch ($this->getTransactionType()) { case self::TYPE_BODY: return ($this->getOldValue() !== null); } return parent::hasChangeDetails(); } public function renderChangeDetails(PhabricatorUser $viewer) { switch ($this->getTransactionType()) { case self::TYPE_BODY: $old = $this->getOldValue(); $new = $this->getNewValue(); return $this->renderTextCorpusChangeDetails( $viewer, $old, $new); } return parent::renderChangeDetails($viewer); } }