diff --git a/src/applications/phriction/controller/PhrictionEditController.php b/src/applications/phriction/controller/PhrictionEditController.php --- a/src/applications/phriction/controller/PhrictionEditController.php +++ b/src/applications/phriction/controller/PhrictionEditController.php @@ -93,96 +93,63 @@ $draft_key); } + if ($draft && + strlen($draft->getDraft()) && + ($draft->getDraft() != $content->getContent())) { + $content_text = $draft->getDraft(); + + $discard = phutil_tag( + 'a', + array( + 'href' => $request->getRequestURI()->alter('nodraft', true), + ), + pht('discard this draft')); + + $draft_note = new AphrontErrorView(); + $draft_note->setSeverity(AphrontErrorView::SEVERITY_NOTICE); + $draft_note->setTitle('Recovered Draft'); + $draft_note->appendChild(hsprintf( + '

Showing a saved draft of your edits, you can %s.

', + $discard)); + } else { + $content_text = $content->getContent(); + $draft_note = null; + } + require_celerity_resource('phriction-document-css'); $e_title = true; + $e_content = true; + $validation_exception = null; $notes = null; - $errors = array(); + $title = $content->getTitle(); + $overwrite = false; if ($request->isFormPost()) { - $overwrite = $request->getBool('overwrite'); - if (!$overwrite) { - $edit_version = $request->getStr('contentVersion'); - if ($edit_version != $current_version) { - $dialog = $this->newDialog() - ->setTitle(pht('Edit Conflict!')) - ->appendParagraph( - pht( - 'Another user made changes to this document after you began '. - 'editing it. Do you want to overwrite their changes?')) - ->appendParagraph( - pht( - '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.')) - ->addSubmitButton(pht('Overwrite Changes')) - ->addCancelButton($request->getRequestURI()); - - $dialog->addHiddenInput('overwrite', 'true'); - foreach ($request->getPassthroughRequestData() as $key => $value) { - $dialog->addHiddenInput($key, $value); - } - - return $dialog; - } - } - - $title = $request->getStr('title'); + $content_text = $request->getStr('content'); $notes = $request->getStr('description'); - - if (!strlen($title)) { - $e_title = pht('Required'); - $errors[] = pht('Document title is required.'); - } else { - $e_title = null; - } - - if ($document->getID()) { - if ($content->getTitle() == $title && - $content->getContent() == $request->getStr('content')) { - - $dialog = new AphrontDialogView(); - $dialog->setUser($user); - $dialog->setTitle(pht('No Edits')); - $dialog->appendChild(phutil_tag('p', array(), pht( - 'You did not make any changes to the document.'))); - $dialog->addCancelButton($request->getRequestURI()); - - return id(new AphrontDialogResponse())->setDialog($dialog); - } - } else if (!strlen($request->getStr('content'))) { - - // We trigger this only for new pages. For existing pages, deleting - // all the content counts as deleting the page. - - $dialog = new AphrontDialogView(); - $dialog->setUser($user); - $dialog->setTitle(pht('Empty Page')); - $dialog->appendChild(phutil_tag('p', array(), pht( - 'You can not create an empty document.'))); - $dialog->addCancelButton($request->getRequestURI()); - - return id(new AphrontDialogResponse())->setDialog($dialog); - } - - if (!count($errors)) { - - $xactions = array(); - $xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_TITLE) - ->setNewValue($title); - $xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_CONTENT) - ->setNewValue($request->getStr('content')); - - $editor = id(new PhrictionTransactionEditor()) - ->setActor($user) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true) - ->setDescription($notes) - ->applyTransactions($document, $xactions); + $current_version = $request->getInt('contentVersion'); + + $xactions = array(); + $xactions[] = id(new PhrictionTransaction()) + ->setTransactionType(PhrictionTransaction::TYPE_TITLE) + ->setNewValue($title); + $xactions[] = id(new PhrictionTransaction()) + ->setTransactionType(PhrictionTransaction::TYPE_CONTENT) + ->setNewValue($content_text); + + $editor = id(new PhrictionTransactionEditor()) + ->setActor($user) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setDescription($notes) + ->setProcessContentVersionError(!$request->getBool('overwrite')) + ->setContentVersion($current_version); + + try { + $editor->applyTransactions($document, $xactions); if ($draft) { $draft->delete(); @@ -190,15 +157,35 @@ $uri = PhrictionDocument::getSlugURI($document->getSlug()); return id(new AphrontRedirectResponse())->setURI($uri); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $validation_exception = $ex; + $e_title = $ex->getShortMessage( + PhrictionTransaction::TYPE_TITLE); + $e_content = $ex->getShortMessage( + PhrictionTransaction::TYPE_CONTENT); + + // if we're not supposed to process the content version error, then + // overwrite that content...! + if (!$editor->getProcessContentVersionError()) { + $overwrite = true; + } + + // TODO - remember to set policy to what the user tried to set it to } } if ($document->getID()) { $panel_header = pht('Edit Phriction Document'); - $submit_button = pht('Save Changes'); + $page_title = pht('Edit Document'); + if ($overwrite) { + $submit_button = pht('Overwrite Changes'); + } else { + $submit_button = pht('Save Changes'); + } } else { $panel_header = pht('Create New Phriction Document'); $submit_button = pht('Create Document'); + $page_title = pht('Create Document'); } $uri = $document->getSlug(); @@ -207,40 +194,16 @@ $cancel_uri = PhrictionDocument::getSlugURI($document->getSlug()); - if ($draft && - strlen($draft->getDraft()) && - ($draft->getDraft() != $content->getContent())) { - $content_text = $draft->getDraft(); - - $discard = phutil_tag( - 'a', - array( - 'href' => $request->getRequestURI()->alter('nodraft', true), - ), - pht('discard this draft')); - - $draft_note = new AphrontErrorView(); - $draft_note->setSeverity(AphrontErrorView::SEVERITY_NOTICE); - $draft_note->setTitle('Recovered Draft'); - $draft_note->appendChild(hsprintf( - '

Showing a saved draft of your edits, you can %s.

', - $discard)); - } else { - $content_text = $content->getContent(); - $draft_note = null; - } - $form = id(new AphrontFormView()) ->setUser($user) - ->setWorkflow(true) - ->setAction($request->getRequestURI()->getPath()) ->addHiddenInput('slug', $document->getSlug()) ->addHiddenInput('nodraft', $request->getBool('nodraft')) ->addHiddenInput('contentVersion', $current_version) + ->addHiddenInput('overwrite', $overwrite) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Title')) - ->setValue($content->getTitle()) + ->setValue($title) ->setError($e_title) ->setName('title')) ->appendChild( @@ -251,6 +214,7 @@ id(new PhabricatorRemarkupControl()) ->setLabel(pht('Content')) ->setValue($content_text) + ->setError($e_content) ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) ->setName('content') ->setID('document-textarea') @@ -267,8 +231,8 @@ ->setValue($submit_button)); $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Edit Document')) - ->setFormErrors($errors) + ->setHeaderText($panel_header) + ->setValidationException($validation_exception) ->setForm($form); $preview = id(new PHUIRemarkupPreviewPanel()) @@ -295,7 +259,7 @@ $preview, ), array( - 'title' => pht('Edit Document'), + 'title' => $page_title, )); } diff --git a/src/applications/phriction/editor/PhrictionTransactionEditor.php b/src/applications/phriction/editor/PhrictionTransactionEditor.php --- a/src/applications/phriction/editor/PhrictionTransactionEditor.php +++ b/src/applications/phriction/editor/PhrictionTransactionEditor.php @@ -8,6 +8,8 @@ private $newContent; private $moveAwayDocument; private $skipAncestorCheck; + private $contentVersion; + private $processContentVersionError = true; public function setDescription($description) { $this->description = $description; @@ -45,6 +47,24 @@ 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 getEditorApplicationClass() { return 'PhabricatorPhrictionApplication'; } @@ -162,6 +182,30 @@ } } + protected function expandTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + $xactions = parent::expandTransaction($object, $xaction); + switch ($xaction->getTransactionType()) { + case PhrictionTransaction::TYPE_CONTENT: + if ($this->getIsNewObject()) { + break; + } + $content = $xaction->getNewValue(); + if ($content === '') { + $xactions[] = id(new PhrictionTransaction()) + ->setTransactionType(PhrictionTransaction::TYPE_DELETE) + ->setNewValue(true); + } + break; + default: + break; + } + + return $xactions; + } + protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { @@ -251,7 +295,8 @@ ->setMetadataValue('stub:create:phid', $object->getPHID()); $stub_xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhrictionTransaction::TYPE_CONTENT) - ->setNewValue(''); + ->setNewValue('') + ->setMetadataValue('stub:create:phid', $object->getPHID()); $sub_editor = id(new PhrictionTransactionEditor()) ->setActor($this->getActor()) ->setContentSource($this->getContentSource()) @@ -366,6 +411,97 @@ return $phids; } + protected function validateTransaction( + PhabricatorLiskDAO $object, + $type, + array $xactions) { + + $errors = parent::validateTransaction($object, $type, $xactions); + + foreach ($xactions as $xaction) { + switch ($type) { + case PhrictionTransaction::TYPE_TITLE: + $title = $object->getContent()->getTitle(); + $missing = $this->validateIsEmptyTextField( + $title, + $xactions); + + if ($missing) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Required'), + pht('Document title is required.'), + nonempty(last($xactions), null)); + + $error->setIsMissingFieldError(true); + $errors[] = $error; + } else if ($this->getProcessContentVersionError()) { + $error = $this->validateContentVersion($object, $type, $xaction); + if ($error) { + $this->setProcessContentVersionError(false); + $errors[] = $error; + } + } + break; + + case PhrictionTransaction::TYPE_CONTENT: + if ($xaction->getMetadataValue('stub:create:phid')) { + continue; + } + + $missing = false; + if ($this->getIsNewObject()) { + $content = $object->getContent()->getContent(); + $missing = $this->validateIsEmptyTextField( + $content, + $xactions); + } + + if ($missing) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Required'), + pht('Document content is required.'), + nonempty(last($xactions), null)); + + $error->setIsMissingFieldError(true); + $errors[] = $error; + } else if ($this->getProcessContentVersionError()) { + $error = $this->validateContentVersion($object, $type, $xaction); + if ($error) { + $this->setProcessContentVersionError(false); + $errors[] = $error; + } + } + break; + } + } + + 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 supportsSearch() { return true; }