public static function initializeNewDocument(PhabricatorUser $actor, $slug) { $document = new PhrictionDocument(); $document->setSlug($slug); $content = new PhrictionContent(); $content->setSlug($slug); $default_title = PhabricatorSlug::getDefaultTitle($slug); $content->setTitle($default_title); $document->attachContent($content); $parent_doc = null; $ancestral_slugs = PhabricatorSlug::getAncestry($slug); if ($ancestral_slugs) { $parent = end($ancestral_slugs); $parent_doc = id(new PhrictionDocumentQuery())->setViewer($actor)->withSlugs(array($parent))->executeOne(); } if ($parent_doc) { $document->setViewPolicy($parent_doc->getViewPolicy()); $document->setEditPolicy($parent_doc->getEditPolicy()); } else { $default_view_policy = PhabricatorPolicies::getMostOpenPolicy(); $document->setViewPolicy($default_view_policy); $document->setEditPolicy(PhabricatorPolicies::POLICY_USER); } return $document; }
protected function loadPage() { $table = new PhrictionDocument(); $conn_r = $table->establishConnection('r'); $rows = queryfx_all($conn_r, 'SELECT * FROM %T %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); $documents = $table->loadAllFromArray($rows); if ($documents) { $ancestor_slugs = array(); foreach ($documents as $key => $document) { $document_slug = $document->getSlug(); foreach (PhabricatorSlug::getAncestry($document_slug) as $ancestor) { $ancestor_slugs[$ancestor][] = $key; } } if ($ancestor_slugs) { $ancestors = queryfx_all($conn_r, 'SELECT * FROM %T WHERE slug IN (%Ls)', $document->getTableName(), array_keys($ancestor_slugs)); $ancestors = $table->loadAllFromArray($ancestors); $ancestors = mpull($ancestors, null, 'getSlug'); foreach ($ancestor_slugs as $ancestor_slug => $document_keys) { $ancestor = idx($ancestors, $ancestor_slug); foreach ($document_keys as $document_key) { $documents[$document_key]->attachAncestor($ancestor_slug, $ancestor); } } } } return $documents; }
public function testSlugAncestry() { $slugs = array('/' => array(), 'pokemon/' => array('/'), 'pokemon/squirtle/' => array('/', 'pokemon/')); foreach ($slugs as $slug => $ancestry) { $this->assertEqual($ancestry, PhabricatorSlug::getAncestry($slug), "Ancestry of '{$slug}'"); } }
public function renderBreadcrumbs($slug) { $ancestor_handles = array(); $ancestral_slugs = PhabricatorSlug::getAncestry($slug); $ancestral_slugs[] = $slug; if ($ancestral_slugs) { $empty_slugs = array_fill_keys($ancestral_slugs, null); $ancestors = id(new PhrictionDocumentQuery())->setViewer($this->getRequest()->getUser())->withSlugs($ancestral_slugs)->execute(); $ancestors = mpull($ancestors, null, 'getSlug'); $ancestor_phids = mpull($ancestors, 'getPHID'); $handles = array(); if ($ancestor_phids) { $handles = $this->loadViewerHandles($ancestor_phids); } $ancestor_handles = array(); foreach ($ancestral_slugs as $slug) { if (isset($ancestors[$slug])) { $ancestor_handles[] = $handles[$ancestors[$slug]->getPHID()]; } else { $handle = new PhabricatorObjectHandle(); $handle->setName(PhabricatorSlug::getDefaultTitle($slug)); $handle->setURI(PhrictionDocument::getSlugURI($slug)); $ancestor_handles[] = $handle; } } } $breadcrumbs = array(); foreach ($ancestor_handles as $ancestor_handle) { $breadcrumbs[] = id(new PHUICrumbView())->setName($ancestor_handle->getName())->setHref($ancestor_handle->getUri()); } return $breadcrumbs; }
private function renderDocumentChildren($slug) { $document_dao = new PhrictionDocument(); $content_dao = new PhrictionContent(); $conn = $document_dao->establishConnection('r'); $limit = 250; $d_child = PhabricatorSlug::getDepth($slug) + 1; $d_grandchild = PhabricatorSlug::getDepth($slug) + 2; // Select children and grandchildren. $children = queryfx_all($conn, 'SELECT d.slug, d.depth, c.title FROM %T d JOIN %T c ON d.contentID = c.id WHERE d.slug LIKE %> AND d.depth IN (%d, %d) AND d.status IN (%Ld) ORDER BY d.depth, c.title LIMIT %d', $document_dao->getTableName(), $content_dao->getTableName(), $slug == '/' ? '' : $slug, $d_child, $d_grandchild, array(PhrictionDocumentStatus::STATUS_EXISTS, PhrictionDocumentStatus::STATUS_STUB), $limit); if (!$children) { return; } // We're going to render in one of three modes to try to accommodate // different information scales: // // - If we found fewer than $limit rows, we know we have all the children // and grandchildren and there aren't all that many. We can just render // everything. // - If we found $limit rows but the results included some grandchildren, // we just throw them out and render only the children, as we know we // have them all. // - If we found $limit rows and the results have no grandchildren, we // have a ton of children. Render them and then let the user know that // this is not an exhaustive list. if (count($children) == $limit) { $more_children = true; foreach ($children as $child) { if ($child['depth'] == $d_grandchild) { $more_children = false; } } $show_grandchildren = false; } else { $show_grandchildren = true; $more_children = false; } $grandchildren = array(); foreach ($children as $key => $child) { if ($child['depth'] == $d_child) { continue; } else { unset($children[$key]); if ($show_grandchildren) { $ancestors = PhabricatorSlug::getAncestry($child['slug']); $grandchildren[end($ancestors)][] = $child; } } } // Fill in any missing children. $known_slugs = ipull($children, null, 'slug'); foreach ($grandchildren as $slug => $ignored) { if (empty($known_slugs[$slug])) { $children[] = array('slug' => $slug, 'depth' => $d_child, 'title' => PhabricatorSlug::getDefaultTitle($slug), 'empty' => true); } } $children = isort($children, 'title'); $list = array(); foreach ($children as $child) { $list[] = hsprintf('<li>'); $list[] = $this->renderChildDocumentLink($child); $grand = idx($grandchildren, $child['slug'], array()); if ($grand) { $list[] = hsprintf('<ul>'); foreach ($grand as $grandchild) { $list[] = hsprintf('<li>'); $list[] = $this->renderChildDocumentLink($grandchild); $list[] = hsprintf('</li>'); } $list[] = hsprintf('</ul>'); } $list[] = hsprintf('</li>'); } if ($more_children) { $list[] = phutil_tag('li', array(), pht('More...')); } $content = array(phutil_tag('div', array('class' => 'phriction-children-header ' . 'sprite-gradient gradient-lightblue-header'), pht('Document Hierarchy')), phutil_tag('div', array('class' => 'phriction-children'), phutil_tag('ul', array(), $list))); return id(new PHUIDocumentView())->setOffset(true)->appendChild($content); }
private function renderDocumentChildren($slug) { $d_child = PhabricatorSlug::getDepth($slug) + 1; $d_grandchild = PhabricatorSlug::getDepth($slug) + 2; $limit = 250; $query = id(new PhrictionDocumentQuery())->setViewer($this->getRequest()->getUser())->withDepths(array($d_child, $d_grandchild))->withSlugPrefix($slug == '/' ? '' : $slug)->withStatuses(array(PhrictionDocumentStatus::STATUS_EXISTS, PhrictionDocumentStatus::STATUS_STUB))->setLimit($limit)->setOrder(PhrictionDocumentQuery::ORDER_HIERARCHY)->needContent(true); $children = $query->execute(); if (!$children) { return; } // We're going to render in one of three modes to try to accommodate // different information scales: // // - If we found fewer than $limit rows, we know we have all the children // and grandchildren and there aren't all that many. We can just render // everything. // - If we found $limit rows but the results included some grandchildren, // we just throw them out and render only the children, as we know we // have them all. // - If we found $limit rows and the results have no grandchildren, we // have a ton of children. Render them and then let the user know that // this is not an exhaustive list. if (count($children) == $limit) { $more_children = true; foreach ($children as $child) { if ($child->getDepth() == $d_grandchild) { $more_children = false; } } $show_grandchildren = false; } else { $show_grandchildren = true; $more_children = false; } $children_dicts = array(); $grandchildren_dicts = array(); foreach ($children as $key => $child) { $child_dict = array('slug' => $child->getSlug(), 'depth' => $child->getDepth(), 'title' => $child->getContent()->getTitle()); if ($child->getDepth() == $d_child) { $children_dicts[] = $child_dict; continue; } else { unset($children[$key]); if ($show_grandchildren) { $ancestors = PhabricatorSlug::getAncestry($child->getSlug()); $grandchildren_dicts[end($ancestors)][] = $child_dict; } } } // Fill in any missing children. $known_slugs = mpull($children, null, 'getSlug'); foreach ($grandchildren_dicts as $slug => $ignored) { if (empty($known_slugs[$slug])) { $children_dicts[] = array('slug' => $slug, 'depth' => $d_child, 'title' => PhabricatorSlug::getDefaultTitle($slug), 'empty' => true); } } $children_dicts = isort($children_dicts, 'title'); $list = array(); foreach ($children_dicts as $child) { $list[] = hsprintf('<li class="remarkup-list-item">'); $list[] = $this->renderChildDocumentLink($child); $grand = idx($grandchildren_dicts, $child['slug'], array()); if ($grand) { $list[] = hsprintf('<ul class="remarkup-list">'); foreach ($grand as $grandchild) { $list[] = hsprintf('<li class="remarkup-list-item">'); $list[] = $this->renderChildDocumentLink($grandchild); $list[] = hsprintf('</li>'); } $list[] = hsprintf('</ul>'); } $list[] = hsprintf('</li>'); } if ($more_children) { $list[] = phutil_tag('li', array('class' => 'remarkup-list-item'), pht('More...')); } $header = id(new PHUIHeaderView())->setHeader(pht('Document Hierarchy')); $box = id(new PHUIObjectBoxView())->setHeader($header)->appendChild(phutil_tag('div', array('class' => 'phabricator-remarkup mlt mlb'), phutil_tag('ul', array('class' => 'remarkup-list'), $list))); return phutil_tag_div('phui-document-box', $box); }
protected function requireCapabilities(PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { /* * New objects have a special case. If a user can't see * x/y * then definitely don't let them make some * x/y/z * We need to load the direct parent to handle this case. */ if ($this->getIsNewObject()) { $actor = $this->requireActor(); $parent_doc = null; $ancestral_slugs = PhabricatorSlug::getAncestry($object->getSlug()); // No ancestral slugs is "/"; the first person gets to play with "/". if ($ancestral_slugs) { $parent = end($ancestral_slugs); $parent_doc = id(new PhrictionDocumentQuery())->setViewer($actor)->withSlugs(array($parent))->executeOne(); // If the $actor can't see the $parent_doc then they can't create // the child $object; throw a policy exception. if (!$parent_doc) { id(new PhabricatorPolicyFilter())->setViewer($actor)->raisePolicyExceptions(true)->rejectObject($object, $object->getEditPolicy(), PhabricatorPolicyCapability::CAN_EDIT); } // If the $actor can't edit the $parent_doc then they can't create // the child $object; throw a policy exception. if (!PhabricatorPolicyFilter::hasCapability($actor, $parent_doc, PhabricatorPolicyCapability::CAN_EDIT)) { id(new PhabricatorPolicyFilter())->setViewer($actor)->raisePolicyExceptions(true)->rejectObject($object, $object->getEditPolicy(), PhabricatorPolicyCapability::CAN_EDIT); } } } return parent::requireCapabilities($object, $xaction); }
private function updateDocument($document, $content, $new_content) { $is_new = false; if (!$document->getID()) { $is_new = true; } $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; case PhrictionChangeType::CHANGE_STUB: $doc_status = PhrictionDocumentStatus::STATUS_STUB; $feed_action = null; break; case PhrictionChangeType::CHANGE_MOVE_AWAY: $doc_status = PhrictionDocumentStatus::STATUS_MOVED; $feed_action = null; break; case PhrictionChangeType::CHANGE_MOVE_HERE: $doc_status = PhrictionDocumentStatus::STATUS_EXISTS; $feed_action = PhrictionActionConstants::ACTION_MOVE_HERE; break; default: throw new Exception("Unsupported content change type '{$change_type}'!"); } $document->setStatus($doc_status); // TODO: This should be transactional. if ($is_new) { $document->save(); } $new_content->setDocumentID($document->getID()); $new_content->save(); $document->setContentID($new_content->getID()); $document->save(); $document->attachContent($new_content); id(new PhabricatorSearchIndexer())->queueDocumentForIndexing($document->getPHID()); // 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); $ancestors = mpull($ancestors, null, 'getSlug'); foreach ($ancestral_slugs as $slug) { // We check for change type to prevent near-infinite recursion if (!isset($ancestors[$slug]) && $new_content->getChangeType() != PhrictionChangeType::CHANGE_STUB) { id(PhrictionDocumentEditor::newForSlug($slug))->setActor($this->getActor())->setTitle(PhabricatorSlug::getDefaultTitle($slug))->setContent('')->setDescription(pht('Empty Parent Document'))->stub(); } } } $project_phid = null; $slug = $document->getSlug(); if (PhrictionDocument::isProjectSlug($slug)) { $project = id(new PhabricatorProjectQuery())->setViewer($this->requireActor())->withPhrictionSlugs(array(PhrictionDocument::getProjectSlugIdentifier($slug)))->executeOne(); if ($project) { $project_phid = $project->getPHID(); } } $related_phids = array($document->getPHID(), $this->getActor()->getPHID()); if ($project_phid) { $related_phids[] = $project_phid; } if ($this->fromDocumentPHID) { $related_phids[] = $this->fromDocumentPHID; } if ($feed_action) { $content_str = id(new PhutilUTF8StringTruncator())->setMaximumGlyphs(140)->truncateString($new_content->getContent()); 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' => $content_str, 'project' => $project_phid, 'movedFromPHID' => $this->fromDocumentPHID))->publish(); } // TODO: Migrate to ApplicationTransactions fast, so we get rid of this code $subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID($document->getPHID()); $this->sendMailToSubscribers($subscribers, $content); return $this; }
protected function willFilterPage(array $documents) { if ($documents) { $ancestor_slugs = array(); foreach ($documents as $key => $document) { $document_slug = $document->getSlug(); foreach (PhabricatorSlug::getAncestry($document_slug) as $ancestor) { $ancestor_slugs[$ancestor][] = $key; } } if ($ancestor_slugs) { $table = new PhrictionDocument(); $conn_r = $table->establishConnection('r'); $ancestors = queryfx_all($conn_r, 'SELECT * FROM %T WHERE slug IN (%Ls)', $document->getTableName(), array_keys($ancestor_slugs)); $ancestors = $table->loadAllFromArray($ancestors); $ancestors = mpull($ancestors, null, 'getSlug'); foreach ($ancestor_slugs as $ancestor_slug => $document_keys) { $ancestor = idx($ancestors, $ancestor_slug); foreach ($document_keys as $document_key) { $documents[$document_key]->attachAncestor($ancestor_slug, $ancestor); } } } } // To view a Phriction document, you must also be able to view all of the // ancestor documents. Filter out documents which have ancestors that are // not visible. $document_map = array(); foreach ($documents as $document) { $document_map[$document->getSlug()] = $document; foreach ($document->getAncestors() as $key => $ancestor) { if ($ancestor) { $document_map[$key] = $ancestor; } } } $filtered_map = $this->applyPolicyFilter($document_map, array(PhabricatorPolicyCapability::CAN_VIEW)); // Filter all of the documents where a parent is not visible. foreach ($documents as $document_key => $document) { // If the document itself is not visible, filter it. if (!isset($filtered_map[$document->getSlug()])) { $this->didRejectResult($documents[$document_key]); unset($documents[$document_key]); continue; } // If an ancestor exists but is not visible, filter the document. foreach ($document->getAncestors() as $ancestor_key => $ancestor) { if (!$ancestor) { continue; } if (!isset($filtered_map[$ancestor_key])) { $this->didRejectResult($documents[$document_key]); unset($documents[$document_key]); break; } } } if (!$documents) { return $documents; } if ($this->needContent) { $contents = id(new PhrictionContent())->loadAllWhere('id IN (%Ld)', mpull($documents, 'getContentID')); foreach ($documents as $key => $document) { $content_id = $document->getContentID(); if (empty($contents[$content_id])) { unset($documents[$key]); continue; } $document->attachContent($contents[$content_id]); } } return $documents; }