diff --git a/resources/sql/autopatches/20150601.divineredge.sql b/resources/sql/autopatches/20150601.divineredge.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20150601.divineredge.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/20150601.divineredit.sql b/resources/sql/autopatches/20150601.divineredit.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20150601.divineredit.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/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -647,12 +647,17 @@ 'DivinerAtomizeWorkflow' => 'applications/diviner/workflow/DivinerAtomizeWorkflow.php', 'DivinerAtomizer' => 'applications/diviner/atomizer/DivinerAtomizer.php', 'DivinerBookController' => 'applications/diviner/controller/DivinerBookController.php', + 'DivinerBookEditController' => 'applications/diviner/storage/DivinerBookEditController.php', + 'DivinerBookEditor' => 'applications/diviner/editor/DivinerBookEditor.php', 'DivinerBookItemView' => 'applications/diviner/view/DivinerBookItemView.php', 'DivinerBookPHIDType' => 'applications/diviner/phid/DivinerBookPHIDType.php', 'DivinerBookQuery' => 'applications/diviner/query/DivinerBookQuery.php', + 'DivinerBookTransaction' => 'applications/diviner/storage/DivinerBookTransaction.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', @@ -3877,12 +3882,17 @@ 'DivinerAtomSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DivinerAtomizeWorkflow' => 'DivinerWorkflow', 'DivinerBookController' => 'DivinerController', + 'DivinerBookEditController' => 'DivinerController', + 'DivinerBookEditor' => 'PhabricatorApplicationTransactionEditor', 'DivinerBookItemView' => 'AphrontTagView', 'DivinerBookPHIDType' => 'PhabricatorPHIDType', 'DivinerBookQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'DivinerBookTransaction' => 'PhabricatorApplicationTransaction', 'DivinerController' => 'PhabricatorController', 'DivinerDAO' => 'PhabricatorLiskDAO', + 'DivinerDefaultEditCapability' => 'PhabricatorPolicyCapability', 'DivinerDefaultRenderer' => 'DivinerRenderer', + 'DivinerDefaultViewCapability' => 'PhabricatorPolicyCapability', 'DivinerFileAtomizer' => 'DivinerAtomizer', 'DivinerFindController' => 'DivinerController', 'DivinerGenerateWorkflow' => 'DivinerWorkflow', @@ -3890,6 +3900,7 @@ 'DivinerLiveBook' => array( 'DivinerDAO', 'PhabricatorPolicyInterface', + 'PhabricatorProjectInterface', 'PhabricatorDestructibleInterface', ), 'DivinerLivePublisher' => 'DivinerPublisher', diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -17,7 +17,7 @@ $crumbs = $this->buildCrumbs(); $content[] = $crumbs; - $content[] = $this->buildPropertiesTable($drequest->getRepository()); + $content[] = $this->buildPropertiesTable($repository); // Before we do any work, make sure we're looking at a some content: we're // on a valid branch, and the repository is not empty. @@ -76,7 +76,7 @@ return $this->buildApplicationPage( $content, array( - 'title' => $drequest->getRepository()->getName(), + 'title' => $repository->getName(), )); } @@ -552,7 +552,7 @@ } $history_table->setIsHead(true); - $callsign = $drequest->getRepository()->getCallsign(); + $callsign = $repository->getCallsign(); $icon = id(new PHUIIconView()) ->setIconFont('fa-list-alt'); 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 @@ +getRequest(); - $viewer = $request->getUser(); + $viewer = $request->getUser(); $book = id(new DivinerBookQuery()) ->setViewer($viewer) @@ -31,6 +32,9 @@ $crumbs->addTextCrumb( $book->getShortTitle(), '/book/'.$book->getName().'/'); + $content[] = $crumbs; + + $content[] = $this->buildPropertiesTable($book); $header = id(new PHUIHeaderView()) ->setHeader($book->getTitle()) @@ -50,6 +54,7 @@ $document->setHeader($header); $document->addClass('diviner-view'); $document->setFontKit(PHUIDocumentView::FONT_SOURCE_SANS); + $content[] = $document; $atoms = id(new DivinerAtomQuery()) ->setViewer($viewer) @@ -97,13 +102,70 @@ $document->appendChild($out); return $this->buildApplicationPage( - array( - $crumbs, - $document, - ), + $content, array( 'title' => $book->getTitle(), )); } + private function buildPropertiesTable(DivinerLiveBook $book) { + $user = $this->getRequest()->getUser(); + + $header = id(new PHUIHeaderView()) + ->setHeader($book->getName()) + ->setUser($user) + ->setPolicyObject($book); + + $header->setStatus('fa-check', 'bluegrey', pht('Active')); + + $actions = $this->buildActionList($book); + + $view = id(new PHUIPropertyListView()) + ->setUser($user); + + $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( + $book->getPHID(), + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); + if ($project_phids) { + $view->addProperty( + pht('Projects'), + $user->renderHandleList($project_phids)); + } + + $view->setActionList($actions); + + $box = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->addPropertyList($view); + + return $box; + } + + private function buildActionList(DivinerLiveBook $book) { + $viewer = $this->getRequest()->getUser(); + + $view_uri = '/book/'.$book->getName().'/'; + $edit_uri = '/book/'.$book->getName().'/edit/'; + + $view = id(new PhabricatorActionListView()) + ->setUser($viewer) + ->setObject($book) + ->setObjectURI($view_uri); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $book, + PhabricatorPolicyCapability::CAN_EDIT); + + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Book')) + ->setIcon('fa-pencil') + ->setHref($edit_uri) + ->setWorkflow(!$can_edit) + ->setDisabled(!$can_edit)); + + return $view; + } + } diff --git a/src/applications/diviner/editor/DivinerBookEditor.php b/src/applications/diviner/editor/DivinerBookEditor.php new file mode 100644 --- /dev/null +++ b/src/applications/diviner/editor/DivinerBookEditor.php @@ -0,0 +1,22 @@ +ids = $ids; return $this; @@ -27,6 +29,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'); @@ -70,6 +77,27 @@ return $books; } + protected function didFilterPage(array $books) { + if ($this->needProjectPHIDs) { + $type_project = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; + + $edge_query = id(new PhabricatorEdgeQuery()) + ->withSourcePHIDs(mpull($books, 'getPHID')) + ->withEdgeTypes(array($type_project)); + $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/storage/DivinerBookEditController.php b/src/applications/diviner/storage/DivinerBookEditController.php new file mode 100644 --- /dev/null +++ b/src/applications/diviner/storage/DivinerBookEditController.php @@ -0,0 +1,98 @@ +bookName = $data['book']; + } + + protected function processRequest() { + $request = $this->getRequest(); + $user = $request->getUser(); + + $book = id(new DivinerBookQuery()) + ->setViewer($user) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->needProjectPHIDs(true) + ->withNames(array($this->bookName)) + ->executeOne(); + + $edit_uri = '/book/'.$book->getName().'/edit/'; + + if (!$book) { + return new Aphront404Response(); + } + + $v_title = $book->getTitle(); + $v_preface = $book->getPreface(); + $e_name = true; + $errors = array(); + + if ($request->isFormPost()) { + $v_projects = $request->getArr('projectPHIDs'); + + $xactions = array(); + $template = id(new DivinerBookTransaction()); + + $type_edge = PhabricatorTransactions::TYPE_EDGE; + + $xactions[] = id(clone $template) + ->setTransactionType($type_edge) + ->setMetadataValue( + 'edge:type', + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST) + ->setNewValue( + array( + '=' => array_fuse($v_projects), + )); + + id(new DivinerBookEditor()) + ->setContinueOnNoEffect(true) + ->setContentSourceFromRequest($request) + ->setActor($user) + ->applyTransactions($book, $xactions); + + return id(new AphrontRedirectResponse())->setURI($edit_uri); + } + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Edit Basics')); + + $title = pht('Edit %s', $book->getTitle()); + + $form = id(new AphrontFormView()) + ->setUser($user) + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setDatasource(new PhabricatorProjectDatasource()) + ->setName('projectPHIDs') + ->setLabel(pht('Projects')) + ->setValue($book->getProjectPHIDs())) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue(pht('Save')) + ->addCancelButton($edit_uri)) + ->appendChild(id(new PHUIFormDividerControl())); + + $object_box = id(new PHUIObjectBoxView()) + ->setHeaderText($title) + ->setForm($form) + ->setFormErrors($errors); + + return $this->buildApplicationPage( + array( + $crumbs, + $object_box, + ), + array( + 'title' => $title, + )); + } + +} diff --git a/src/applications/diviner/storage/DivinerBookTransaction.php b/src/applications/diviner/storage/DivinerBookTransaction.php new file mode 100644 --- /dev/null +++ b/src/applications/diviner/storage/DivinerBookTransaction.php @@ -0,0 +1,17 @@ +assertAttached($this->repository); + public function attachProjectPHIDs(array $project_phids) { + $this->projectPHIDs = $project_phids; + return $this; + } + + public function getProjectPHIDs() { + return $this->assertAttached($this->projectPHIDs); } public function attachRepository(PhabricatorRepository $repository) { @@ -77,16 +85,27 @@ return $this; } + public function getRepository() { + return $this->assertAttached($this->repository); + } + + /* -( 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 $user) {