Skip to content

Commit 911d02e

Browse files
AnhNhanepriestley
authored andcommitted
Phriction Documents can be moved
Summary: Refs T1575 Test Plan: created plenty of pages, did all kind of stuff with them, moved them around, verified expected behaviour. {F34453} {F34455} Reviewers: epriestley, chad, btrahan Reviewed By: epriestley CC: aran, epriestley, Korvin Maniphest Tasks: T1575 Differential Revision: https://secure.phabricator.com/D5198
1 parent cd99850 commit 911d02e

File tree

8 files changed

+274
-21
lines changed

8 files changed

+274
-21
lines changed

src/__phutil_library_map__.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1497,6 +1497,7 @@
14971497
'PhrictionEditController' => 'applications/phriction/controller/PhrictionEditController.php',
14981498
'PhrictionHistoryController' => 'applications/phriction/controller/PhrictionHistoryController.php',
14991499
'PhrictionListController' => 'applications/phriction/controller/PhrictionListController.php',
1500+
'PhrictionMoveController' => 'applications/phriction/controller/PhrictionMoveController.php',
15001501
'PhrictionNewController' => 'applications/phriction/controller/PhrictionNewController.php',
15011502
'PhrictionRemarkupRule' => 'applications/phriction/remarkup/PhrictionRemarkupRule.php',
15021503
'PhrictionSearchIndexer' => 'applications/phriction/search/PhrictionSearchIndexer.php',
@@ -3003,6 +3004,7 @@
30033004
'PhrictionEditController' => 'PhrictionController',
30043005
'PhrictionHistoryController' => 'PhrictionController',
30053006
'PhrictionListController' => 'PhrictionController',
3007+
'PhrictionMoveController' => 'PhrictionController',
30063008
'PhrictionNewController' => 'PhrictionController',
30073009
'PhrictionRemarkupRule' => 'PhutilRemarkupRule',
30083010
'PhrictionSearchIndexer' => 'PhabricatorSearchDocumentIndexer',

