diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 214e567296..866a0cc660 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1497,6 +1497,7 @@ phutil_register_library_map(array( 'PhrictionEditController' => 'applications/phriction/controller/PhrictionEditController.php', 'PhrictionHistoryController' => 'applications/phriction/controller/PhrictionHistoryController.php', 'PhrictionListController' => 'applications/phriction/controller/PhrictionListController.php', + 'PhrictionMoveController' => 'applications/phriction/controller/PhrictionMoveController.php', 'PhrictionNewController' => 'applications/phriction/controller/PhrictionNewController.php', 'PhrictionRemarkupRule' => 'applications/phriction/remarkup/PhrictionRemarkupRule.php', 'PhrictionSearchIndexer' => 'applications/phriction/search/PhrictionSearchIndexer.php', @@ -3003,6 +3004,7 @@ phutil_register_library_map(array( 'PhrictionEditController' => 'PhrictionController', 'PhrictionHistoryController' => 'PhrictionController', 'PhrictionListController' => 'PhrictionController', + 'PhrictionMoveController' => 'PhrictionController', 'PhrictionNewController' => 'PhrictionController', 'PhrictionRemarkupRule' => 'PhutilRemarkupRule', 'PhrictionSearchIndexer' => 'PhabricatorSearchDocumentIndexer', diff --git a/src/applications/phriction/application/PhabricatorApplicationPhriction.php b/src/applications/phriction/application/PhabricatorApplicationPhriction.php index 5f6cf95e1b..bf0423b237 100644 --- a/src/applications/phriction/application/PhabricatorApplicationPhriction.php +++ b/src/applications/phriction/application/PhabricatorApplicationPhriction.php @@ -43,7 +43,7 @@ final class PhabricatorApplicationPhriction extends PhabricatorApplication { 'edit/(?:(?P[1-9]\d*)/)?' => 'PhrictionEditController', 'delete/(?P[1-9]\d*)/' => 'PhrictionDeleteController', 'new/' => 'PhrictionNewController', - 'move/(?P[1-9]\d*)/' => 'PhrictionMoveController', + 'move/(?:(?P[1-9]\d*)/)?' => 'PhrictionMoveController', 'preview/' => 'PhrictionDocumentPreviewController', 'diff/(?P[1-9]\d*)/' => 'PhrictionDiffController', diff --git a/src/applications/phriction/constants/PhrictionActionConstants.php b/src/applications/phriction/constants/PhrictionActionConstants.php index d8681224a7..298214adb3 100644 --- a/src/applications/phriction/constants/PhrictionActionConstants.php +++ b/src/applications/phriction/constants/PhrictionActionConstants.php @@ -8,12 +8,16 @@ final class PhrictionActionConstants extends PhrictionConstants { const ACTION_CREATE = 'create'; const ACTION_EDIT = 'edit'; const ACTION_DELETE = 'delete'; + const ACTION_MOVE_AWAY = 'move to'; + const ACTION_MOVE_HERE = 'move here'; public static function getActionPastTenseVerb($action) { static $map = array( self::ACTION_CREATE => 'created', self::ACTION_EDIT => 'edited', self::ACTION_DELETE => 'deleted', + self::ACTION_MOVE_AWAY => 'moved a document to', + self::ACTION_MOVE_HERE => 'moved a document from', ); return idx($map, $action, "brazenly {$action}'d"); diff --git a/src/applications/phriction/constants/PhrictionDocumentStatus.php b/src/applications/phriction/constants/PhrictionDocumentStatus.php index 88fad77d5b..c92f237429 100644 --- a/src/applications/phriction/constants/PhrictionDocumentStatus.php +++ b/src/applications/phriction/constants/PhrictionDocumentStatus.php @@ -12,8 +12,9 @@ final class PhrictionDocumentStatus extends PhrictionConstants { public static function getConduitConstant($const) { static $map = array( - self::STATUS_EXISTS => 'exists', + self::STATUS_EXISTS => 'exists', self::STATUS_DELETED => 'deleted', + self::STATUS_MOVED => 'moved', self::STATUS_STUB => 'stubbed', ); diff --git a/src/applications/phriction/controller/PhrictionDocumentController.php b/src/applications/phriction/controller/PhrictionDocumentController.php index ec0666d268..c28e736d4f 100644 --- a/src/applications/phriction/controller/PhrictionDocumentController.php +++ b/src/applications/phriction/controller/PhrictionDocumentController.php @@ -13,7 +13,6 @@ final class PhrictionDocumentController } public function processRequest() { - $request = $this->getRequest(); $user = $request->getUser(); @@ -148,14 +147,43 @@ final class PhrictionDocumentController pht('This document is empty. You can edit it to put some proper '. 'content here.')); $core_content = $notice->render(); + } else if ($doc_status == PhrictionDocumentStatus::STATUS_MOVED) { + $new_doc_id = $content->getChangeRef(); + $new_doc = new PhrictionDocument(); + $new_doc->load($new_doc_id); + + $slug_uri = PhrictionDocument::getSlugURI($new_doc->getSlug()); + + $notice = new AphrontErrorView(); + $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE); + $notice->setTitle(pht('Document Moved')); + $notice->appendChild(phutil_tag('p', array(), + pht('This document has been moved to %s. You can edit it to put new '. + 'content here, or use history to revert to an earlier version.', + phutil_tag('a', array('href' => $slug_uri), $slug_uri)))); + $core_content = $notice->render(); } else { throw new Exception("Unknown document status '{$doc_status}'!"); } + $move_notice = null; + if ($content->getChangeType() == PhrictionChangeType::CHANGE_MOVE_HERE) { + $from_doc_id = $content->getChangeRef(); + $from_doc = id(new PhrictionDocument())->load($from_doc_id); + $slug_uri = PhrictionDocument::getSlugURI($from_doc->getSlug()); + + $move_notice = id(new AphrontErrorView()) + ->setSeverity(AphrontErrorView::SEVERITY_NOTICE) + ->appendChild(pht('This document was moved from %s', + phutil_tag('a', array('href' => $slug_uri), $slug_uri))) + ->render(); + } + $page_content = hsprintf( - '
%s%s%s
', + '
%s%s%s%s
', $index_link, $byline, + $move_notice, $core_content); } @@ -221,6 +249,13 @@ final class PhrictionDocumentController ->setHref('/phriction/edit/'.$document->getID().'/')); if ($document->getStatus() == PhrictionDocumentStatus::STATUS_EXISTS) { + $action_view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Move Document')) + ->setIcon('move') + ->setHref('/phriction/move/'.$document->getID().'/') + ->setWorkflow(true)); + $action_view->addAction( id(new PhabricatorActionView()) ->setName(pht('Delete Document')) diff --git a/src/applications/phriction/controller/PhrictionListController.php b/src/applications/phriction/controller/PhrictionListController.php index fdc6b49822..8f4c0e7638 100644 --- a/src/applications/phriction/controller/PhrictionListController.php +++ b/src/applications/phriction/controller/PhrictionListController.php @@ -115,9 +115,13 @@ final class PhrictionListController case 'active': $data = queryfx_all( $conn, - 'SELECT * FROM %T WHERE status != %d ORDER BY id DESC LIMIT %d, %d', + 'SELECT * FROM %T WHERE status NOT IN (%Ld) ORDER BY id DESC '. + 'LIMIT %d, %d', $document_dao->getTableName(), - PhrictionDocumentStatus::STATUS_DELETED, + array( + PhrictionDocumentStatus::STATUS_DELETED, + PhrictionDocumentStatus::STATUS_MOVED, + ), $pager->getOffset(), $pager->getPageSize() + 1); break; diff --git a/src/applications/phriction/controller/PhrictionMoveController.php b/src/applications/phriction/controller/PhrictionMoveController.php new file mode 100644 index 0000000000..fb8019adb3 --- /dev/null +++ b/src/applications/phriction/controller/PhrictionMoveController.php @@ -0,0 +1,188 @@ +id = idx($data, 'id'); + } + + public function processRequest() { + $request = $this->getRequest(); + $user = $request->getUser(); + $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); + + if ($this->id) { + $document = id(new PhrictionDocument())->load($this->id); + } else { + $slug = PhabricatorSlug::normalize( + $request->getStr('slug')); + if (!$slug) { + return new Aphront404Response(); + } + + $document = id(new PhrictionDocument())->loadOneWhere( + 'slug = %s', + $slug); + } + + if (!$document) { + return new Aphront404Response(); + } + + if (!isset($slug)) { + $slug = $document->getSlug(); + } + + $target_slug = PhabricatorSlug::normalize( + $request->getStr('new-slug', $slug)); + + $submit_uri = $request->getRequestURI()->getPath(); + $cancel_uri = PhrictionDocument::getSlugURI($slug); + + $errors = array(); + $error_view = null; + $e_url = null; + $e_block = false; + + $disallowed_statuses = array( + PhrictionDocumentStatus::STATUS_DELETED, // Stupid + PhrictionDocumentStatus::STATUS_MOVED, // Plain stupid + ); + if (in_array($document->getStatus(), $disallowed_statuses)) { + $error_view = new AphrontErrorView(); + $error_view->setSeverity(AphrontErrorView::SEVERITY_ERROR); + $error_view->appendChild(pht('An already moved or deleted document '. + 'can not be moved again.')); + + $error_dialog = new AphrontDialogView(); + $error_dialog->setUser($user); + $error_dialog->setTitle(""); + $error_dialog->appendChild($error_view); + $error_dialog->addCancelButton($cancel_uri, pht('I understand')); + + return id(new AphrontDialogResponse())->setDialog($error_dialog); + } + + $content = id(new PhrictionContent())->load($document->getContentID()); + + if ($request->isFormPost() && !count($errors)) { + if (!count($errors)) { // First check if the target document exists + $target_document = id(new PhrictionDocument())->loadOneWhere( + 'slug = %s', + $target_slug); + + // Considering to overwrite existing docs? Nuke this! + if ($target_document && $target_document->getStatus() == + PhrictionDocumentStatus::STATUS_EXISTS) { + + $errors[] = pht('Can not overwrite existing target document.'); + $e_url = pht('Already exists.'); + } + } + + if (!count($errors)) { // I like to move it, move it! + $from_editor = id(PhrictionDocumentEditor::newForSlug($slug)) + ->setActor($user) + ->setTitle($content->getTitle()) + ->setContent($content->getContent()) + ->setDescription($content->getDescription()); + + $target_editor = id(PhrictionDocumentEditor::newForSlug( + $target_slug)) + ->setActor($user) + ->setTitle($content->getTitle()) + ->setContent($content->getContent()) + ->setDescription($content->getDescription()); + + // Move it! + $target_editor->moveHere($document->getID()); + + // Retrieve the target doc directly from the editor + // No need to load it per Sql again + $target_document = $target_editor->getDocument(); + $from_editor->moveAway($target_document->getID()); + + $redir_uri = PhrictionDocument::getSlugURI($target_document->getSlug()); + return id(new AphrontRedirectResponse())->setURI($redir_uri); + } + } + + if ($errors) { + $error_view = id(new AphrontErrorView()) + ->setTitle(pht('Form Errors')) + ->setErrors($errors); + } + + $descr_caption = $is_serious ? pht('A reason for the move.') : + pht('You better give a good reason for this.'); + + if ($request->isAjax()) { + $form = new AphrontFormLayoutView(); + } else { + $form = new AphrontFormView(); + $form->setAction($submit_uri) + // Append title so the user can verify that he's touching + // the right document + ->appendChild( + id(new AphrontFormStaticControl()) + ->setLabel(pht('Title')) + ->setValue($content->getTitle())); + } + + $form + ->setUser($user) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel(pht('New URI')) + ->setValue($target_slug) + ->setError($e_url) + ->setName('new-slug') + ->setCaption(pht('The new location of the document.'))) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel(pht('Edit Notes')) + ->setValue($content->getDescription()) + ->setError(null) + ->setName('description') + ->setCaption($descr_caption)); + + if ($request->isAjax()) { + $dialog = new AphrontDialogView(); + $dialog->setUser($user); + $dialog->setTitle(pht('Move Document')); + $dialog->appendChild($form); + $dialog->setSubmitURI($submit_uri); + $dialog->addSubmitButton(pht('Move Document')); + $dialog->addCancelButton($cancel_uri); + + return id(new AphrontDialogResponse())->setDialog($dialog); + } else { + $form->appendChild( + id(new AphrontFormSubmitControl()) + ->addCancelButton($cancel_uri) + ->setValue(pht('Move Document')) + ->setDisabled($e_block)); + + $panel = id(new AphrontPanelView()) + ->setNoBackground() + ->setHeader(pht('Move Phriction Document')) + ->appendChild($form); + + return $this->buildApplicationPage( + array( + $error_view, + $panel, + ), + array( + 'title' => pht('Move Document'), + 'device' => true, + )); + } + } +} diff --git a/src/applications/phriction/editor/PhrictionDocumentEditor.php b/src/applications/phriction/editor/PhrictionDocumentEditor.php index c103b26ac2..89d065c616 100644 --- a/src/applications/phriction/editor/PhrictionDocumentEditor.php +++ b/src/applications/phriction/editor/PhrictionDocumentEditor.php @@ -66,33 +66,44 @@ final class PhrictionDocumentEditor extends PhabricatorEditor { return $this->document; } - public function delete() { - $actor = $this->requireActor(); + public function moveAway($new_doc_id) { + return $this->execute( + PhrictionChangeType::CHANGE_MOVE_AWAY, true, $new_doc_id); + } - // TODO: Should we do anything about deleting an already-deleted document? - // We currently allow it. + public function moveHere($old_doc_id) { + return $this->execute( + PhrictionChangeType::CHANGE_MOVE_HERE, false, $old_doc_id); + } + + private function execute( + $change_type, $del_new_content = true, $doc_ref = null) { + + $actor = $this->requireActor(); $document = $this->document; $content = $this->content; $new_content = $this->buildContentTemplate($document, $content); + $new_content->setChangeType($change_type); - $new_content->setChangeType(PhrictionChangeType::CHANGE_DELETE); - $new_content->setContent(''); + if ($del_new_content) { + $new_content->setContent(''); + } + + if ($doc_ref) { + $new_content->setChangeRef($doc_ref); + } return $this->updateDocument($document, $content, $new_content); } + public function delete() { + return $this->execute(PhrictionChangeType::CHANGE_DELETE, true); + } + private function stub() { - $actor = $this->requireActor(); - $document = $this->document; - $content = $this->content; - $new_content = $this->buildContentTemplate($document, $content); - - $new_content->setChangeType(PhrictionChangeType::CHANGE_STUB); - $new_content->setContent(''); - - return $this->updateDocument($document, $content, $new_content); + return $this->execute(PhrictionChangeType::CHANGE_STUB, true); } public function save() { @@ -168,6 +179,14 @@ final class PhrictionDocumentEditor extends PhabricatorEditor { $doc_status = PhrictionDocumentStatus::STATUS_STUB; $feed_action = null; break; + case PhrictionChangeType::CHANGE_MOVE_AWAY: + $doc_status = PhrictionDocumentStatus::STATUS_MOVED; + $feed_action = PhrictionActionConstants::ACTION_MOVE_AWAY; + break; + case PhrictionChangeType::CHANGE_MOVE_HERE: + $doc_status = PhrictionDocumentStatus::STATUS_EXISTS; + $feed_action = null; + break; default: throw new Exception( "Unsupported content change type '{$change_type}'!");