@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Phriction - consolidate edit business logic into Editor

Summary:
Ref T4029. Some business logic lives outside the editor. This revision moves that logic from the edit controller into the editor proper. This makes re-using that business logic across other endpoints - say like a conduit end point - possible. This is also part of the general modernization quest for phriction I am on.

This diff also restores the functionality where you can delete a document by wiping out the content and saving.

Test Plan: tried to make a document with no title or content and saw errors. opened a document for edit with user 1, then made edits with user 2, then saw an error when i made the edit with user 1. clicking "overwrite changes" then worked. deleted a document by wiping out the body and clicking save.

Reviewers: epriestley

Reviewed By: epriestley

Subscribers: Korvin, epriestley

Maniphest Tasks: T4029

Differential Revision: https://secure.phabricator.com/D10795

+209 -109
+72 -108
src/applications/phriction/controller/PhrictionEditController.php
··· 93 93 $draft_key); 94 94 } 95 95 96 + if ($draft && 97 + strlen($draft->getDraft()) && 98 + ($draft->getDraft() != $content->getContent())) { 99 + $content_text = $draft->getDraft(); 100 + 101 + $discard = phutil_tag( 102 + 'a', 103 + array( 104 + 'href' => $request->getRequestURI()->alter('nodraft', true), 105 + ), 106 + pht('discard this draft')); 107 + 108 + $draft_note = new AphrontErrorView(); 109 + $draft_note->setSeverity(AphrontErrorView::SEVERITY_NOTICE); 110 + $draft_note->setTitle('Recovered Draft'); 111 + $draft_note->appendChild(hsprintf( 112 + '<p>Showing a saved draft of your edits, you can %s.</p>', 113 + $discard)); 114 + } else { 115 + $content_text = $content->getContent(); 116 + $draft_note = null; 117 + } 118 + 96 119 require_celerity_resource('phriction-document-css'); 97 120 98 121 $e_title = true; 122 + $e_content = true; 123 + $validation_exception = null; 99 124 $notes = null; 100 - $errors = array(); 125 + $title = $content->getTitle(); 126 + $overwrite = false; 101 127 102 128 if ($request->isFormPost()) { 103 129 104 - $overwrite = $request->getBool('overwrite'); 105 - if (!$overwrite) { 106 - $edit_version = $request->getStr('contentVersion'); 107 - if ($edit_version != $current_version) { 108 - $dialog = $this->newDialog() 109 - ->setTitle(pht('Edit Conflict!')) 110 - ->appendParagraph( 111 - pht( 112 - 'Another user made changes to this document after you began '. 113 - 'editing it. Do you want to overwrite their changes?')) 114 - ->appendParagraph( 115 - pht( 116 - 'If you choose to overwrite their changes, you should review '. 117 - 'the document edit history to see what you overwrote, and '. 118 - 'then make another edit to merge the changes if necessary.')) 119 - ->addSubmitButton(pht('Overwrite Changes')) 120 - ->addCancelButton($request->getRequestURI()); 121 - 122 - $dialog->addHiddenInput('overwrite', 'true'); 123 - foreach ($request->getPassthroughRequestData() as $key => $value) { 124 - $dialog->addHiddenInput($key, $value); 125 - } 126 - 127 - return $dialog; 128 - } 129 - } 130 - 131 - 132 130 $title = $request->getStr('title'); 131 + $content_text = $request->getStr('content'); 133 132 $notes = $request->getStr('description'); 134 - 135 - if (!strlen($title)) { 136 - $e_title = pht('Required'); 137 - $errors[] = pht('Document title is required.'); 138 - } else { 139 - $e_title = null; 140 - } 141 - 142 - if ($document->getID()) { 143 - if ($content->getTitle() == $title && 144 - $content->getContent() == $request->getStr('content')) { 145 - 146 - $dialog = new AphrontDialogView(); 147 - $dialog->setUser($user); 148 - $dialog->setTitle(pht('No Edits')); 149 - $dialog->appendChild(phutil_tag('p', array(), pht( 150 - 'You did not make any changes to the document.'))); 151 - $dialog->addCancelButton($request->getRequestURI()); 152 - 153 - return id(new AphrontDialogResponse())->setDialog($dialog); 154 - } 155 - } else if (!strlen($request->getStr('content'))) { 156 - 157 - // We trigger this only for new pages. For existing pages, deleting 158 - // all the content counts as deleting the page. 159 - 160 - $dialog = new AphrontDialogView(); 161 - $dialog->setUser($user); 162 - $dialog->setTitle(pht('Empty Page')); 163 - $dialog->appendChild(phutil_tag('p', array(), pht( 164 - 'You can not create an empty document.'))); 165 - $dialog->addCancelButton($request->getRequestURI()); 166 - 167 - return id(new AphrontDialogResponse())->setDialog($dialog); 168 - } 133 + $current_version = $request->getInt('contentVersion'); 169 134 170 - if (!count($errors)) { 135 + $xactions = array(); 136 + $xactions[] = id(new PhrictionTransaction()) 137 + ->setTransactionType(PhrictionTransaction::TYPE_TITLE) 138 + ->setNewValue($title); 139 + $xactions[] = id(new PhrictionTransaction()) 140 + ->setTransactionType(PhrictionTransaction::TYPE_CONTENT) 141 + ->setNewValue($content_text); 171 142 172 - $xactions = array(); 173 - $xactions[] = id(new PhrictionTransaction()) 174 - ->setTransactionType(PhrictionTransaction::TYPE_TITLE) 175 - ->setNewValue($title); 176 - $xactions[] = id(new PhrictionTransaction()) 177 - ->setTransactionType(PhrictionTransaction::TYPE_CONTENT) 178 - ->setNewValue($request->getStr('content')); 143 + $editor = id(new PhrictionTransactionEditor()) 144 + ->setActor($user) 145 + ->setContentSourceFromRequest($request) 146 + ->setContinueOnNoEffect(true) 147 + ->setDescription($notes) 148 + ->setProcessContentVersionError(!$request->getBool('overwrite')) 149 + ->setContentVersion($current_version); 179 150 180 - $editor = id(new PhrictionTransactionEditor()) 181 - ->setActor($user) 182 - ->setContentSourceFromRequest($request) 183 - ->setContinueOnNoEffect(true) 184 - ->setDescription($notes) 185 - ->applyTransactions($document, $xactions); 151 + try { 152 + $editor->applyTransactions($document, $xactions); 186 153 187 154 if ($draft) { 188 155 $draft->delete(); ··· 190 157 191 158 $uri = PhrictionDocument::getSlugURI($document->getSlug()); 192 159 return id(new AphrontRedirectResponse())->setURI($uri); 160 + } catch (PhabricatorApplicationTransactionValidationException $ex) { 161 + $validation_exception = $ex; 162 + $e_title = $ex->getShortMessage( 163 + PhrictionTransaction::TYPE_TITLE); 164 + $e_content = $ex->getShortMessage( 165 + PhrictionTransaction::TYPE_CONTENT); 166 + 167 + // if we're not supposed to process the content version error, then 168 + // overwrite that content...! 169 + if (!$editor->getProcessContentVersionError()) { 170 + $overwrite = true; 171 + } 172 + 173 + // TODO - remember to set policy to what the user tried to set it to 193 174 } 194 175 } 195 176 196 177 if ($document->getID()) { 197 178 $panel_header = pht('Edit Phriction Document'); 198 - $submit_button = pht('Save Changes'); 179 + $page_title = pht('Edit Document'); 180 + if ($overwrite) { 181 + $submit_button = pht('Overwrite Changes'); 182 + } else { 183 + $submit_button = pht('Save Changes'); 184 + } 199 185 } else { 200 186 $panel_header = pht('Create New Phriction Document'); 201 187 $submit_button = pht('Create Document'); 188 + $page_title = pht('Create Document'); 202 189 } 203 190 204 191 $uri = $document->getSlug(); ··· 207 194 208 195 $cancel_uri = PhrictionDocument::getSlugURI($document->getSlug()); 209 196 210 - if ($draft && 211 - strlen($draft->getDraft()) && 212 - ($draft->getDraft() != $content->getContent())) { 213 - $content_text = $draft->getDraft(); 214 - 215 - $discard = phutil_tag( 216 - 'a', 217 - array( 218 - 'href' => $request->getRequestURI()->alter('nodraft', true), 219 - ), 220 - pht('discard this draft')); 221 - 222 - $draft_note = new AphrontErrorView(); 223 - $draft_note->setSeverity(AphrontErrorView::SEVERITY_NOTICE); 224 - $draft_note->setTitle('Recovered Draft'); 225 - $draft_note->appendChild(hsprintf( 226 - '<p>Showing a saved draft of your edits, you can %s.</p>', 227 - $discard)); 228 - } else { 229 - $content_text = $content->getContent(); 230 - $draft_note = null; 231 - } 232 - 233 197 $form = id(new AphrontFormView()) 234 198 ->setUser($user) 235 - ->setWorkflow(true) 236 - ->setAction($request->getRequestURI()->getPath()) 237 199 ->addHiddenInput('slug', $document->getSlug()) 238 200 ->addHiddenInput('nodraft', $request->getBool('nodraft')) 239 201 ->addHiddenInput('contentVersion', $current_version) 202 + ->addHiddenInput('overwrite', $overwrite) 240 203 ->appendChild( 241 204 id(new AphrontFormTextControl()) 242 205 ->setLabel(pht('Title')) 243 - ->setValue($content->getTitle()) 206 + ->setValue($title) 244 207 ->setError($e_title) 245 208 ->setName('title')) 246 209 ->appendChild( ··· 251 214 id(new PhabricatorRemarkupControl()) 252 215 ->setLabel(pht('Content')) 253 216 ->setValue($content_text) 217 + ->setError($e_content) 254 218 ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) 255 219 ->setName('content') 256 220 ->setID('document-textarea') ··· 267 231 ->setValue($submit_button)); 268 232 269 233 $form_box = id(new PHUIObjectBoxView()) 270 - ->setHeaderText(pht('Edit Document')) 271 - ->setFormErrors($errors) 234 + ->setHeaderText($panel_header) 235 + ->setValidationException($validation_exception) 272 236 ->setForm($form); 273 237 274 238 $preview = id(new PHUIRemarkupPreviewPanel()) ··· 295 259 $preview, 296 260 ), 297 261 array( 298 - 'title' => pht('Edit Document'), 262 + 'title' => $page_title, 299 263 )); 300 264 } 301 265
+137 -1
src/applications/phriction/editor/PhrictionTransactionEditor.php
··· 8 8 private $newContent; 9 9 private $moveAwayDocument; 10 10 private $skipAncestorCheck; 11 + private $contentVersion; 12 + private $processContentVersionError = true; 11 13 12 14 public function setDescription($description) { 13 15 $this->description = $description; ··· 43 45 44 46 public function getSkipAncestorCheck() { 45 47 return $this->skipAncestorCheck; 48 + } 49 + 50 + public function setContentVersion($version) { 51 + $this->contentVersion = $version; 52 + return $this; 53 + } 54 + 55 + public function getContentVersion() { 56 + return $this->contentVersion; 57 + } 58 + 59 + public function setProcessContentVersionError($process) { 60 + $this->processContentVersionError = $process; 61 + return $this; 62 + } 63 + 64 + public function getProcessContentVersionError() { 65 + return $this->processContentVersionError; 46 66 } 47 67 48 68 public function getEditorApplicationClass() { ··· 162 182 } 163 183 } 164 184 185 + protected function expandTransaction( 186 + PhabricatorLiskDAO $object, 187 + PhabricatorApplicationTransaction $xaction) { 188 + 189 + $xactions = parent::expandTransaction($object, $xaction); 190 + switch ($xaction->getTransactionType()) { 191 + case PhrictionTransaction::TYPE_CONTENT: 192 + if ($this->getIsNewObject()) { 193 + break; 194 + } 195 + $content = $xaction->getNewValue(); 196 + if ($content === '') { 197 + $xactions[] = id(new PhrictionTransaction()) 198 + ->setTransactionType(PhrictionTransaction::TYPE_DELETE) 199 + ->setNewValue(true); 200 + } 201 + break; 202 + default: 203 + break; 204 + } 205 + 206 + return $xactions; 207 + } 208 + 165 209 protected function applyCustomExternalTransaction( 166 210 PhabricatorLiskDAO $object, 167 211 PhabricatorApplicationTransaction $xaction) { ··· 251 295 ->setMetadataValue('stub:create:phid', $object->getPHID()); 252 296 $stub_xactions[] = id(new PhrictionTransaction()) 253 297 ->setTransactionType(PhrictionTransaction::TYPE_CONTENT) 254 - ->setNewValue(''); 298 + ->setNewValue('') 299 + ->setMetadataValue('stub:create:phid', $object->getPHID()); 255 300 $sub_editor = id(new PhrictionTransactionEditor()) 256 301 ->setActor($this->getActor()) 257 302 ->setContentSource($this->getContentSource()) ··· 364 409 } 365 410 366 411 return $phids; 412 + } 413 + 414 + protected function validateTransaction( 415 + PhabricatorLiskDAO $object, 416 + $type, 417 + array $xactions) { 418 + 419 + $errors = parent::validateTransaction($object, $type, $xactions); 420 + 421 + foreach ($xactions as $xaction) { 422 + switch ($type) { 423 + case PhrictionTransaction::TYPE_TITLE: 424 + $title = $object->getContent()->getTitle(); 425 + $missing = $this->validateIsEmptyTextField( 426 + $title, 427 + $xactions); 428 + 429 + if ($missing) { 430 + $error = new PhabricatorApplicationTransactionValidationError( 431 + $type, 432 + pht('Required'), 433 + pht('Document title is required.'), 434 + nonempty(last($xactions), null)); 435 + 436 + $error->setIsMissingFieldError(true); 437 + $errors[] = $error; 438 + } else if ($this->getProcessContentVersionError()) { 439 + $error = $this->validateContentVersion($object, $type, $xaction); 440 + if ($error) { 441 + $this->setProcessContentVersionError(false); 442 + $errors[] = $error; 443 + } 444 + } 445 + break; 446 + 447 + case PhrictionTransaction::TYPE_CONTENT: 448 + if ($xaction->getMetadataValue('stub:create:phid')) { 449 + continue; 450 + } 451 + 452 + $missing = false; 453 + if ($this->getIsNewObject()) { 454 + $content = $object->getContent()->getContent(); 455 + $missing = $this->validateIsEmptyTextField( 456 + $content, 457 + $xactions); 458 + } 459 + 460 + if ($missing) { 461 + $error = new PhabricatorApplicationTransactionValidationError( 462 + $type, 463 + pht('Required'), 464 + pht('Document content is required.'), 465 + nonempty(last($xactions), null)); 466 + 467 + $error->setIsMissingFieldError(true); 468 + $errors[] = $error; 469 + } else if ($this->getProcessContentVersionError()) { 470 + $error = $this->validateContentVersion($object, $type, $xaction); 471 + if ($error) { 472 + $this->setProcessContentVersionError(false); 473 + $errors[] = $error; 474 + } 475 + } 476 + break; 477 + } 478 + } 479 + 480 + return $errors; 481 + } 482 + 483 + private function validateContentVersion( 484 + PhabricatorLiskDAO $object, 485 + $type, 486 + PhabricatorApplicationTransaction $xaction) { 487 + 488 + $error = null; 489 + if ($this->getContentVersion() && 490 + ($object->getContent()->getVersion() != $this->getContentVersion())) { 491 + $error = new PhabricatorApplicationTransactionValidationError( 492 + $type, 493 + pht('Edit Conflict'), 494 + pht( 495 + 'Another user made changes to this document after you began '. 496 + 'editing it. Do you want to overwrite their changes? '. 497 + '(If you choose to overwrite their changes, you should review '. 498 + 'the document edit history to see what you overwrote, and '. 499 + 'then make another edit to merge the changes if necessary.)'), 500 + $xaction); 501 + } 502 + return $error; 367 503 } 368 504 369 505 protected function supportsSearch() {