2011-08-26 21:50:28 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create or update Phriction documents.
|
2011-09-14 17:02:31 +02:00
|
|
|
*
|
|
|
|
* @group phriction
|
2011-08-26 21:50:28 +02:00
|
|
|
*/
|
2012-10-10 19:18:23 +02:00
|
|
|
final class PhrictionDocumentEditor extends PhabricatorEditor {
|
2011-08-26 21:50:28 +02:00
|
|
|
|
|
|
|
private $document;
|
|
|
|
private $content;
|
|
|
|
|
|
|
|
private $newTitle;
|
|
|
|
private $newContent;
|
|
|
|
private $description;
|
|
|
|
|
|
|
|
private function __construct() {
|
|
|
|
// <restricted>
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function newForSlug($slug) {
|
2012-04-10 23:18:20 +02:00
|
|
|
$slug = PhabricatorSlug::normalize($slug);
|
2011-08-26 21:50:28 +02:00
|
|
|
$document = id(new PhrictionDocument())->loadOneWhere(
|
|
|
|
'slug = %s',
|
|
|
|
$slug);
|
|
|
|
$content = null;
|
|
|
|
|
|
|
|
if ($document) {
|
|
|
|
$content = id(new PhrictionContent())->load($document->getContentID());
|
|
|
|
} else {
|
|
|
|
$document = new PhrictionDocument();
|
|
|
|
$document->setSlug($slug);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$content) {
|
2012-04-10 23:18:20 +02:00
|
|
|
$default_title = PhabricatorSlug::getDefaultTitle($slug);
|
2011-08-26 21:50:28 +02:00
|
|
|
$content = new PhrictionContent();
|
|
|
|
$content->setSlug($slug);
|
|
|
|
$content->setTitle($default_title);
|
Set default content to "" (empty string), not null, in PhrictionDocumentEditor
Summary: phriction.edit allows you to omit the content string, meaning "don't
edit content". If you're also creating the page, we currently break in a
terrible, horrible, no-good, very-bad way because 'content' can't be null.
Default to empty string instead so phriction.edit creates an empty page instead
of a broken mess.
Test Plan: Called phriction.edit on a new page with no content.
Reviewers: skrul, jungejason, nh, tuomaspelkonen, aran
Reviewed By: skrul
CC: aran, skrul
Differential Revision: 920
2011-09-12 20:11:45 +02:00
|
|
|
$content->setContent('');
|
2011-08-26 21:50:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$obj = new PhrictionDocumentEditor();
|
|
|
|
$obj->document = $document;
|
|
|
|
$obj->content = $content;
|
|
|
|
|
|
|
|
return $obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setTitle($title) {
|
|
|
|
$this->newTitle = $title;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setContent($content) {
|
|
|
|
$this->newContent = $content;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setDescription($description) {
|
|
|
|
$this->description = $description;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getDocument() {
|
|
|
|
return $this->document;
|
|
|
|
}
|
|
|
|
|
2011-12-17 18:19:08 +01:00
|
|
|
public function delete() {
|
2012-10-10 19:18:23 +02:00
|
|
|
$actor = $this->requireActor();
|
2011-12-17 18:19:08 +01:00
|
|
|
|
|
|
|
// TODO: Should we do anything about deleting an already-deleted document?
|
|
|
|
// We currently allow it.
|
|
|
|
|
|
|
|
$document = $this->document;
|
|
|
|
$content = $this->content;
|
|
|
|
|
|
|
|
$new_content = $this->buildContentTemplate($document, $content);
|
|
|
|
|
|
|
|
$new_content->setChangeType(PhrictionChangeType::CHANGE_DELETE);
|
|
|
|
$new_content->setContent('');
|
|
|
|
|
|
|
|
return $this->updateDocument($document, $content, $new_content);
|
|
|
|
}
|
|
|
|
|
2013-03-04 17:51:10 +01:00
|
|
|
private function stub() {
|
2013-03-04 16:31:59 +01:00
|
|
|
$actor = $this->requireActor();
|
|
|
|
$document = $this->document;
|
|
|
|
$content = $this->content;
|
|
|
|
$new_content = $this->buildContentTemplate($document, $content);
|
|
|
|
|
2013-03-04 17:51:10 +01:00
|
|
|
$new_content->setChangeType(PhrictionChangeType::CHANGE_STUB);
|
2013-03-04 16:31:59 +01:00
|
|
|
$new_content->setContent('');
|
|
|
|
|
|
|
|
return $this->updateDocument($document, $content, $new_content);
|
|
|
|
}
|
|
|
|
|
2011-08-26 21:50:28 +02:00
|
|
|
public function save() {
|
2012-10-10 19:18:23 +02:00
|
|
|
$actor = $this->requireActor();
|
2011-12-17 18:19:08 +01:00
|
|
|
|
|
|
|
if ($this->newContent === '') {
|
|
|
|
// If this is an edit which deletes all the content, just treat it as
|
|
|
|
// a delete. NOTE: null means "don't change the content", not "delete
|
|
|
|
// the page"! Thus the strict type check.
|
|
|
|
return $this->delete();
|
2011-08-26 21:50:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$document = $this->document;
|
|
|
|
$content = $this->content;
|
|
|
|
|
2011-12-17 18:19:08 +01:00
|
|
|
$new_content = $this->buildContentTemplate($document, $content);
|
|
|
|
|
|
|
|
return $this->updateDocument($document, $content, $new_content);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function buildContentTemplate(
|
|
|
|
PhrictionDocument $document,
|
|
|
|
PhrictionContent $content) {
|
|
|
|
|
2011-08-26 21:50:28 +02:00
|
|
|
$new_content = new PhrictionContent();
|
|
|
|
$new_content->setSlug($document->getSlug());
|
2012-10-10 19:18:23 +02:00
|
|
|
$new_content->setAuthorPHID($this->getActor()->getPHID());
|
2011-12-17 18:19:08 +01:00
|
|
|
$new_content->setChangeType(PhrictionChangeType::CHANGE_EDIT);
|
2011-08-26 21:50:28 +02:00
|
|
|
|
|
|
|
$new_content->setTitle(
|
|
|
|
coalesce(
|
|
|
|
$this->newTitle,
|
|
|
|
$content->getTitle()));
|
|
|
|
|
|
|
|
$new_content->setContent(
|
|
|
|
coalesce(
|
|
|
|
$this->newContent,
|
|
|
|
$content->getContent()));
|
|
|
|
|
|
|
|
if (strlen($this->description)) {
|
|
|
|
$new_content->setDescription($this->description);
|
|
|
|
}
|
|
|
|
|
2011-12-17 18:19:08 +01:00
|
|
|
return $new_content;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function updateDocument($document, $content, $new_content) {
|
2011-08-26 21:50:28 +02:00
|
|
|
|
|
|
|
$is_new = false;
|
|
|
|
if (!$document->getID()) {
|
|
|
|
$is_new = true;
|
2011-12-17 18:19:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$new_content->setVersion($content->getVersion() + 1);
|
|
|
|
|
|
|
|
$change_type = $new_content->getChangeType();
|
|
|
|
switch ($change_type) {
|
|
|
|
case PhrictionChangeType::CHANGE_EDIT:
|
|
|
|
$doc_status = PhrictionDocumentStatus::STATUS_EXISTS;
|
|
|
|
$feed_action = $is_new
|
|
|
|
? PhrictionActionConstants::ACTION_CREATE
|
|
|
|
: PhrictionActionConstants::ACTION_EDIT;
|
|
|
|
break;
|
|
|
|
case PhrictionChangeType::CHANGE_DELETE:
|
|
|
|
$doc_status = PhrictionDocumentStatus::STATUS_DELETED;
|
|
|
|
$feed_action = PhrictionActionConstants::ACTION_DELETE;
|
|
|
|
if ($is_new) {
|
|
|
|
throw new Exception(
|
|
|
|
"You can not delete a document which doesn't exist yet!");
|
|
|
|
}
|
|
|
|
break;
|
2013-03-04 17:51:10 +01:00
|
|
|
case PhrictionChangeType::CHANGE_STUB:
|
2013-03-04 16:31:59 +01:00
|
|
|
$doc_status = PhrictionDocumentStatus::STATUS_STUB;
|
|
|
|
$feed_action = null;
|
|
|
|
break;
|
2011-12-17 18:19:08 +01:00
|
|
|
default:
|
|
|
|
throw new Exception(
|
|
|
|
"Unsupported content change type '{$change_type}'!");
|
|
|
|
}
|
|
|
|
|
|
|
|
$document->setStatus($doc_status);
|
|
|
|
|
|
|
|
// TODO: This should be transactional.
|
|
|
|
|
|
|
|
if ($is_new) {
|
2011-08-26 21:50:28 +02:00
|
|
|
$document->save();
|
|
|
|
}
|
|
|
|
|
|
|
|
$new_content->setDocumentID($document->getID());
|
|
|
|
$new_content->save();
|
|
|
|
|
|
|
|
$document->setContentID($new_content->getID());
|
|
|
|
$document->save();
|
|
|
|
|
|
|
|
$document->attachContent($new_content);
|
Improve Search architecture
Summary:
The search indexing API has several problems right now:
- Always runs in-process.
- It would be nice to push this into the task queue for performance. However, the API currently passses an object all the way through (and some indexers depend on preloaded object attributes), so it can't be dumped into the task queue at any stage since we can't serialize it.
- Being able to use the task queue will also make rebuilding indexes faster.
- Instead, make the API phid-oriented.
- No uniform indexing API.
- Each "Editor" currently calls SomeCustomIndexer::indexThing(). This won't work with AbstractTransactions. The API is also just weird.
- Instead, provide a uniform API.
- No uniform CLI.
- We have `scripts/search/reindex_everything.php`, but it doesn't actually index everything. Each new document type needs to be separately added to it, leading to stuff like D3839. Third-party applications can't provide indexers.
- Instead, let indexers expose documents for indexing.
- Not application-oriented.
- All the indexers live in search/ right now, which isn't the right organization in an application-orietned view of the world.
- Instead, move indexers to applications and load them with SymbolLoader.
Test Plan:
- `bin/search index`
- Indexed one revision, one task.
- Indexed `--type TASK`, `--type DREV`, etc., for all types.
- Indexed `--all`.
- Added the word "saboteur" to a revision, task, wiki page, and question and then searched for it.
- Creating users is a pain; searched for a user after indexing.
- Creating commits is a pain; searched for a commit after indexing.
- Mocks aren't currently loadable in the result view, so their indexing is moot.
Reviewers: btrahan, vrana
Reviewed By: btrahan
CC: 20after4, aran
Maniphest Tasks: T1991, T2104
Differential Revision: https://secure.phabricator.com/D4261
2012-12-21 23:21:31 +01:00
|
|
|
|
|
|
|
id(new PhabricatorSearchIndexer())
|
|
|
|
->indexDocumentByPHID($document->getPHID());
|
2011-08-26 21:50:28 +02:00
|
|
|
|
2013-03-04 16:31:59 +01:00
|
|
|
// Stub out empty parent documents if they don't exist
|
|
|
|
$ancestral_slugs = PhabricatorSlug::getAncestry($document->getSlug());
|
|
|
|
if ($ancestral_slugs) {
|
|
|
|
$ancestors = id(new PhrictionDocument())->loadAllWhere(
|
|
|
|
'slug IN (%Ls)',
|
|
|
|
$ancestral_slugs);
|
2013-03-04 17:51:10 +01:00
|
|
|
$ancestors = mpull($ancestors, null, 'getSlug');
|
2013-03-04 16:31:59 +01:00
|
|
|
foreach ($ancestral_slugs as $slug) {
|
|
|
|
// We check for change type to prevent near-infinite recursion
|
|
|
|
if (!isset($ancestors[$slug]) &&
|
2013-03-04 17:51:10 +01:00
|
|
|
$new_content->getChangeType() != PhrictionChangeType::CHANGE_STUB) {
|
2013-03-04 16:31:59 +01:00
|
|
|
|
|
|
|
id(PhrictionDocumentEditor::newForSlug($slug))
|
|
|
|
->setActor($this->getActor())
|
|
|
|
->setTitle(PhabricatorSlug::getDefaultTitle($slug))
|
|
|
|
->setContent('')
|
|
|
|
->setDescription(pht('Empty Parent Document'))
|
|
|
|
->stub();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Provide wiki pages for projects
Summary:
Provide tighter integration between Projects and Phriction. Partly, I have most
of a rewrite for the Projects homepage ready but it's not currently possible to
publish feed stories about a project so all the feeds are empty/boring. This
partly makes them more useful and partly just provides a tool integration point.
- When you create a project, all the wiki pages in projects/<project_name>/*
are associated with it.
- Publish updates to those pages as being related to the project so they'll
show up in project feeds.
- Show a project link on those pages.
This is very "convention over configuration" but I think it's the right
approach. We could provide some sort of, like, "@project=derp" tag to let you
associated arbitrary pages to projects later, but just letting you move pages is
probably far better.
Test Plan:
- Ran upgrade scripts against stupidly named projects ("der", " der", " der
", "der (2)", " der (2) (2)", etc). Ended up with uniquely named projects.
- Ran unit tests.
- Created /projects/ wiki documents and made sure they displayed correctly.
- Verified feed stories publish as project-related.
- Edited projects, including perfomring a name-colliding edit.
- Created projects, including performing a name-colliding create.
Reviewers: btrahan, jungejason
Reviewed By: btrahan
CC: aran, epriestley, btrahan
Maniphest Tasks: T681
Differential Revision: 1231
2011-12-17 20:58:55 +01:00
|
|
|
$project_phid = null;
|
|
|
|
$slug = $document->getSlug();
|
|
|
|
if (PhrictionDocument::isProjectSlug($slug)) {
|
|
|
|
$project = id(new PhabricatorProject())->loadOneWhere(
|
|
|
|
'phrictionSlug = %s',
|
|
|
|
PhrictionDocument::getProjectSlugIdentifier($slug));
|
|
|
|
if ($project) {
|
|
|
|
$project_phid = $project->getPHID();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$related_phids = array(
|
|
|
|
$document->getPHID(),
|
2012-10-10 19:18:23 +02:00
|
|
|
$this->getActor()->getPHID(),
|
Provide wiki pages for projects
Summary:
Provide tighter integration between Projects and Phriction. Partly, I have most
of a rewrite for the Projects homepage ready but it's not currently possible to
publish feed stories about a project so all the feeds are empty/boring. This
partly makes them more useful and partly just provides a tool integration point.
- When you create a project, all the wiki pages in projects/<project_name>/*
are associated with it.
- Publish updates to those pages as being related to the project so they'll
show up in project feeds.
- Show a project link on those pages.
This is very "convention over configuration" but I think it's the right
approach. We could provide some sort of, like, "@project=derp" tag to let you
associated arbitrary pages to projects later, but just letting you move pages is
probably far better.
Test Plan:
- Ran upgrade scripts against stupidly named projects ("der", " der", " der
", "der (2)", " der (2) (2)", etc). Ended up with uniquely named projects.
- Ran unit tests.
- Created /projects/ wiki documents and made sure they displayed correctly.
- Verified feed stories publish as project-related.
- Edited projects, including perfomring a name-colliding edit.
- Created projects, including performing a name-colliding create.
Reviewers: btrahan, jungejason
Reviewed By: btrahan
CC: aran, epriestley, btrahan
Maniphest Tasks: T681
Differential Revision: 1231
2011-12-17 20:58:55 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
if ($project_phid) {
|
|
|
|
$related_phids[] = $project_phid;
|
|
|
|
}
|
|
|
|
|
2013-03-04 16:31:59 +01:00
|
|
|
if ($feed_action) {
|
|
|
|
id(new PhabricatorFeedStoryPublisher())
|
|
|
|
->setRelatedPHIDs($related_phids)
|
|
|
|
->setStoryAuthorPHID($this->getActor()->getPHID())
|
|
|
|
->setStoryTime(time())
|
|
|
|
->setStoryType(PhabricatorFeedStoryTypeConstants::STORY_PHRICTION)
|
|
|
|
->setStoryData(
|
|
|
|
array(
|
|
|
|
'phid' => $document->getPHID(),
|
|
|
|
'action' => $feed_action,
|
|
|
|
'content' => phutil_utf8_shorten($new_content->getContent(), 140),
|
|
|
|
'project' => $project_phid,
|
|
|
|
))
|
|
|
|
->publish();
|
|
|
|
}
|
2011-08-26 21:50:28 +02:00
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|