diff --git a/resources/sql/autopatches/20150605.diviner.edges.sql b/resources/sql/autopatches/20150605.diviner.edges.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20150605.diviner.edges.sql @@ -0,0 +1,17 @@ +CREATE TABLE {$NAMESPACE}_diviner.edge ( + src VARBINARY(64) NOT NULL, + type INT UNSIGNED NOT NULL, + dst VARBINARY(64) NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + seq INT UNSIGNED NOT NULL, + dataID INT UNSIGNED, + + PRIMARY KEY (src, type, dst), + KEY src (src, type, dateCreated, seq), + UNIQUE KEY key_dst (dst, type, src) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; + +CREATE TABLE {$NAMESPACE}_diviner.edgedata ( + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + data LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20150605.diviner.editPolicy.sql b/resources/sql/autopatches/20150605.diviner.editPolicy.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20150605.diviner.editPolicy.sql @@ -0,0 +1,6 @@ +ALTER TABLE {$NAMESPACE}_diviner.diviner_livebook + ADD COLUMN editPolicy VARBINARY(64) NOT NULL AFTER viewPolicy; + +UPDATE {$NAMESPACE}_diviner.diviner_livebook + SET editPolicy = 'admin' + WHERE editPolicy IS NULL; diff --git a/resources/sql/autopatches/20150605.diviner.xaction.sql b/resources/sql/autopatches/20150605.diviner.xaction.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20150605.diviner.xaction.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_diviner.diviner_livebooktransaction ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + authorPHID VARBINARY(64) NOT NULL, + objectPHID VARBINARY(64) NOT NULL, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + commentPHID VARBINARY(64) DEFAULT NULL, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL, + oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY key_phid (phid), + KEY key_object (objectPHID) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -649,19 +649,25 @@ 'DivinerAtomizeWorkflow' => 'applications/diviner/workflow/DivinerAtomizeWorkflow.php', 'DivinerAtomizer' => 'applications/diviner/atomizer/DivinerAtomizer.php', 'DivinerBookController' => 'applications/diviner/controller/DivinerBookController.php', + 'DivinerBookEditController' => 'applications/diviner/controller/DivinerBookEditController.php', 'DivinerBookItemView' => 'applications/diviner/view/DivinerBookItemView.php', 'DivinerBookPHIDType' => 'applications/diviner/phid/DivinerBookPHIDType.php', 'DivinerBookQuery' => 'applications/diviner/query/DivinerBookQuery.php', 'DivinerBookSearchIndexer' => 'applications/diviner/search/DivinerBookSearchIndexer.php', 'DivinerController' => 'applications/diviner/controller/DivinerController.php', 'DivinerDAO' => 'applications/diviner/storage/DivinerDAO.php', + 'DivinerDefaultEditCapability' => 'applications/diviner/capability/DivinerDefaultEditCapability.php', 'DivinerDefaultRenderer' => 'applications/diviner/renderer/DivinerDefaultRenderer.php', + 'DivinerDefaultViewCapability' => 'applications/diviner/capability/DivinerDefaultViewCapability.php', 'DivinerDiskCache' => 'applications/diviner/cache/DivinerDiskCache.php', 'DivinerFileAtomizer' => 'applications/diviner/atomizer/DivinerFileAtomizer.php', 'DivinerFindController' => 'applications/diviner/controller/DivinerFindController.php', 'DivinerGenerateWorkflow' => 'applications/diviner/workflow/DivinerGenerateWorkflow.php', 'DivinerLiveAtom' => 'applications/diviner/storage/DivinerLiveAtom.php', 'DivinerLiveBook' => 'applications/diviner/storage/DivinerLiveBook.php', + 'DivinerLiveBookEditor' => 'applications/diviner/editor/DivinerLiveBookEditor.php', + 'DivinerLiveBookTransaction' => 'applications/diviner/storage/DivinerLiveBookTransaction.php', + 'DivinerLiveBookTransactionQuery' => 'applications/diviner/query/DivinerLiveBookTransactionQuery.php', 'DivinerLivePublisher' => 'applications/diviner/publisher/DivinerLivePublisher.php', 'DivinerLiveSymbol' => 'applications/diviner/storage/DivinerLiveSymbol.php', 'DivinerMainController' => 'applications/diviner/controller/DivinerMainController.php', @@ -671,6 +677,7 @@ 'DivinerPublisher' => 'applications/diviner/publisher/DivinerPublisher.php', 'DivinerRenderer' => 'applications/diviner/renderer/DivinerRenderer.php', 'DivinerReturnTableView' => 'applications/diviner/view/DivinerReturnTableView.php', + 'DivinerSchemaSpec' => 'applications/diviner/storage/DivinerSchemaSpec.php', 'DivinerSectionView' => 'applications/diviner/view/DivinerSectionView.php', 'DivinerStaticPublisher' => 'applications/diviner/publisher/DivinerStaticPublisher.php', 'DivinerSymbolRemarkupRule' => 'applications/diviner/markup/DivinerSymbolRemarkupRule.php', @@ -4009,13 +4016,16 @@ 'DivinerAtomizeWorkflow' => 'DivinerWorkflow', 'DivinerAtomizer' => 'Phobject', 'DivinerBookController' => 'DivinerController', + 'DivinerBookEditController' => 'DivinerController', 'DivinerBookItemView' => 'AphrontTagView', 'DivinerBookPHIDType' => 'PhabricatorPHIDType', 'DivinerBookQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'DivinerBookSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'DivinerController' => 'PhabricatorController', 'DivinerDAO' => 'PhabricatorLiskDAO', + 'DivinerDefaultEditCapability' => 'PhabricatorPolicyCapability', 'DivinerDefaultRenderer' => 'DivinerRenderer', + 'DivinerDefaultViewCapability' => 'PhabricatorPolicyCapability', 'DivinerDiskCache' => 'Phobject', 'DivinerFileAtomizer' => 'DivinerAtomizer', 'DivinerFindController' => 'DivinerController', @@ -4024,8 +4034,13 @@ 'DivinerLiveBook' => array( 'DivinerDAO', 'PhabricatorPolicyInterface', + 'PhabricatorProjectInterface', 'PhabricatorDestructibleInterface', + 'PhabricatorApplicationTransactionInterface', ), + 'DivinerLiveBookEditor' => 'PhabricatorApplicationTransactionEditor', + 'DivinerLiveBookTransaction' => 'PhabricatorApplicationTransaction', + 'DivinerLiveBookTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'DivinerLivePublisher' => 'DivinerPublisher', 'DivinerLiveSymbol' => array( 'DivinerDAO', @@ -4040,6 +4055,7 @@ 'DivinerPublisher' => 'Phobject', 'DivinerRenderer' => 'Phobject', 'DivinerReturnTableView' => 'AphrontTagView', + 'DivinerSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'DivinerSectionView' => 'AphrontTagView', 'DivinerStaticPublisher' => 'DivinerPublisher', 'DivinerSymbolRemarkupRule' => 'PhutilRemarkupRule', diff --git a/src/applications/diviner/application/PhabricatorDivinerApplication.php b/src/applications/diviner/application/PhabricatorDivinerApplication.php --- a/src/applications/diviner/application/PhabricatorDivinerApplication.php +++ b/src/applications/diviner/application/PhabricatorDivinerApplication.php @@ -39,6 +39,7 @@ 'find/' => 'DivinerFindController', ), '/book/(?P[^/]+)/' => 'DivinerBookController', + '/book/(?P[^/]+)/edit/' => 'DivinerBookEditController', '/book/'. '(?P[^/]+)/'. '(?P[^/]+)/'. @@ -52,6 +53,15 @@ return self::GROUP_UTILITIES; } + protected function getCustomCapabilities() { + return array( + DivinerDefaultViewCapability::CAPABILITY => array(), + DivinerDefaultEditCapability::CAPABILITY => array( + 'default' => PhabricatorPolicies::POLICY_ADMIN, + ), + ); + } + public function getRemarkupRules() { return array( new DivinerSymbolRemarkupRule(), diff --git a/src/applications/diviner/capability/DivinerDefaultEditCapability.php b/src/applications/diviner/capability/DivinerDefaultEditCapability.php new file mode 100644 --- /dev/null +++ b/src/applications/diviner/capability/DivinerDefaultEditCapability.php @@ -0,0 +1,11 @@ +bookName = $data['book']; - $this->atomType = $data['type']; - $this->atomName = $data['name']; - $this->atomContext = nonempty(idx($data, 'context'), null); - $this->atomIndex = nonempty(idx($data, 'index'), null); - } - - public function processRequest() { - $request = $this->getRequest(); + public function handleRequest(AphrontRequest $request) { $viewer = $request->getUser(); require_celerity_resource('diviner-shared-css'); $book = id(new DivinerBookQuery()) ->setViewer($viewer) - ->withNames(array($this->bookName)) + ->withNames(array($request->getURIData('book'))) ->executeOne(); if (!$book) { @@ -38,10 +23,10 @@ $symbol = id(new DivinerAtomQuery()) ->setViewer($viewer) ->withBookPHIDs(array($book->getPHID())) - ->withTypes(array($this->atomType)) - ->withNames(array($this->atomName)) - ->withContexts(array($this->atomContext)) - ->withIndexes(array($this->atomIndex)) + ->withTypes(array($request->getURIData('type'))) + ->withNames(array($request->getURIData('name'))) + ->withContexts(array($request->getURIData('context'))) + ->withIndexes(array($request->getURIData('index'))) ->withIsDocumentable(true) ->needAtoms(true) ->needExtends(true) @@ -75,7 +60,7 @@ ->setName(DivinerAtom::getAtomTypeNameString( $atom ? $atom->getType() : $symbol->getType()))); - $properties = id(new PHUIPropertyListView()); + $properties = new PHUIPropertyListView(); $group = $atom ? $atom->getProperty('group') : $symbol->getGroupName(); if ($group) { @@ -134,9 +119,7 @@ $document->appendChild( id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) - ->appendChild( - pht( - 'This atom no longer exists.'))); + ->appendChild(pht('This atom no longer exists.'))); } if ($atom) { @@ -154,8 +137,8 @@ foreach ($methods_by_task as $task => $ignored) { if (empty($tasks[$task])) { $tasks[$task] = array( - 'name' => $task, - 'title' => $task ? $task : pht('Other Methods'), + 'name' => $task, + 'title' => $task ? $task : pht('Other Methods'), 'defined' => $symbol, ); } @@ -304,7 +287,6 @@ pht('Implements'), phutil_implode_html(phutil_tag('br'), $items)); } - } private function renderAtomTag(DivinerLiveSymbol $symbol) { @@ -333,7 +315,7 @@ if ($extends->getType() == 'interface') { $implements[$extends->getName()] = array( 'interface' => $extends, - 'via' => $symbol, + 'via' => $symbol, ); } } @@ -421,8 +403,8 @@ $child_specs[$name]['implementations'][] = $symbol; } else { $child_specs[$name] = array( - 'atoms' => array($child), - 'defined' => $symbol, + 'atoms' => array($child), + 'defined' => $symbol, 'implementations' => array($symbol), ); } @@ -450,9 +432,9 @@ $title = trim($title); $task_specs[$name] = array( - 'name' => $name, - 'title' => $title, - 'defined' => $symbol, + 'name' => $name, + 'title' => $title, + 'defined' => $symbol, ); } } @@ -471,6 +453,7 @@ private function renderFullSignature( DivinerLiveSymbol $symbol, $is_link = false) { + switch ($symbol->getType()) { case DivinerAtom::TYPE_CLASS: case DivinerAtom::TYPE_INTERFACE: @@ -661,7 +644,7 @@ $doc = $this->renderDocumentationText($symbol, $engine); - if (($impl !== $parent) || $out) { + if ($impl !== $parent || $out) { $where = id(new PHUIBoxView()) ->addPadding(PHUI::PADDING_MEDIUM_LEFT) ->addPadding(PHUI::PADDING_MEDIUM_RIGHT) diff --git a/src/applications/diviner/controller/DivinerAtomListController.php b/src/applications/diviner/controller/DivinerAtomListController.php --- a/src/applications/diviner/controller/DivinerAtomListController.php +++ b/src/applications/diviner/controller/DivinerAtomListController.php @@ -2,20 +2,13 @@ final class DivinerAtomListController extends DivinerController { - private $key; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->key = idx($data, 'key', 'all'); - } - - public function processRequest() { - $request = $this->getRequest(); + public function handleRequest(AphrontRequest $request) { $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($this->key) + ->setQueryKey($request->getURIData('key', 'all')) ->setSearchEngine(new DivinerAtomSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/diviner/controller/DivinerBookController.php b/src/applications/diviner/controller/DivinerBookController.php --- a/src/applications/diviner/controller/DivinerBookController.php +++ b/src/applications/diviner/controller/DivinerBookController.php @@ -2,41 +2,44 @@ final class DivinerBookController extends DivinerController { - private $bookName; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->bookName = $data['book']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); $book = id(new DivinerBookQuery()) ->setViewer($viewer) - ->withNames(array($this->bookName)) + ->withNames(array($request->getURIData('book'))) ->executeOne(); if (!$book) { return new Aphront404Response(); } + $actions = $this->buildActionView($viewer, $book); + $crumbs = $this->buildApplicationCrumbs(); $crumbs->setBorder(true); - $crumbs->addTextCrumb( $book->getShortTitle(), '/book/'.$book->getName().'/'); + $action_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Actions')) + ->setHref('#') + ->setIconFont('fa-bars') + ->addClass('phui-mobile-menu') + ->setDropdownMenu($actions); + $header = id(new PHUIHeaderView()) ->setHeader($book->getTitle()) ->setUser($viewer) ->setPolicyObject($book) - ->setEpoch($book->getDateModified()); + ->setEpoch($book->getDateModified()) + ->addActionLink($action_button); $document = new PHUIDocumentView(); $document->setHeader($header); @@ -100,4 +103,28 @@ )); } + private function buildActionView( + PhabricatorUser $user, + DivinerLiveBook $book) { + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $user, + $book, + PhabricatorPolicyCapability::CAN_EDIT); + + $action_view = id(new PhabricatorActionListView()) + ->setUser($user) + ->setObjectURI($this->getRequest()->getRequestURI()) + ->setObject($book); + + $action_view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Book')) + ->setIcon('fa-pencil') + ->setHref('/book/'.$book->getName().'/edit/') + ->setDisabled(!$can_edit)); + + return $action_view; + } + } diff --git a/src/applications/diviner/controller/DivinerBookEditController.php b/src/applications/diviner/controller/DivinerBookEditController.php new file mode 100644 --- /dev/null +++ b/src/applications/diviner/controller/DivinerBookEditController.php @@ -0,0 +1,117 @@ +getViewer(); + + $book = id(new DivinerBookQuery()) + ->setViewer($viewer) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->needProjectPHIDs(true) + ->withNames(array($request->getURIData('book'))) + ->executeOne(); + + $view_uri = '/book/'.$book->getName(); + $edit_uri = '/book/'.$book->getName().'/edit/'; + + if (!$book) { + return new Aphront404Response(); + } + + if ($request->isFormPost()) { + $v_projects = $request->getArr('projectPHIDs'); + $v_view = $request->getStr('viewPolicy'); + $v_edit = $request->getStr('editPolicy'); + + $xactions = array(); + $xactions[] = id(new DivinerLiveBookTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue( + 'edge:type', + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST) + ->setNewValue( + array( + '=' => array_fuse($v_projects), + )); + $xactions[] = id(new DivinerLiveBookTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) + ->setNewValue($v_view); + $xactions[] = id(new DivinerLiveBookTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) + ->setNewValue($v_edit); + + id(new DivinerLiveBookEditor()) + ->setContinueOnNoEffect(true) + ->setContentSourceFromRequest($request) + ->setActor($viewer) + ->applyTransactions($book, $xactions); + + return id(new AphrontRedirectResponse())->setURI($view_uri); + } + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Edit Basics')); + + $title = pht('Edit %s', $book->getTitle()); + + $policies = id(new PhabricatorPolicyQuery()) + ->setViewer($viewer) + ->setObject($book) + ->execute(); + $view_capability = PhabricatorPolicyCapability::CAN_VIEW; + $edit_capability = PhabricatorPolicyCapability::CAN_EDIT; + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setDatasource(new PhabricatorProjectDatasource()) + ->setName('projectPHIDs') + ->setLabel(pht('Projects')) + ->setValue($book->getProjectPHIDs())) + ->appendChild( + id(new AphrontFormPolicyControl()) + ->setName('viewPolicy') + ->setPolicyObject($book) + ->setCapability($view_capability) + ->setPolicies($policies) + ->setCaption($book->describeAutomaticCapability($view_capability))) + ->appendChild( + id(new AphrontFormPolicyControl()) + ->setName('editPolicy') + ->setPolicyObject($book) + ->setCapability($edit_capability) + ->setPolicies($policies) + ->setCaption($book->describeAutomaticCapability($edit_capability))) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue(pht('Save')) + ->addCancelButton($view_uri)) + ->appendChild(id(new PHUIFormDividerControl())); + + $object_box = id(new PHUIObjectBoxView()) + ->setHeaderText($title) + ->setForm($form); + + $timeline = $this->buildTransactionTimeline( + $book, + new DivinerLiveBookTransactionQuery()); + $timeline->setShouldTerminate(true); + + return $this->buildApplicationPage( + array( + $crumbs, + $object_box, + $timeline, + ), + array( + 'title' => $title, + )); + } + +} diff --git a/src/applications/diviner/controller/DivinerController.php b/src/applications/diviner/controller/DivinerController.php --- a/src/applications/diviner/controller/DivinerController.php +++ b/src/applications/diviner/controller/DivinerController.php @@ -3,19 +3,15 @@ abstract class DivinerController extends PhabricatorController { protected function buildSideNavView() { - $menu = $this->buildMenu(); + $menu = $this->buildApplicationMenu(); return AphrontSideNavFilterView::newFromMenu($menu); } public function buildApplicationMenu() { - return $this->buildMenu(); - } - - private function buildMenu() { $menu = new PHUIListView(); id(new DivinerAtomSearchEngine()) - ->setViewer($this->getRequest()->getUser()) + ->setViewer($this->getRequest()->getViewer()) ->addNavigationItems($menu); return $menu; @@ -24,12 +20,8 @@ protected function renderAtomList(array $symbols) { assert_instances_of($symbols, 'DivinerLiveSymbol'); - $request = $this->getRequest(); - $user = $request->getUser(); - $list = array(); foreach ($symbols as $symbol) { - switch ($symbol->getType()) { case DivinerAtom::TYPE_FUNCTION: $title = $symbol->getTitle().'()'; @@ -43,8 +35,7 @@ ->setTitle($title) ->setHref($symbol->getURI()) ->setSubtitle($symbol->getSummary()) - ->setType(DivinerAtom::getAtomTypeNameString( - $symbol->getType())); + ->setType(DivinerAtom::getAtomTypeNameString($symbol->getType())); $list[] = $item; } diff --git a/src/applications/diviner/controller/DivinerFindController.php b/src/applications/diviner/controller/DivinerFindController.php --- a/src/applications/diviner/controller/DivinerFindController.php +++ b/src/applications/diviner/controller/DivinerFindController.php @@ -6,12 +6,11 @@ return true; } - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); - $book_name = $request->getStr('book'); - $query_text = $request->getStr('name'); + $book_name = $request->getURIData('book'); + $query_text = $request->getURIData('name'); $book = null; if ($book_name) { @@ -19,6 +18,7 @@ ->setViewer($viewer) ->withNames(array($book_name)) ->executeOne(); + if (!$book) { return new Aphront404Response(); } @@ -31,12 +31,12 @@ $query->withBookPHIDs(array($book->getPHID())); } - $context = $request->getStr('context'); + $context = $request->getURIData('context'); if (strlen($context)) { $query->withContexts(array($context)); } - $type = $request->getStr('type'); + $type = $request->getURIData('type'); if (strlen($type)) { $query->withTypes(array($type)); } @@ -70,8 +70,8 @@ ->setTitle(pht('Documentation Not Found')) ->appendChild( pht( - 'Unable to find the specified documentation. You may have '. - 'followed a bad or outdated link.')) + 'Unable to find the specified documentation. '. + 'You may have followed a bad or outdated link.')) ->addCancelButton($not_found_uri, pht('Read More Documentation')); return id(new AphrontDialogResponse())->setDialog($dialog); diff --git a/src/applications/diviner/controller/DivinerMainController.php b/src/applications/diviner/controller/DivinerMainController.php --- a/src/applications/diviner/controller/DivinerMainController.php +++ b/src/applications/diviner/controller/DivinerMainController.php @@ -6,9 +6,8 @@ return true; } - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); $books = id(new DivinerBookQuery()) ->setViewer($viewer) @@ -31,10 +30,10 @@ ->setHeader(pht('Documentation Books')) ->addActionLink($query_button); - $document = new PHUIDocumentView(); - $document->setHeader($header); - $document->setFontKit(PHUIDocumentView::FONT_SOURCE_SANS); - $document->addClass('diviner-view'); + $document = id(new PHUIDocumentView()) + ->setHeader($header) + ->setFontKit(PHUIDocumentView::FONT_SOURCE_SANS) + ->addClass('diviner-view'); if ($books) { $books = msort($books, 'getTitle'); @@ -54,24 +53,20 @@ ->appendChild($list); $document->appendChild($list); - } else { $text = pht( - "(NOTE) **Looking for Phabricator documentation?** If you're looking ". - "for help and information about Phabricator, you can ". - "[[ https://secure.phabricator.com/diviner/ | browse the public ". - "Phabricator documentation ]] on the live site.\n\n". - "Diviner is the documentation generator used to build the Phabricator ". - "documentation.\n\n". + "(NOTE) **Looking for Phabricator documentation?** ". + "If you're looking for help and information about Phabricator, ". + "you can [[https://secure.phabricator.com/diviner/ | ". + "browse the public Phabricator documentation]] on the live site.\n\n". + "Diviner is the documentation generator used to build the ". + "Phabricator documentation.\n\n". "You haven't generated any Diviner documentation books yet, so ". "there's nothing to show here. If you'd like to generate your own ". "local copy of the Phabricator documentation and have it appear ". "here, run this command:\n\n". - " phabricator/ $ ./bin/diviner generate\n\n". - "Right now, Diviner isn't very useful for generating documentation ". - "for projects other than Phabricator. If you're interested in using ". - "it in your own projects, leave feedback for us on ". - "[[ https://secure.phabricator.com/T4558 | T4558 ]]."); + " %s\n\n", + 'phabricator/ $ ./bin/diviner generate'); $text = PhabricatorMarkupEngine::renderOneObject( id(new PhabricatorMarkupOneOff())->setContent($text), diff --git a/src/applications/diviner/editor/DivinerLiveBookEditor.php b/src/applications/diviner/editor/DivinerLiveBookEditor.php new file mode 100644 --- /dev/null +++ b/src/applications/diviner/editor/DivinerLiveBookEditor.php @@ -0,0 +1,23 @@ + $handle) { $book = $objects[$phid]; - $name = $book->getName(); - $handle ->setName($book->getShortTitle()) ->setFullName($book->getTitle()) - ->setURI("/book/{$name}/"); + ->setURI('/book/'.$book->getName().'/'); } } diff --git a/src/applications/diviner/publisher/DivinerLivePublisher.php b/src/applications/diviner/publisher/DivinerLivePublisher.php --- a/src/applications/diviner/publisher/DivinerLivePublisher.php +++ b/src/applications/diviner/publisher/DivinerLivePublisher.php @@ -13,6 +13,7 @@ $book = id(new DivinerLiveBook()) ->setName($book_name) ->setViewPolicy(PhabricatorPolicies::getMostOpenPolicy()) + ->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN) ->save(); } diff --git a/src/applications/diviner/query/DivinerBookQuery.php b/src/applications/diviner/query/DivinerBookQuery.php --- a/src/applications/diviner/query/DivinerBookQuery.php +++ b/src/applications/diviner/query/DivinerBookQuery.php @@ -6,6 +6,8 @@ private $phids; private $names; + private $needProjectPHIDs; + public function withIDs(array $ids) { $this->ids = $ids; return $this; @@ -21,6 +23,11 @@ return $this; } + public function needProjectPHIDs($need_phids) { + $this->needProjectPHIDs = $need_phids; + return $this; + } + protected function loadPage() { $table = new DivinerLiveBook(); $conn_r = $table->establishConnection('r'); @@ -36,6 +43,28 @@ return $table->loadAllFromArray($data); } + protected function didFilterPage(array $books) { + if ($this->needProjectPHIDs) { + $edge_query = id(new PhabricatorEdgeQuery()) + ->withSourcePHIDs(mpull($books, 'getPHID')) + ->withEdgeTypes( + array( + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, + )); + $edge_query->execute(); + + foreach ($books as $book) { + $project_phids = $edge_query->getDestinationPHIDs( + array( + $book->getPHID(), + )); + $book->attachProjectPHIDs($project_phids); + } + } + + return $books; + } + protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); diff --git a/src/applications/diviner/query/DivinerLiveBookTransactionQuery.php b/src/applications/diviner/query/DivinerLiveBookTransactionQuery.php new file mode 100644 --- /dev/null +++ b/src/applications/diviner/query/DivinerLiveBookTransactionQuery.php @@ -0,0 +1,10 @@ +getBook() != $this->getConfig('name')) { - // If the ref is from a different book, we can't normalize it. Just return - // it as-is if it has enough information to resolve. + // If the ref is from a different book, we can't normalize it. + // Just return it as-is if it has enough information to resolve. if ($ref->getName() && $ref->getType()) { return $ref; } else { @@ -260,5 +260,4 @@ $ref->getTitle()); } - } diff --git a/src/applications/diviner/search/DivinerBookSearchIndexer.php b/src/applications/diviner/search/DivinerBookSearchIndexer.php --- a/src/applications/diviner/search/DivinerBookSearchIndexer.php +++ b/src/applications/diviner/search/DivinerBookSearchIndexer.php @@ -18,6 +18,11 @@ PhabricatorSearchDocumentFieldType::FIELD_BODY, $book->getPreface()); + $this->indexTransactions( + $doc, + new DivinerLiveBookTransactionQuery(), + array($phid)); + return $doc; } diff --git a/src/applications/diviner/storage/DivinerLiveBook.php b/src/applications/diviner/storage/DivinerLiveBook.php --- a/src/applications/diviner/storage/DivinerLiveBook.php +++ b/src/applications/diviner/storage/DivinerLiveBook.php @@ -3,12 +3,17 @@ final class DivinerLiveBook extends DivinerDAO implements PhabricatorPolicyInterface, - PhabricatorDestructibleInterface { + PhabricatorProjectInterface, + PhabricatorDestructibleInterface, + PhabricatorApplicationTransactionInterface { protected $name; protected $viewPolicy; + protected $editPolicy; protected $configurationData = array(); + private $projectPHIDs = self::ATTACHABLE; + protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, @@ -63,20 +68,36 @@ return idx($spec, 'name', $group); } + public function attachProjectPHIDs(array $project_phids) { + $this->projectPHIDs = $project_phids; + return $this; + } + + public function getProjectPHIDs() { + return $this->assertAttached($this->projectPHIDs); + } + + /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { - return PhabricatorPolicies::getMostOpenPolicy(); + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return $this->getViewPolicy(); + case PhabricatorPolicyCapability::CAN_EDIT: + return $this->getEditPolicy(); + } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { - return false; + return false; } public function describeAutomaticCapability($capability) { @@ -102,4 +123,27 @@ $this->saveTransaction(); } + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new DivinerLiveBookEditor(); + } + + public function getApplicationTransactionObject() { + return $this; + } + + public function getApplicationTransactionTemplate() { + return new DivinerLiveBookTransaction(); + } + + public function willRenderTimeline( + PhabricatorApplicationTransactionView $timeline, + AphrontRequest $request) { + + return $timeline; + } + } diff --git a/src/applications/diviner/storage/DivinerLiveBookTransaction.php b/src/applications/diviner/storage/DivinerLiveBookTransaction.php new file mode 100644 --- /dev/null +++ b/src/applications/diviner/storage/DivinerLiveBookTransaction.php @@ -0,0 +1,18 @@ +buildEdgeSchemata(new DivinerLiveBook()); + } + +} diff --git a/src/applications/diviner/view/DivinerBookItemView.php b/src/applications/diviner/view/DivinerBookItemView.php --- a/src/applications/diviner/view/DivinerBookItemView.php +++ b/src/applications/diviner/view/DivinerBookItemView.php @@ -43,23 +43,23 @@ $title = phutil_tag( 'span', - array( - 'class' => 'diviner-book-item-title', - ), + array( + 'class' => 'diviner-book-item-title', + ), $this->title); $subtitle = phutil_tag( 'span', - array( - 'class' => 'diviner-book-item-subtitle', - ), + array( + 'class' => 'diviner-book-item-subtitle', + ), $this->subtitle); $type = phutil_tag( 'span', - array( - 'class' => 'diviner-book-item-type', - ), + array( + 'class' => 'diviner-book-item-type', + ), $this->type); return array($title, $type, $subtitle); diff --git a/src/applications/transactions/constants/PhabricatorTransactions.php b/src/applications/transactions/constants/PhabricatorTransactions.php --- a/src/applications/transactions/constants/PhabricatorTransactions.php +++ b/src/applications/transactions/constants/PhabricatorTransactions.php @@ -12,7 +12,7 @@ const TYPE_BUILDABLE = 'harbormaster:buildable'; const TYPE_TOKEN = 'token:give'; const TYPE_INLINESTATE = 'core:inlinestate'; - const TYPE_SPACE = 'core:space'; + const TYPE_SPACE = 'core:space'; const COLOR_RED = 'red'; const COLOR_ORANGE = 'orange';