diff --git a/src/applications/phriction/controller/PhrictionEditController.php b/src/applications/phriction/controller/PhrictionEditController.php index 9f59b63121..d86fa1e05b 100644 --- a/src/applications/phriction/controller/PhrictionEditController.php +++ b/src/applications/phriction/controller/PhrictionEditController.php @@ -1,337 +1,349 @@ getViewer(); $id = $request->getURIData('id'); $max_version = null; if ($id) { $is_new = false; $document = id(new PhrictionDocumentQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->needContent(true) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$document) { return new Aphront404Response(); } $max_version = $document->getMaxVersion(); $revert = $request->getInt('revert'); if ($revert) { $content = id(new PhrictionContentQuery()) ->setViewer($viewer) ->withDocumentPHIDs(array($document->getPHID())) ->withVersions(array($revert)) ->executeOne(); if (!$content) { return new Aphront404Response(); } } else { $content = id(new PhrictionContentQuery()) ->setViewer($viewer) ->withDocumentPHIDs(array($document->getPHID())) ->setLimit(1) ->executeOne(); } } else { $slug = $request->getStr('slug'); $slug = PhabricatorSlug::normalize($slug); if (!$slug) { return new Aphront404Response(); } $document = id(new PhrictionDocumentQuery()) ->setViewer($viewer) ->withSlugs(array($slug)) ->needContent(true) ->executeOne(); if ($document) { $content = id(new PhrictionContentQuery()) ->setViewer($viewer) ->withDocumentPHIDs(array($document->getPHID())) ->setLimit(1) ->executeOne(); $max_version = $document->getMaxVersion(); $is_new = false; } else { $document = PhrictionDocument::initializeNewDocument($viewer, $slug); $content = $document->getContent(); $is_new = true; } } require_celerity_resource('phriction-document-css'); $e_title = true; $e_content = true; $validation_exception = null; $notes = null; $title = $content->getTitle(); $overwrite = false; $v_cc = PhabricatorSubscribersQuery::loadSubscribersForPHID( $document->getPHID()); if ($is_new) { $v_projects = array(); } else { $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( $document->getPHID(), PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); $v_projects = array_reverse($v_projects); } $v_space = $document->getSpacePHID(); $content_text = $content->getContent(); $is_draft_mode = ($document->getContent()->getVersion() != $max_version); + $default_view = $document->getViewPolicy(); + $default_edit = $document->getEditPolicy(); + $default_space = $document->getSpacePHID(); + if ($request->isFormPost()) { if ($is_new) { $save_as_draft = false; } else { $save_as_draft = ($is_draft_mode || $request->getExists('draft')); } $title = $request->getStr('title'); $content_text = $request->getStr('content'); $notes = $request->getStr('description'); $max_version = $request->getInt('contentVersion'); $v_view = $request->getStr('viewPolicy'); $v_edit = $request->getStr('editPolicy'); $v_cc = $request->getArr('cc'); $v_projects = $request->getArr('projects'); $v_space = $request->getStr('spacePHID'); if ($save_as_draft) { $edit_type = PhrictionDocumentDraftTransaction::TRANSACTIONTYPE; } else { $edit_type = PhrictionDocumentContentTransaction::TRANSACTIONTYPE; } $xactions = array(); + if ($is_new) { + $xactions[] = id(new PhrictionTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_CREATE); + } + $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhrictionDocumentTitleTransaction::TRANSACTIONTYPE) ->setNewValue($title); $xactions[] = id(new PhrictionTransaction()) ->setTransactionType($edit_type) ->setNewValue($content_text); $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) - ->setNewValue($v_view); + ->setNewValue($v_view) + ->setIsDefaultTransaction($is_new && ($v_view === $default_view)); $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) - ->setNewValue($v_edit); + ->setNewValue($v_edit) + ->setIsDefaultTransaction($is_new && ($v_edit === $default_edit)); $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_SPACE) - ->setNewValue($v_space); + ->setNewValue($v_space) + ->setIsDefaultTransaction($is_new && ($v_space === $default_space)); $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS) ->setNewValue(array('=' => $v_cc)); $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setMetadataValue('edge:type', $proj_edge_type) ->setNewValue(array('=' => array_fuse($v_projects))); $editor = id(new PhrictionTransactionEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) ->setDescription($notes) ->setProcessContentVersionError(!$request->getBool('overwrite')) ->setContentVersion($max_version); try { $editor->applyTransactions($document, $xactions); $uri = PhrictionDocument::getSlugURI($document->getSlug()); $uri = new PhutilURI($uri); // If the user clicked "Save as Draft", take them to the draft, not // to the current published page. if ($save_as_draft) { $uri = $uri->alter('v', $document->getMaxVersion()); } return id(new AphrontRedirectResponse())->setURI($uri); } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; $e_title = nonempty( $ex->getShortMessage( PhrictionDocumentTitleTransaction::TRANSACTIONTYPE), true); $e_content = nonempty( $ex->getShortMessage( PhrictionDocumentContentTransaction::TRANSACTIONTYPE), true); // if we're not supposed to process the content version error, then // overwrite that content...! if (!$editor->getProcessContentVersionError()) { $overwrite = true; } $document->setViewPolicy($v_view); $document->setEditPolicy($v_edit); $document->setSpacePHID($v_space); } } if ($document->getID()) { $page_title = pht('Edit Document: %s', $content->getTitle()); if ($overwrite) { $submit_button = pht('Overwrite Changes'); } else { $submit_button = pht('Save and Publish'); } } else { $submit_button = pht('Create Document'); $page_title = pht('Create Document'); } $uri = $document->getSlug(); $uri = PhrictionDocument::getSlugURI($uri); $uri = PhabricatorEnv::getProductionURI($uri); $cancel_uri = PhrictionDocument::getSlugURI($document->getSlug()); $policies = id(new PhabricatorPolicyQuery()) ->setViewer($viewer) ->setObject($document) ->execute(); $view_capability = PhabricatorPolicyCapability::CAN_VIEW; $edit_capability = PhabricatorPolicyCapability::CAN_EDIT; $form = id(new AphrontFormView()) ->setUser($viewer) ->addHiddenInput('slug', $document->getSlug()) ->addHiddenInput('contentVersion', $max_version) ->addHiddenInput('overwrite', $overwrite) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Title')) ->setValue($title) ->setError($e_title) ->setName('title')) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('URI')) ->setValue($uri)) ->appendChild( id(new PhabricatorRemarkupControl()) ->setLabel(pht('Content')) ->setValue($content_text) ->setError($e_content) ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) ->setName('content') ->setID('document-textarea') ->setUser($viewer)) ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Tags')) ->setName('projects') ->setValue($v_projects) ->setDatasource(new PhabricatorProjectDatasource())) ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Subscribers')) ->setName('cc') ->setValue($v_cc) ->setUser($viewer) ->setDatasource(new PhabricatorMetaMTAMailableDatasource())) ->appendChild( id(new AphrontFormPolicyControl()) ->setViewer($viewer) ->setName('viewPolicy') ->setSpacePHID($v_space) ->setPolicyObject($document) ->setCapability($view_capability) ->setPolicies($policies)) ->appendChild( id(new AphrontFormPolicyControl()) ->setName('editPolicy') ->setPolicyObject($document) ->setCapability($edit_capability) ->setPolicies($policies)) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Edit Notes')) ->setValue($notes) ->setError(null) ->setName('description')); if ($is_draft_mode) { $form->appendControl( id(new AphrontFormSubmitControl()) ->addCancelButton($cancel_uri) ->setValue(pht('Save Draft'))); } else { $submit = id(new AphrontFormSubmitControl()); if (!$is_new) { $draft_button = id(new PHUIButtonView()) ->setTag('input') ->setName('draft') ->setText(pht('Save as Draft')) ->setColor(PHUIButtonView::GREEN); $submit->addButton($draft_button); } $submit ->addCancelButton($cancel_uri) ->setValue($submit_button); $form->appendControl($submit); } $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($page_title) ->setValidationException($validation_exception) ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setForm($form); $preview = id(new PHUIRemarkupPreviewPanel()) ->setHeader($content->getTitle()) ->setPreviewURI('/phriction/preview/'.$document->getSlug()) ->setControlID('document-textarea') ->setPreviewType(PHUIRemarkupPreviewPanel::DOCUMENT); $crumbs = $this->buildApplicationCrumbs(); if ($document->getID()) { $crumbs->addTextCrumb( $content->getTitle(), PhrictionDocument::getSlugURI($document->getSlug())); $crumbs->addTextCrumb(pht('Edit')); } else { $crumbs->addTextCrumb(pht('Create')); } $crumbs->setBorder(true); $view = id(new PHUITwoColumnView()) ->setFooter( array( $form_box, $preview, )); return $this->newPage() ->setTitle($page_title) ->setCrumbs($crumbs) ->appendChild($view); } } diff --git a/src/applications/phriction/storage/PhrictionDocument.php b/src/applications/phriction/storage/PhrictionDocument.php index 9f8a4a475b..88c6b142e6 100644 --- a/src/applications/phriction/storage/PhrictionDocument.php +++ b/src/applications/phriction/storage/PhrictionDocument.php @@ -1,320 +1,323 @@ true, self::CONFIG_TIMESTAMPS => false, self::CONFIG_COLUMN_SCHEMA => array( 'slug' => 'sort128', 'depth' => 'uint32', 'status' => 'text32', 'editedEpoch' => 'epoch', 'maxVersion' => 'uint32', ), self::CONFIG_KEY_SCHEMA => array( 'slug' => array( 'columns' => array('slug'), 'unique' => true, ), 'depth' => array( 'columns' => array('depth', 'slug'), 'unique' => true, ), ), ) + parent::getConfiguration(); } public function getPHIDType() { return PhrictionDocumentPHIDType::TYPECONST; } public static function initializeNewDocument(PhabricatorUser $actor, $slug) { $document = id(new self()) ->setSlug($slug); $content = id(new PhrictionContent()) ->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) { + $space_phid = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID( + $parent_doc); + $document ->setViewPolicy($parent_doc->getViewPolicy()) ->setEditPolicy($parent_doc->getEditPolicy()) - ->setSpacePHID($parent_doc->getSpacePHID()); + ->setSpacePHID($space_phid); } else { $default_view_policy = PhabricatorPolicies::getMostOpenPolicy(); $document ->setViewPolicy($default_view_policy) ->setEditPolicy(PhabricatorPolicies::POLICY_USER) ->setSpacePHID($actor->getDefaultSpacePHID()); } $document->setEditedEpoch(PhabricatorTime::getNow()); $document->setMaxVersion(0); return $document; } public static function getSlugURI($slug, $type = 'document') { static $types = array( 'document' => '/w/', 'history' => '/phriction/history/', ); if (empty($types[$type])) { throw new Exception(pht("Unknown URI type '%s'!", $type)); } $prefix = $types[$type]; if ($slug == '/') { return $prefix; } else { // NOTE: The effect here is to escape non-latin characters, since modern // browsers deal with escaped UTF8 characters in a reasonable way (showing // the user a readable URI) but older programs may not. $slug = phutil_escape_uri($slug); return $prefix.$slug; } } public function setSlug($slug) { $this->slug = PhabricatorSlug::normalize($slug); $this->depth = PhabricatorSlug::getDepth($slug); return $this; } public function attachContent(PhrictionContent $content) { $this->contentObject = $content; return $this; } public function getContent() { return $this->assertAttached($this->contentObject); } public function getAncestors() { return $this->ancestors; } public function getAncestor($slug) { return $this->assertAttachedKey($this->ancestors, $slug); } public function attachAncestor($slug, $ancestor) { $this->ancestors[$slug] = $ancestor; return $this; } public function getURI() { return self::getSlugURI($this->getSlug()); } /* -( Status )------------------------------------------------------------- */ public function getStatusObject() { return PhrictionDocumentStatus::newStatusObject($this->getStatus()); } public function getStatusIcon() { return $this->getStatusObject()->getIcon(); } public function getStatusColor() { return $this->getStatusObject()->getColor(); } public function getStatusDisplayName() { return $this->getStatusObject()->getDisplayName(); } public function isActive() { return $this->getStatusObject()->isActive(); } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return $this->getViewPolicy(); case PhabricatorPolicyCapability::CAN_EDIT: return $this->getEditPolicy(); } } public function hasAutomaticCapability($capability, PhabricatorUser $user) { return false; } /* -( PhabricatorSpacesInterface )----------------------------------------- */ public function getSpacePHID() { return $this->spacePHID; } /* -( PhabricatorSubscribableInterface )----------------------------------- */ public function isAutomaticallySubscribed($phid) { return false; } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new PhrictionTransactionEditor(); } public function getApplicationTransactionTemplate() { return new PhrictionTransaction(); } /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ public function getUsersToNotifyOfTokenGiven() { return PhabricatorSubscribersQuery::loadSubscribersForPHID($this->phid); } /* -( PhabricatorDestructibleInterface )----------------------------------- */ public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { $this->openTransaction(); $contents = id(new PhrictionContentQuery()) ->setViewer($engine->getViewer()) ->withDocumentPHIDs(array($this->getPHID())) ->execute(); foreach ($contents as $content) { $engine->destroyObject($content); } $this->delete(); $this->saveTransaction(); } /* -( PhabricatorFulltextInterface )--------------------------------------- */ public function newFulltextEngine() { return new PhrictionDocumentFulltextEngine(); } /* -( PhabricatorFerretInterface )----------------------------------------- */ public function newFerretEngine() { return new PhrictionDocumentFerretEngine(); } /* -( PhabricatorConduitResultInterface )---------------------------------- */ public function getFieldSpecificationsForConduit() { return array( id(new PhabricatorConduitSearchFieldSpecification()) ->setKey('path') ->setType('string') ->setDescription(pht('The path to the document.')), id(new PhabricatorConduitSearchFieldSpecification()) ->setKey('status') ->setType('map') ->setDescription(pht('Status information about the document.')), ); } public function getFieldValuesForConduit() { $status = array( 'value' => $this->getStatus(), 'name' => $this->getStatusDisplayName(), ); return array( 'path' => $this->getSlug(), 'status' => $status, ); } public function getConduitSearchAttachments() { return array( id(new PhrictionContentSearchEngineAttachment()) ->setAttachmentKey('content'), ); } /* -( PhabricatorPolicyCodexInterface )------------------------------------ */ public function newPolicyCodex() { return new PhrictionDocumentPolicyCodex(); } }