src/applications/phriction/application/PhabricatorApplicationPhriction.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public function getRoutes() {
4343
'edit/(?:(?P<id>[1-9]\d*)/)?' => 'PhrictionEditController',
4444
'delete/(?P<id>[1-9]\d*)/' => 'PhrictionDeleteController',
4545
'new/' => 'PhrictionNewController',
46-
'move/(?P<id>[1-9]\d*)/' => 'PhrictionMoveController',
46+
'move/(?:(?P<id>[1-9]\d*)/)?' => 'PhrictionMoveController',
4747

4848
'preview/' => 'PhrictionDocumentPreviewController',
4949
'diff/(?P<id>[1-9]\d*)/' => 'PhrictionDiffController',

src/applications/phriction/constants/PhrictionActionConstants.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,16 @@ final class PhrictionActionConstants extends PhrictionConstants {
88
const ACTION_CREATE = 'create';
99
const ACTION_EDIT = 'edit';
1010
const ACTION_DELETE = 'delete';
11+
const ACTION_MOVE_AWAY = 'move to';
12+
const ACTION_MOVE_HERE = 'move here';
1113

1214
public static function getActionPastTenseVerb($action) {
1315
static $map = array(
1416
self::ACTION_CREATE => 'created',
1517
self::ACTION_EDIT => 'edited',
1618
self::ACTION_DELETE => 'deleted',
19+
self::ACTION_MOVE_AWAY => 'moved a document to',
20+
self::ACTION_MOVE_HERE => 'moved a document from',
1721
);
1822

1923
return idx($map, $action, "brazenly {$action}'d");

src/applications/phriction/constants/PhrictionDocumentStatus.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ final class PhrictionDocumentStatus extends PhrictionConstants {
1212

1313
public static function getConduitConstant($const) {
1414
static $map = array(
15-
self::STATUS_EXISTS => 'exists',
15+
self::STATUS_EXISTS => 'exists',
1616
self::STATUS_DELETED => 'deleted',
17+
self::STATUS_MOVED => 'moved',
1718
self::STATUS_STUB => 'stubbed',
1819
);
1920

src/applications/phriction/controller/PhrictionDocumentController.php

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ public function willProcessRequest(array $data) {
1313
}
1414

1515
public function processRequest() {
16-
1716
$request = $this->getRequest();
1817
$user = $request->getUser();
1918

@@ -148,14 +147,43 @@ public function processRequest() {
148147
pht('This document is empty. You can edit it to put some proper '.
149148
'content here.'));
150149
$core_content = $notice->render();
150+
} else if ($doc_status == PhrictionDocumentStatus::STATUS_MOVED) {
151+
$new_doc_id = $content->getChangeRef();
152+
$new_doc = new PhrictionDocument();
153+
$new_doc->load($new_doc_id);
154+
155+
$slug_uri = PhrictionDocument::getSlugURI($new_doc->getSlug());
156+
157+
$notice = new AphrontErrorView();
158+
$notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
159+
$notice->setTitle(pht('Document Moved'));
160+
$notice->appendChild(phutil_tag('p', array(),
161+
pht('This document has been moved to %s. You can edit it to put new '.
162+
'content here, or use history to revert to an earlier version.',
163+
phutil_tag('a', array('href' => $slug_uri), $slug_uri))));
164+
$core_content = $notice->render();
151165
} else {
152166
throw new Exception("Unknown document status '{$doc_status}'!");
153167
}
154168

169+
$move_notice = null;
170+
if ($content->getChangeType() == PhrictionChangeType::CHANGE_MOVE_HERE) {
171+
$from_doc_id = $content->getChangeRef();
172+
$from_doc = id(new PhrictionDocument())->load($from_doc_id);
173+
$slug_uri = PhrictionDocument::getSlugURI($from_doc->getSlug());
174+
175+
$move_notice = id(new AphrontErrorView())
176+
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
177+
->appendChild(pht('This document was moved from %s',
178+
phutil_tag('a', array('href' => $slug_uri), $slug_uri)))
179+
->render();
180+
}
181+
155182
$page_content = hsprintf(
156-
'<div class="phriction-content">%s%s%s</div>',
183+
'<div class="phriction-content">%s%s%s%s</div>',
157184
$index_link,
158185
$byline,
186+
$move_notice,
159187
$core_content);
160188
}
161189

@@ -221,6 +249,13 @@ private function buildActionView(
221249
->setHref('/phriction/edit/'.$document->getID().'/'));
222250

223251
if ($document->getStatus() == PhrictionDocumentStatus::STATUS_EXISTS) {
252+
$action_view->addAction(
253+
id(new PhabricatorActionView())
254+
->setName(pht('Move Document'))
255+
->setIcon('move')
256+
->setHref('/phriction/move/'.$document->getID().'/')
257+
->setWorkflow(true));
258+
224259
$action_view->addAction(
225260
id(new PhabricatorActionView())
226261
->setName(pht('Delete Document'))

src/applications/phriction/controller/PhrictionListController.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,13 @@ private function loadDocuments(AphrontPagerView $pager) {
115115
case 'active':
116116
$data = queryfx_all(
117117
$conn,
118-
'SELECT * FROM %T WHERE status != %d ORDER BY id DESC LIMIT %d, %d',
118+
'SELECT * FROM %T WHERE status NOT IN (%Ld) ORDER BY id DESC '.
119+
'LIMIT %d, %d',
119120
$document_dao->getTableName(),
120-
PhrictionDocumentStatus::STATUS_DELETED,
121+
array(
122+
PhrictionDocumentStatus::STATUS_DELETED,
123+
PhrictionDocumentStatus::STATUS_MOVED,
124+
),
121125
$pager->getOffset(),
122126
$pager->getPageSize() + 1);
123127
break;
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
<?php
2+
3+
/**
4+
* @group phriction
5+
*/
6+
final class PhrictionMoveController
7+
extends PhrictionController {
8+
9+
private $id;
10+
11+
public function willProcessRequest(array $data) {
12+
$this->id = idx($data, 'id');
13+
}
14+
15+
public function processRequest() {
16+
$request = $this->getRequest();
17+
$user = $request->getUser();
18+
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
19+
20+
if ($this->id) {
21+
$document = id(new PhrictionDocument())->load($this->id);
22+
} else {
23+
$slug = PhabricatorSlug::normalize(
24+
$request->getStr('slug'));
25+
if (!$slug) {
26+
return new Aphront404Response();
27+
}
28+
29+
$document = id(new PhrictionDocument())->loadOneWhere(
30+
'slug = %s',
31+
$slug);
32+
}
33+
34+
if (!$document) {
35+
return new Aphront404Response();
36+
}
37+
38+
if (!isset($slug)) {
39+
$slug = $document->getSlug();
40+
}
41+
42+
$target_slug = PhabricatorSlug::normalize(
43+
$request->getStr('new-slug', $slug));
44+
45+
$submit_uri = $request->getRequestURI()->getPath();
46+
$cancel_uri = PhrictionDocument::getSlugURI($slug);
47+
48+
$errors = array();
49+
$error_view = null;
50+
$e_url = null;
51+
$e_block = false;
52+
53+
$disallowed_statuses = array(
54+
PhrictionDocumentStatus::STATUS_DELETED, // Stupid
55+
PhrictionDocumentStatus::STATUS_MOVED, // Plain stupid
56+
);
57+
if (in_array($document->getStatus(), $disallowed_statuses)) {
58+
$error_view = new AphrontErrorView();
59+
$error_view->setSeverity(AphrontErrorView::SEVERITY_ERROR);
60+
$error_view->appendChild(pht('An already moved or deleted document '.
61+
'can not be moved again.'));
62+
63+
$error_dialog = new AphrontDialogView();
64+
$error_dialog->setUser($user);
65+
$error_dialog->setTitle("");
66+
$error_dialog->appendChild($error_view);
67+
$error_dialog->addCancelButton($cancel_uri, pht('I understand'));
68+
69+
return id(new AphrontDialogResponse())->setDialog($error_dialog);
70+
}
71+
72+
$content = id(new PhrictionContent())->load($document->getContentID());
73+
74+
if ($request->isFormPost() && !count($errors)) {
75+
if (!count($errors)) { // First check if the target document exists
76+
$target_document = id(new PhrictionDocument())->loadOneWhere(
77+
'slug = %s',
78+
$target_slug);
79+
80+
// Considering to overwrite existing docs? Nuke this!
81+
if ($target_document && $target_document->getStatus() ==
82+
PhrictionDocumentStatus::STATUS_EXISTS) {
83+
84+
$errors[] = pht('Can not overwrite existing target document.');
85+
$e_url = pht('Already exists.');
86+
}
87+
}
88+
89+
if (!count($errors)) { // I like to move it, move it!
90+
$from_editor = id(PhrictionDocumentEditor::newForSlug($slug))
91+
->setActor($user)
92+
->setTitle($content->getTitle())
93+
->setContent($content->getContent())
94+
->setDescription($content->getDescription());
95+
96+
$target_editor = id(PhrictionDocumentEditor::newForSlug(
97+
$target_slug))
98+
->setActor($user)
99+
->setTitle($content->getTitle())
100+
->setContent($content->getContent())
101+
->setDescription($content->getDescription());
102+
103+
// Move it!
104+
$target_editor->moveHere($document->getID());
105+
106+
// Retrieve the target doc directly from the editor
107+
// No need to load it per Sql again
108+
$target_document = $target_editor->getDocument();
109+
$from_editor->moveAway($target_document->getID());
110+
111+
$redir_uri = PhrictionDocument::getSlugURI($target_document->getSlug());
112+
return id(new AphrontRedirectResponse())->setURI($redir_uri);
113+
}
114+
}
115+
116+
if ($errors) {
117+
$error_view = id(new AphrontErrorView())
118+
->setTitle(pht('Form Errors'))
119+
->setErrors($errors);
120+
}
121+
122+
$descr_caption = $is_serious ? pht('A reason for the move.') :
123+
pht('You better give a good reason for this.');
124+
125+
if ($request->isAjax()) {
126+
$form = new AphrontFormLayoutView();
127+
} else {
128+
$form = new AphrontFormView();
129+
$form->setAction($submit_uri)
130+
// Append title so the user can verify that he's touching
131+
// the right document
132+
->appendChild(
133+
id(new AphrontFormStaticControl())
134+
->setLabel(pht('Title'))
135+
->setValue($content->getTitle()));
136+
}
137+
138+
$form
139+
->setUser($user)
140+
->appendChild(
141+
id(new AphrontFormTextControl())
142+
->setLabel(pht('New URI'))
143+
->setValue($target_slug)
144+
->setError($e_url)
145+
->setName('new-slug')
146+
->setCaption(pht('The new location of the document.')))
147+
->appendChild(
148+
id(new AphrontFormTextControl())
149+
->setLabel(pht('Edit Notes'))
150+
->setValue($content->getDescription())
151+
->setError(null)
152+
->setName('description')
153+
->setCaption($descr_caption));
154+
155+
if ($request->isAjax()) {
156+
$dialog = new AphrontDialogView();
157+
$dialog->setUser($user);
158+
$dialog->setTitle(pht('Move Document'));
159+
$dialog->appendChild($form);
160+
$dialog->setSubmitURI($submit_uri);
161+
$dialog->addSubmitButton(pht('Move Document'));
162+
$dialog->addCancelButton($cancel_uri);
163+
164+
return id(new AphrontDialogResponse())->setDialog($dialog);
165+
} else {
166+
$form->appendChild(
167+
id(new AphrontFormSubmitControl())
168+
->addCancelButton($cancel_uri)
169+
->setValue(pht('Move Document'))
170+
->setDisabled($e_block));
171+
172+
$panel = id(new AphrontPanelView())
173+
->setNoBackground()
174+
->setHeader(pht('Move Phriction Document'))
175+
->appendChild($form);
176+
177+
return $this->buildApplicationPage(
178+
array(
179+
$error_view,
180+
$panel,
181+
),
182+
array(
183+
'title' => pht('Move Document'),
184+
'device' => true,
185+
));
186+
}
187+
}
188+
}

src/applications/phriction/editor/PhrictionDocumentEditor.php

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -66,33 +66,44 @@ public function getDocument() {
6666
return $this->document;
6767
}
6868

69-
public function delete() {
70-
$actor = $this->requireActor();
69+
public function moveAway($new_doc_id) {
70+
return $this->execute(
71+
PhrictionChangeType::CHANGE_MOVE_AWAY, true, $new_doc_id);
72+
}
73+
74+
public function moveHere($old_doc_id) {
75+
return $this->execute(
76+
PhrictionChangeType::CHANGE_MOVE_HERE, false, $old_doc_id);
77+
}
78+
79+
private function execute(
80+
$change_type, $del_new_content = true, $doc_ref = null) {
7181

72-
// TODO: Should we do anything about deleting an already-deleted document?
73-
// We currently allow it.
82+
$actor = $this->requireActor();
7483

7584
$document = $this->document;
7685
$content = $this->content;
7786

7887
$new_content = $this->buildContentTemplate($document, $content);
88+
$new_content->setChangeType($change_type);
89+
90+
if ($del_new_content) {
91+
$new_content->setContent('');
92+
}
7993

80-
$new_content->setChangeType(PhrictionChangeType::CHANGE_DELETE);
81-
$new_content->setContent('');
94+
if ($doc_ref) {
95+
$new_content->setChangeRef($doc_ref);
96+
}
8297

8398
return $this->updateDocument($document, $content, $new_content);
8499
}
85100

86-
private function stub() {
87-
$actor = $this->requireActor();
88-
$document = $this->document;
89-
$content = $this->content;
90-
$new_content = $this->buildContentTemplate($document, $content);
91-
92-
$new_content->setChangeType(PhrictionChangeType::CHANGE_STUB);
93-
$new_content->setContent('');
101+
public function delete() {
102+
return $this->execute(PhrictionChangeType::CHANGE_DELETE, true);
103+
}
94104

95-
return $this->updateDocument($document, $content, $new_content);
105+
private function stub() {
106+
return $this->execute(PhrictionChangeType::CHANGE_STUB, true);
96107
}
97108

98109
public function save() {
@@ -168,6 +179,14 @@ private function updateDocument($document, $content, $new_content) {
168179
$doc_status = PhrictionDocumentStatus::STATUS_STUB;
169180
$feed_action = null;
170181
break;
182+
case PhrictionChangeType::CHANGE_MOVE_AWAY:
183+
$doc_status = PhrictionDocumentStatus::STATUS_MOVED;
184+
$feed_action = PhrictionActionConstants::ACTION_MOVE_AWAY;
185+
break;
186+
case PhrictionChangeType::CHANGE_MOVE_HERE:
187+
$doc_status = PhrictionDocumentStatus::STATUS_EXISTS;
188+
$feed_action = null;
189+
break;
171190
default:
172191
throw new Exception(
173192
"Unsupported content change type '{$change_type}'!");

0 commit comments

Comments
 (0)