diff --git a/resources/sql/autopatches/20180215.phriction.03.descempty.sql b/resources/sql/autopatches/20180215.phriction.03.descempty.sql new file mode 100644 index 0000000000..c41df5285a --- /dev/null +++ b/resources/sql/autopatches/20180215.phriction.03.descempty.sql @@ -0,0 +1,2 @@ +UPDATE {$NAMESPACE}_phriction.phriction_content + SET description = '' WHERE description IS NULL; diff --git a/resources/sql/autopatches/20180215.phriction.04.descnull.sql b/resources/sql/autopatches/20180215.phriction.04.descnull.sql new file mode 100644 index 0000000000..3ff017cd64 --- /dev/null +++ b/resources/sql/autopatches/20180215.phriction.04.descnull.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phriction.phriction_content + CHANGE description description LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}; diff --git a/src/applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php b/src/applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php index fc28b53b66..300dbbfa80 100644 --- a/src/applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php +++ b/src/applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php @@ -1,71 +1,71 @@ <?php final class PhrictionCreateConduitAPIMethod extends PhrictionConduitAPIMethod { public function getAPIMethodName() { return 'phriction.create'; } public function getMethodDescription() { return pht('Create a Phriction document.'); } protected function defineParamTypes() { return array( 'slug' => 'required string', 'title' => 'required string', 'content' => 'required string', 'description' => 'optional string', ); } protected function defineReturnType() { return 'nonempty dict'; } protected function execute(ConduitAPIRequest $request) { $slug = $request->getValue('slug'); if (!strlen($slug)) { throw new Exception(pht('No such document.')); } $doc = id(new PhrictionDocumentQuery()) ->setViewer($request->getUser()) ->withSlugs(array(PhabricatorSlug::normalize($slug))) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if ($doc) { throw new Exception(pht('Document already exists!')); } $doc = PhrictionDocument::initializeNewDocument( $request->getUser(), $slug); $xactions = array(); $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhrictionDocumentTitleTransaction::TRANSACTIONTYPE) ->setNewValue($request->getValue('title')); $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhrictionDocumentContentTransaction::TRANSACTIONTYPE) ->setNewValue($request->getValue('content')); $editor = id(new PhrictionTransactionEditor()) ->setActor($request->getUser()) ->setContentSource($request->newContentSource()) ->setContinueOnNoEffect(true) - ->setDescription($request->getValue('description')); + ->setDescription((string)$request->getValue('description')); try { $editor->applyTransactions($doc, $xactions); } catch (PhabricatorApplicationTransactionValidationException $ex) { // TODO - some magical hotness via T5873 throw $ex; } return $this->buildDocumentInfoDictionary($doc); } } diff --git a/src/applications/phriction/conduit/PhrictionEditConduitAPIMethod.php b/src/applications/phriction/conduit/PhrictionEditConduitAPIMethod.php index 70c02d376a..9d97ed7f8c 100644 --- a/src/applications/phriction/conduit/PhrictionEditConduitAPIMethod.php +++ b/src/applications/phriction/conduit/PhrictionEditConduitAPIMethod.php @@ -1,67 +1,67 @@ <?php final class PhrictionEditConduitAPIMethod extends PhrictionConduitAPIMethod { public function getAPIMethodName() { return 'phriction.edit'; } public function getMethodDescription() { return pht('Update a Phriction document.'); } protected function defineParamTypes() { return array( 'slug' => 'required string', 'title' => 'optional string', 'content' => 'optional string', 'description' => 'optional string', ); } protected function defineReturnType() { return 'nonempty dict'; } protected function execute(ConduitAPIRequest $request) { $slug = $request->getValue('slug'); $doc = id(new PhrictionDocumentQuery()) ->setViewer($request->getUser()) ->withSlugs(array(PhabricatorSlug::normalize($slug))) ->needContent(true) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$doc) { throw new Exception(pht('No such document.')); } $xactions = array(); $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhrictionDocumentTitleTransaction::TRANSACTIONTYPE) ->setNewValue($request->getValue('title')); $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhrictionDocumentContentTransaction::TRANSACTIONTYPE) ->setNewValue($request->getValue('content')); $editor = id(new PhrictionTransactionEditor()) ->setActor($request->getUser()) ->setContentSource($request->newContentSource()) ->setContinueOnNoEffect(true) - ->setDescription($request->getValue('description')); + ->setDescription((string)$request->getValue('description')); try { $editor->applyTransactions($doc, $xactions); } catch (PhabricatorApplicationTransactionValidationException $ex) { // TODO - some magical hotness via T5873 throw $ex; } return $this->buildDocumentInfoDictionary($doc); } } diff --git a/src/applications/phriction/controller/PhrictionHistoryController.php b/src/applications/phriction/controller/PhrictionHistoryController.php index 9b0d3188a2..432b1dfdef 100644 --- a/src/applications/phriction/controller/PhrictionHistoryController.php +++ b/src/applications/phriction/controller/PhrictionHistoryController.php @@ -1,179 +1,176 @@ <?php final class PhrictionHistoryController extends PhrictionController { public function shouldAllowPublic() { return true; } public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $slug = $request->getURIData('slug'); $document = id(new PhrictionDocumentQuery()) ->setViewer($viewer) ->withSlugs(array(PhabricatorSlug::normalize($slug))) ->needContent(true) ->executeOne(); if (!$document) { return new Aphront404Response(); } $current = $document->getContent(); - $pager = new PHUIPagerView(); - $pager->setOffset($request->getInt('page')); - $pager->setURI($request->getRequestURI(), 'page'); - - $history = id(new PhrictionContent())->loadAllWhere( - 'documentID = %d ORDER BY version DESC LIMIT %d, %d', - $document->getID(), - $pager->getOffset(), - $pager->getPageSize() + 1); - $history = $pager->sliceResults($history); + $pager = id(new AphrontCursorPagerView()) + ->readFromRequest($request); + + $history = id(new PhrictionContentQuery()) + ->setViewer($viewer) + ->withDocumentPHIDs(array($document->getPHID())) + ->executeWithCursorPager($pager); $author_phids = mpull($history, 'getAuthorPHID'); $handles = $this->loadViewerHandles($author_phids); $list = new PHUIObjectItemListView(); $list->setFlush(true); foreach ($history as $content) { $author = $handles[$content->getAuthorPHID()]->renderLink(); $slug_uri = PhrictionDocument::getSlugURI($document->getSlug()); $version = $content->getVersion(); $diff_uri = new PhutilURI('/phriction/diff/'.$document->getID().'/'); $vs_previous = null; if ($content->getVersion() != 1) { $vs_previous = $diff_uri ->alter('l', $content->getVersion() - 1) ->alter('r', $content->getVersion()); } $vs_head = null; if ($content->getID() != $document->getContentID()) { $vs_head = $diff_uri ->alter('l', $content->getVersion()) ->alter('r', $current->getVersion()); } $change_type = PhrictionChangeType::getChangeTypeLabel( $content->getChangeType()); switch ($content->getChangeType()) { case PhrictionChangeType::CHANGE_DELETE: $color = 'red'; break; case PhrictionChangeType::CHANGE_EDIT: $color = 'lightbluetext'; break; case PhrictionChangeType::CHANGE_MOVE_HERE: $color = 'yellow'; break; case PhrictionChangeType::CHANGE_MOVE_AWAY: $color = 'orange'; break; case PhrictionChangeType::CHANGE_STUB: $color = 'green'; break; default: throw new Exception(pht('Unknown change type!')); break; } $item = id(new PHUIObjectItemView()) ->setHeader(pht('%s by %s', $change_type, $author)) ->setStatusIcon('fa-file '.$color) ->addAttribute( phutil_tag( 'a', array( 'href' => $slug_uri.'?v='.$version, ), pht('Version %s', $version))) ->addAttribute(pht('%s %s', phabricator_date($content->getDateCreated(), $viewer), phabricator_time($content->getDateCreated(), $viewer))); if ($content->getDescription()) { $item->addAttribute($content->getDescription()); } if ($vs_previous) { $item->addIcon( 'fa-reply', pht('Show Change'), array( 'href' => $vs_previous, )); } else { $item->addIcon( 'fa-reply grey', phutil_tag('em', array(), pht('No previous change'))); } if ($vs_head) { $item->addIcon( 'fa-reply-all', pht('Show Later Changes'), array( 'href' => $vs_head, )); } else { $item->addIcon( 'fa-reply-all grey', phutil_tag('em', array(), pht('No later changes'))); } $list->addItem($item); } $crumbs = $this->buildApplicationCrumbs(); $crumb_views = $this->renderBreadcrumbs($document->getSlug()); foreach ($crumb_views as $view) { $crumbs->addCrumb($view); } $crumbs->addTextCrumb( pht('History'), PhrictionDocument::getSlugURI($document->getSlug(), 'history')); $crumbs->setBorder(true); $header = new PHUIHeaderView(); $header->setHeader(phutil_tag( 'a', array('href' => PhrictionDocument::getSlugURI($document->getSlug())), head($history)->getTitle())); $header->setSubheader(pht('Document History')); $obj_box = id(new PHUIObjectBoxView()) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList($list); $pager = id(new PHUIBoxView()) ->addClass('ml') ->appendChild($pager); $header = id(new PHUIHeaderView()) ->setHeader(pht('Document History: %s', head($history)->getTitle())) ->setHeaderIcon('fa-history'); $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setFooter(array( $obj_box, $pager, )); $title = pht('Document History'); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild($view); } } diff --git a/src/applications/phriction/editor/PhrictionTransactionEditor.php b/src/applications/phriction/editor/PhrictionTransactionEditor.php index 73aee3fd4c..793c609517 100644 --- a/src/applications/phriction/editor/PhrictionTransactionEditor.php +++ b/src/applications/phriction/editor/PhrictionTransactionEditor.php @@ -1,619 +1,622 @@ <?php final class PhrictionTransactionEditor extends PhabricatorApplicationTransactionEditor { const VALIDATE_CREATE_ANCESTRY = 'create'; const VALIDATE_MOVE_ANCESTRY = 'move'; private $description; private $oldContent; private $newContent; private $moveAwayDocument; private $skipAncestorCheck; private $contentVersion; private $processContentVersionError = true; private $contentDiffURI; public function setDescription($description) { $this->description = $description; return $this; } private function getDescription() { return $this->description; } private function setOldContent(PhrictionContent $content) { $this->oldContent = $content; return $this; } public function getOldContent() { return $this->oldContent; } private function setNewContent(PhrictionContent $content) { $this->newContent = $content; return $this; } public function getNewContent() { return $this->newContent; } public function setSkipAncestorCheck($bool) { $this->skipAncestorCheck = $bool; return $this; } public function getSkipAncestorCheck() { return $this->skipAncestorCheck; } public function setContentVersion($version) { $this->contentVersion = $version; return $this; } public function getContentVersion() { return $this->contentVersion; } public function setProcessContentVersionError($process) { $this->processContentVersionError = $process; return $this; } public function getProcessContentVersionError() { return $this->processContentVersionError; } public function setMoveAwayDocument(PhrictionDocument $document) { $this->moveAwayDocument = $document; return $this; } public function getEditorApplicationClass() { return 'PhabricatorPhrictionApplication'; } public function getEditorObjectsDescription() { return pht('Phriction Documents'); } public function getTransactionTypes() { $types = parent::getTransactionTypes(); $types[] = PhabricatorTransactions::TYPE_EDGE; $types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; return $types; } protected function shouldApplyInitialEffects( PhabricatorLiskDAO $object, array $xactions) { foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: case PhrictionDocumentContentTransaction::TRANSACTIONTYPE: case PhrictionDocumentDeleteTransaction::TRANSACTIONTYPE: case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: case PhrictionDocumentMoveAwayTransaction::TRANSACTIONTYPE: return true; } } return parent::shouldApplyInitialEffects($object, $xactions); } protected function applyInitialEffects( PhabricatorLiskDAO $object, array $xactions) { $this->setOldContent($object->getContent()); $this->setNewContent($this->buildNewContentTemplate($object)); } protected function expandTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { $xactions = parent::expandTransaction($object, $xaction); switch ($xaction->getTransactionType()) { case PhrictionDocumentContentTransaction::TRANSACTIONTYPE: if ($this->getIsNewObject()) { break; } $content = $xaction->getNewValue(); if ($content === '') { $xactions[] = id(new PhrictionTransaction()) ->setTransactionType( PhrictionDocumentDeleteTransaction::TRANSACTIONTYPE) ->setNewValue(true) ->setMetadataValue('contentDelete', true); } break; case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: $document = $xaction->getNewValue(); $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) ->setNewValue($document->getViewPolicy()); $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) ->setNewValue($document->getEditPolicy()); break; default: break; } return $xactions; } protected function applyFinalEffects( PhabricatorLiskDAO $object, array $xactions) { $save_content = false; foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: case PhrictionDocumentMoveAwayTransaction::TRANSACTIONTYPE: case PhrictionDocumentDeleteTransaction::TRANSACTIONTYPE: case PhrictionDocumentContentTransaction::TRANSACTIONTYPE: $save_content = true; break; default: break; } } if ($save_content) { $content = $this->getNewContent(); $content->setDocumentID($object->getID()); $content->save(); $object->setContentID($content->getID()); $object->save(); $object->attachContent($content); } if ($this->getIsNewObject() && !$this->getSkipAncestorCheck()) { // Stub out empty parent documents if they don't exist $ancestral_slugs = PhabricatorSlug::getAncestry($object->getSlug()); if ($ancestral_slugs) { $ancestors = id(new PhrictionDocumentQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withSlugs($ancestral_slugs) ->needContent(true) ->execute(); $ancestors = mpull($ancestors, null, 'getSlug'); $stub_type = PhrictionChangeType::CHANGE_STUB; foreach ($ancestral_slugs as $slug) { $ancestor_doc = idx($ancestors, $slug); // We check for change type to prevent near-infinite recursion if (!$ancestor_doc && $content->getChangeType() != $stub_type) { $ancestor_doc = PhrictionDocument::initializeNewDocument( $this->getActor(), $slug); $stub_xactions = array(); $stub_xactions[] = id(new PhrictionTransaction()) ->setTransactionType( PhrictionDocumentTitleTransaction::TRANSACTIONTYPE) ->setNewValue(PhabricatorSlug::getDefaultTitle($slug)) ->setMetadataValue('stub:create:phid', $object->getPHID()); $stub_xactions[] = id(new PhrictionTransaction()) ->setTransactionType( PhrictionDocumentContentTransaction::TRANSACTIONTYPE) ->setNewValue('') ->setMetadataValue('stub:create:phid', $object->getPHID()); $stub_xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) ->setNewValue($object->getViewPolicy()); $stub_xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) ->setNewValue($object->getEditPolicy()); $sub_editor = id(new PhrictionTransactionEditor()) ->setActor($this->getActor()) ->setContentSource($this->getContentSource()) ->setContinueOnNoEffect($this->getContinueOnNoEffect()) ->setSkipAncestorCheck(true) ->setDescription(pht('Empty Parent Document')) ->applyTransactions($ancestor_doc, $stub_xactions); } } } } if ($this->moveAwayDocument !== null) { $move_away_xactions = array(); $move_away_xactions[] = id(new PhrictionTransaction()) ->setTransactionType( PhrictionDocumentMoveAwayTransaction::TRANSACTIONTYPE) ->setNewValue($object); $sub_editor = id(new PhrictionTransactionEditor()) ->setActor($this->getActor()) ->setContentSource($this->getContentSource()) ->setContinueOnNoEffect($this->getContinueOnNoEffect()) ->setDescription($this->getDescription()) ->applyTransactions($this->moveAwayDocument, $move_away_xactions); } // Compute the content diff URI for the publishing phase. foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { case PhrictionDocumentContentTransaction::TRANSACTIONTYPE: $uri = id(new PhutilURI('/phriction/diff/'.$object->getID().'/')) ->alter('l', $this->getOldContent()->getVersion()) ->alter('r', $this->getNewContent()->getVersion()); $this->contentDiffURI = (string)$uri; break 2; default: break; } } return $xactions; } protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { return true; } protected function getMailSubjectPrefix() { return '[Phriction]'; } protected function getMailTo(PhabricatorLiskDAO $object) { return array( $object->getContent()->getAuthorPHID(), $this->getActingAsPHID(), ); } public function getMailTagsMap() { return array( PhrictionTransaction::MAILTAG_TITLE => pht("A document's title changes."), PhrictionTransaction::MAILTAG_CONTENT => pht("A document's content changes."), PhrictionTransaction::MAILTAG_DELETE => pht('A document is deleted.'), PhrictionTransaction::MAILTAG_SUBSCRIBERS => pht('A document\'s subscribers change.'), PhrictionTransaction::MAILTAG_OTHER => pht('Other document activity not listed above occurs.'), ); } protected function buildReplyHandler(PhabricatorLiskDAO $object) { return id(new PhrictionReplyHandler()) ->setMailReceiver($object); } protected function buildMailTemplate(PhabricatorLiskDAO $object) { $title = $object->getContent()->getTitle(); return id(new PhabricatorMetaMTAMail()) ->setSubject($title); } protected function buildMailBody( PhabricatorLiskDAO $object, array $xactions) { $body = parent::buildMailBody($object, $xactions); if ($this->getIsNewObject()) { $body->addRemarkupSection( pht('DOCUMENT CONTENT'), $object->getContent()->getContent()); } else if ($this->contentDiffURI) { $body->addLinkSection( pht('DOCUMENT DIFF'), PhabricatorEnv::getProductionURI($this->contentDiffURI)); } $description = $object->getContent()->getDescription(); if (strlen($description)) { $body->addTextSection( pht('EDIT NOTES'), $description); } $body->addLinkSection( pht('DOCUMENT DETAIL'), PhabricatorEnv::getProductionURI( PhrictionDocument::getSlugURI($object->getSlug()))); return $body; } protected function shouldPublishFeedStory( PhabricatorLiskDAO $object, array $xactions) { return $this->shouldSendMail($object, $xactions); } protected function getFeedRelatedPHIDs( PhabricatorLiskDAO $object, array $xactions) { $phids = parent::getFeedRelatedPHIDs($object, $xactions); foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: $dict = $xaction->getNewValue(); $phids[] = $dict['phid']; break; } } return $phids; } protected function validateTransaction( PhabricatorLiskDAO $object, $type, array $xactions) { $errors = parent::validateTransaction($object, $type, $xactions); foreach ($xactions as $xaction) { switch ($type) { case PhrictionDocumentContentTransaction::TRANSACTIONTYPE: if ($xaction->getMetadataValue('stub:create:phid')) { continue; } if ($this->getProcessContentVersionError()) { $error = $this->validateContentVersion($object, $type, $xaction); if ($error) { $this->setProcessContentVersionError(false); $errors[] = $error; } } if ($this->getIsNewObject()) { $ancestry_errors = $this->validateAncestry( $object, $type, $xaction, self::VALIDATE_CREATE_ANCESTRY); if ($ancestry_errors) { $errors = array_merge($errors, $ancestry_errors); } } break; case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: $source_document = $xaction->getNewValue(); $ancestry_errors = $this->validateAncestry( $object, $type, $xaction, self::VALIDATE_MOVE_ANCESTRY); if ($ancestry_errors) { $errors = array_merge($errors, $ancestry_errors); } $target_document = id(new PhrictionDocumentQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withSlugs(array($object->getSlug())) ->needContent(true) ->executeOne(); // Prevent overwrites and no-op moves. $exists = PhrictionDocumentStatus::STATUS_EXISTS; if ($target_document) { $message = null; if ($target_document->getSlug() == $source_document->getSlug()) { $message = pht( 'You can not move a document to its existing location. '. 'Choose a different location to move the document to.'); } else if ($target_document->getStatus() == $exists) { $message = pht( 'You can not move this document there, because it would '. 'overwrite an existing document which is already at that '. 'location. Move or delete the existing document first.'); } if ($message !== null) { $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), $message, $xaction); $errors[] = $error; } } break; } } return $errors; } public function validateAncestry( PhabricatorLiskDAO $object, $type, PhabricatorApplicationTransaction $xaction, $verb) { $errors = array(); // NOTE: We use the omnipotent user for these checks because policy // doesn't matter; existence does. $other_doc_viewer = PhabricatorUser::getOmnipotentUser(); $ancestral_slugs = PhabricatorSlug::getAncestry($object->getSlug()); if ($ancestral_slugs) { $ancestors = id(new PhrictionDocumentQuery()) ->setViewer($other_doc_viewer) ->withSlugs($ancestral_slugs) ->execute(); $ancestors = mpull($ancestors, null, 'getSlug'); foreach ($ancestral_slugs as $slug) { $ancestor_doc = idx($ancestors, $slug); if (!$ancestor_doc) { $create_uri = '/phriction/edit/?slug='.$slug; $create_link = phutil_tag( 'a', array( 'href' => $create_uri, ), $slug); switch ($verb) { case self::VALIDATE_MOVE_ANCESTRY: $message = pht( 'Can not move document because the parent document with '. 'slug %s does not exist!', $create_link); break; case self::VALIDATE_CREATE_ANCESTRY: $message = pht( 'Can not create document because the parent document with '. 'slug %s does not exist!', $create_link); break; } $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Missing Ancestor'), $message, $xaction); $errors[] = $error; } } } return $errors; } private function validateContentVersion( PhabricatorLiskDAO $object, $type, PhabricatorApplicationTransaction $xaction) { $error = null; if ($this->getContentVersion() && ($object->getContent()->getVersion() != $this->getContentVersion())) { $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Edit Conflict'), pht( 'Another user made changes to this document after you began '. 'editing it. Do you want to overwrite their changes? '. '(If you choose to overwrite their changes, you should review '. 'the document edit history to see what you overwrote, and '. 'then make another edit to merge the changes if necessary.)'), $xaction); } return $error; } protected function requireCapabilities( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { /* * New objects have a special case. If a user can't see * x/y * then definitely don't let them make some * x/y/z * We need to load the direct parent to handle this case. */ if ($this->getIsNewObject()) { $actor = $this->requireActor(); $parent_doc = null; $ancestral_slugs = PhabricatorSlug::getAncestry($object->getSlug()); // No ancestral slugs is "/"; the first person gets to play with "/". if ($ancestral_slugs) { $parent = end($ancestral_slugs); $parent_doc = id(new PhrictionDocumentQuery()) ->setViewer($actor) ->withSlugs(array($parent)) ->executeOne(); // If the $actor can't see the $parent_doc then they can't create // the child $object; throw a policy exception. if (!$parent_doc) { id(new PhabricatorPolicyFilter()) ->setViewer($actor) ->raisePolicyExceptions(true) ->rejectObject( $object, $object->getEditPolicy(), PhabricatorPolicyCapability::CAN_EDIT); } // If the $actor can't edit the $parent_doc then they can't create // the child $object; throw a policy exception. if (!PhabricatorPolicyFilter::hasCapability( $actor, $parent_doc, PhabricatorPolicyCapability::CAN_EDIT)) { id(new PhabricatorPolicyFilter()) ->setViewer($actor) ->raisePolicyExceptions(true) ->rejectObject( $object, $object->getEditPolicy(), PhabricatorPolicyCapability::CAN_EDIT); } } } return parent::requireCapabilities($object, $xaction); } protected function supportsSearch() { return true; } protected function shouldApplyHeraldRules( PhabricatorLiskDAO $object, array $xactions) { return true; } protected function buildHeraldAdapter( PhabricatorLiskDAO $object, array $xactions) { return id(new PhrictionDocumentHeraldAdapter()) ->setDocument($object); } private function buildNewContentTemplate( PhrictionDocument $document) { $new_content = id(new PhrictionContent()) ->setSlug($document->getSlug()) ->setAuthorPHID($this->getActor()->getPHID()) ->setChangeType(PhrictionChangeType::CHANGE_EDIT) ->setTitle($this->getOldContent()->getTitle()) - ->setContent($this->getOldContent()->getContent()); + ->setContent($this->getOldContent()->getContent()) + ->setDescription(''); + if (strlen($this->getDescription())) { $new_content->setDescription($this->getDescription()); } + $new_content->setVersion($this->getOldContent()->getVersion() + 1); return $new_content; } protected function getCustomWorkerState() { return array( 'contentDiffURI' => $this->contentDiffURI, ); } protected function loadCustomWorkerState(array $state) { $this->contentDiffURI = idx($state, 'contentDiffURI'); return $this; } } diff --git a/src/applications/phriction/storage/PhrictionContent.php b/src/applications/phriction/storage/PhrictionContent.php index a6aee7226f..390da64682 100644 --- a/src/applications/phriction/storage/PhrictionContent.php +++ b/src/applications/phriction/storage/PhrictionContent.php @@ -1,109 +1,106 @@ <?php final class PhrictionContent extends PhrictionDAO implements PhabricatorPolicyInterface, PhabricatorDestructibleInterface { protected $documentID; protected $version; protected $authorPHID; protected $title; protected $slug; protected $content; protected $description; protected $changeType; protected $changeRef; private $document = self::ATTACHABLE; protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_COLUMN_SCHEMA => array( 'version' => 'uint32', 'title' => 'sort', 'slug' => 'text128', 'content' => 'text', 'changeType' => 'uint32', 'changeRef' => 'uint32?', - - // T6203/NULLABILITY - // This should just be empty if not provided? - 'description' => 'text?', + 'description' => 'text', ), self::CONFIG_KEY_SCHEMA => array( 'documentID' => array( 'columns' => array('documentID', 'version'), 'unique' => true, ), 'authorPHID' => array( 'columns' => array('authorPHID'), ), 'slug' => array( 'columns' => array('slug'), ), ), ) + parent::getConfiguration(); } public function getPHIDType() { return PhrictionContentPHIDType::TYPECONST; } public function newRemarkupView(PhabricatorUser $viewer) { return id(new PHUIRemarkupView($viewer, $this->getContent())) ->setRemarkupOption(PHUIRemarkupView::OPTION_GENERATE_TOC, true) ->setGenerateTableOfContents(true); } public function attachDocument(PhrictionDocument $document) { $this->document = $document; return $this; } public function getDocument() { return $this->assertAttached($this->document); } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, ); } public function getPolicy($capability) { return PhabricatorPolicies::getMostOpenPolicy(); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return false; } /* -( PhabricatorExtendedPolicyInterface )--------------------------------- */ public function getExtendedPolicy($capability, PhabricatorUser $viewer) { return array( array($this->getDocument(), PhabricatorPolicyCapability::CAN_VIEW), ); } /* -( PhabricatorDestructibleInterface )----------------------------------- */ public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { $this->delete(); } }