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 @@ -646,19 +646,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', @@ -668,6 +674,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', @@ -3890,13 +3897,16 @@ 'DivinerAtomSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'DivinerAtomizeWorkflow' => 'DivinerWorkflow', 'DivinerBookController' => 'DivinerController', + 'DivinerBookEditController' => 'DivinerController', 'DivinerBookItemView' => 'AphrontTagView', 'DivinerBookPHIDType' => 'PhabricatorPHIDType', 'DivinerBookQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'DivinerBookSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'DivinerController' => 'PhabricatorController', 'DivinerDAO' => 'PhabricatorLiskDAO', + 'DivinerDefaultEditCapability' => 'PhabricatorPolicyCapability', 'DivinerDefaultRenderer' => 'DivinerRenderer', + 'DivinerDefaultViewCapability' => 'PhabricatorPolicyCapability', 'DivinerFileAtomizer' => 'DivinerAtomizer', 'DivinerFindController' => 'DivinerController', 'DivinerGenerateWorkflow' => 'DivinerWorkflow', @@ -3904,8 +3914,12 @@ 'DivinerLiveBook' => array( 'DivinerDAO', 'PhabricatorPolicyInterface', + 'PhabricatorProjectInterface', 'PhabricatorDestructibleInterface', ), + 'DivinerLiveBookEditor' => 'PhabricatorApplicationTransactionEditor', + 'DivinerLiveBookTransaction' => 'PhabricatorApplicationTransaction', + 'DivinerLiveBookTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'DivinerLivePublisher' => 'DivinerPublisher', 'DivinerLiveSymbol' => array( 'DivinerDAO', @@ -3918,6 +3932,7 @@ 'DivinerParameterTableView' => 'AphrontTagView', 'DivinerPublishCache' => 'DivinerDiskCache', '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']; + } + + 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(); + + $view_uri = '/book/'.$book->getName(); + $edit_uri = '/book/'.$book->getName().'/edit/'; + + if (!$book) { + return new Aphront404Response(); + } + + if ($request->isFormPost()) { + $v_projects = $request->getArr('projectPHIDs'); + + $xactions = array(); + $template = id(new DivinerLiveBookTransaction()); + + $xactions[] = id(clone $template) + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue( + 'edge:type', + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST) + ->setNewValue( + array( + '=' => array_fuse($v_projects), + )); + + id(new DivinerLiveBookEditor()) + ->setContinueOnNoEffect(true) + ->setContentSourceFromRequest($request) + ->setActor($user) + ->applyTransactions($book, $xactions); + + return id(new AphrontRedirectResponse())->setURI($view_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($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/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,14 @@ +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 @@ + 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()); + } + +}