Page MenuHomePhorge

No OneTemporary

diff --git a/src/applications/conduit/method/remarkup/ConduitAPI_remarkup_process_Method.php b/src/applications/conduit/method/remarkup/ConduitAPI_remarkup_process_Method.php
index 6d8d975a79..892776ad51 100644
--- a/src/applications/conduit/method/remarkup/ConduitAPI_remarkup_process_Method.php
+++ b/src/applications/conduit/method/remarkup/ConduitAPI_remarkup_process_Method.php
@@ -1,75 +1,76 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class ConduitAPI_remarkup_process_Method extends ConduitAPIMethod {
public function getMethodStatus() {
return self::METHOD_STATUS_UNSTABLE;
}
public function getMethodDescription() {
return 'Process text through remarkup in phabricator context.';
}
public function defineReturnType() {
return 'nonempty dict';
}
public function defineErrorTypes() {
return array(
'ERR-NO-CONTENT' => 'Content may not be empty.',
'ERR-INVALID-ENGINE' => 'Invalid markup engine.',
);
}
public function defineParamTypes() {
$available_contexts = array_keys($this->getEngineContexts());
$available_contexts = implode(', ', $available_contexts);
return array(
'context' => 'required enum<'.$available_contexts.'>',
'content' => 'required string',
);
}
protected function execute(ConduitAPIRequest $request) {
$content = $request->getValue('content');
$context = $request->getValue('context');
$engine_class = idx($this->getEngineContexts(), $context);
if (!$engine_class) {
throw new ConduitException('ERR-INVALID_ENGINE');
}
$engine = PhabricatorMarkupEngine::$engine_class();
+ $engine->setConfig('viewer', $request->getUser());
$result = array(
'content' => $engine->markupText($content),
);
return $result;
}
private function getEngineContexts() {
return array(
'phriction' => 'newPhrictionMarkupEngine',
'maniphest' => 'newManiphestMarkupEngine',
'differential' => 'newDifferentialMarkupEngine',
);
}
}
diff --git a/src/applications/maniphest/controller/ManiphestTaskDescriptionChangeController.php b/src/applications/maniphest/controller/ManiphestTaskDescriptionChangeController.php
index eda546d22b..d28f209ab6 100644
--- a/src/applications/maniphest/controller/ManiphestTaskDescriptionChangeController.php
+++ b/src/applications/maniphest/controller/ManiphestTaskDescriptionChangeController.php
@@ -1,87 +1,88 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group maniphest
*/
final class ManiphestTaskDescriptionChangeController
extends ManiphestController {
private $transactionID;
protected function setTransactionID($transaction_id) {
$this->transactionID = $transaction_id;
return $this;
}
public function getTransactionID() {
return $this->transactionID;
}
public function willProcessRequest(array $data) {
$this->setTransactionID(idx($data, 'id'));
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$is_show_more = false;
if (!$this->getTransactionID()) {
$this->setTransactionID($this->getRequest()->getStr('ref'));
$is_show_more = true;
}
$transaction_id = $this->getTransactionID();
$transaction = id(new ManiphestTransaction())->load($transaction_id);
if (!$transaction) {
return new Aphront404Response();
}
$transactions = array($transaction);
$phids = array();
foreach ($transactions as $xaction) {
foreach ($xaction->extractPHIDs() as $phid) {
$phids[$phid] = $phid;
}
}
$handles = $this->loadViewerHandles($phids);
$engine = new PhabricatorMarkupEngine();
+ $engine->setViewer($user);
$engine->addObject($transaction, ManiphestTransaction::MARKUP_FIELD_BODY);
$engine->process();
$view = new ManiphestTransactionDetailView();
$view->setTransactionGroup($transactions);
$view->setHandles($handles);
$view->setUser($user);
$view->setMarkupEngine($engine);
$view->setRenderSummaryOnly(true);
$view->setRenderFullSummary(true);
$view->setRangeSpecification($request->getStr('range'));
if ($is_show_more) {
return id(new PhabricatorChangesetResponse())
->setRenderedChangeset($view->render());
} else {
return id(new AphrontAjaxResponse())->setContent($view->render());
}
}
}
diff --git a/src/applications/maniphest/controller/ManiphestTaskDescriptionPreviewController.php b/src/applications/maniphest/controller/ManiphestTaskDescriptionPreviewController.php
index 17a89f6991..35989c9e57 100644
--- a/src/applications/maniphest/controller/ManiphestTaskDescriptionPreviewController.php
+++ b/src/applications/maniphest/controller/ManiphestTaskDescriptionPreviewController.php
@@ -1,45 +1,45 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group maniphest
*/
final class ManiphestTaskDescriptionPreviewController
extends ManiphestController {
public function processRequest() {
-
$request = $this->getRequest();
$description = $request->getStr('description');
$task = new ManiphestTask();
$task->setDescription($description);
$output = PhabricatorMarkupEngine::renderOneObject(
$task,
- ManiphestTask::MARKUP_FIELD_DESCRIPTION);
+ ManiphestTask::MARKUP_FIELD_DESCRIPTION,
+ $request->getUser());
$content =
'<div class="phabricator-remarkup">'.
$output.
'</div>';
return id(new AphrontAjaxResponse())
->setContent($content);
}
}
diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php
index cf1f15165a..12615caac5 100644
--- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php
+++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php
@@ -1,555 +1,556 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group maniphest
*/
final class ManiphestTaskDetailController extends ManiphestController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$e_title = null;
$priority_map = ManiphestTaskPriority::getTaskPriorityMap();
$task = id(new ManiphestTask())->load($this->id);
if (!$task) {
return new Aphront404Response();
}
$workflow = $request->getStr('workflow');
$parent_task = null;
if ($workflow && is_numeric($workflow)) {
$parent_task = id(new ManiphestTask())->load($workflow);
}
$transactions = id(new ManiphestTransaction())->loadAllWhere(
'taskID = %d ORDER BY id ASC',
$task->getID());
$e_commit = PhabricatorEdgeConfig::TYPE_TASK_HAS_COMMIT;
$e_dep_on = PhabricatorEdgeConfig::TYPE_TASK_DEPENDS_ON_TASK;
$e_dep_by = PhabricatorEdgeConfig::TYPE_TASK_DEPENDED_ON_BY_TASK;
$e_rev = PhabricatorEdgeConfig::TYPE_TASK_HAS_RELATED_DREV;
$phid = $task->getPHID();
$query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(array($phid))
->withEdgeTypes(
array(
$e_commit,
$e_dep_on,
$e_dep_by,
$e_rev,
));
$edges = $query->execute();
$commit_phids = array_keys($edges[$phid][$e_commit]);
$dep_on_tasks = array_keys($edges[$phid][$e_dep_on]);
$dep_by_tasks = array_keys($edges[$phid][$e_dep_by]);
$revs = array_keys($edges[$phid][$e_rev]);
$phids = array_fill_keys($query->getDestinationPHIDs(), true);
foreach ($transactions as $transaction) {
foreach ($transaction->extractPHIDs() as $phid) {
$phids[$phid] = true;
}
}
foreach ($task->getCCPHIDs() as $phid) {
$phids[$phid] = true;
}
foreach ($task->getProjectPHIDs() as $phid) {
$phids[$phid] = true;
}
if ($task->getOwnerPHID()) {
$phids[$task->getOwnerPHID()] = true;
}
$phids[$task->getAuthorPHID()] = true;
$attached = $task->getAttached();
foreach ($attached as $type => $list) {
foreach ($list as $phid => $info) {
$phids[$phid] = true;
}
}
if ($parent_task) {
$phids[$parent_task->getPHID()] = true;
}
$phids = array_keys($phids);
$handles = $this->loadViewerHandles($phids);
$dict = array();
$dict['Status'] =
'<strong>'.
ManiphestTaskStatus::getTaskStatusFullName($task->getStatus()).
'</strong>';
$dict['Assigned To'] = $task->getOwnerPHID()
? $handles[$task->getOwnerPHID()]->renderLink()
: '<em>None</em>';
$dict['Priority'] = ManiphestTaskPriority::getTaskPriorityName(
$task->getPriority());
$cc = $task->getCCPHIDs();
if ($cc) {
$cc_links = array();
foreach ($cc as $phid) {
$cc_links[] = $handles[$phid]->renderLink();
}
$dict['CC'] = implode(', ', $cc_links);
} else {
$dict['CC'] = '<em>None</em>';
}
$dict['Author'] = $handles[$task->getAuthorPHID()]->renderLink();
$source = $task->getOriginalEmailSource();
if ($source) {
$subject = '[T'.$task->getID().'] '.$task->getTitle();
$dict['From Email'] = phutil_render_tag(
'a',
array(
'href' => 'mailto:'.$source.'?subject='.$subject
),
phutil_escape_html($source));
}
$projects = $task->getProjectPHIDs();
if ($projects) {
$project_links = array();
foreach ($projects as $phid) {
$project_links[] = $handles[$phid]->renderLink();
}
$dict['Projects'] = implode(', ', $project_links);
} else {
$dict['Projects'] = '<em>None</em>';
}
$extensions = ManiphestTaskExtensions::newExtensions();
$aux_fields = $extensions->getAuxiliaryFieldSpecifications();
if ($aux_fields) {
$task->loadAndAttachAuxiliaryAttributes();
foreach ($aux_fields as $aux_field) {
$aux_key = $aux_field->getAuxiliaryKey();
$aux_field->setValue($task->getAuxiliaryAttribute($aux_key));
$value = $aux_field->renderForDetailView();
if (strlen($value)) {
$dict[$aux_field->getLabel()] = $value;
}
}
}
if ($dep_by_tasks) {
$dict['Dependent Tasks'] = $this->renderHandleList(
array_select_keys($handles, $dep_by_tasks));
}
if ($dep_on_tasks) {
$dict['Depends On'] = $this->renderHandleList(
array_select_keys($handles, $dep_on_tasks));
}
if ($revs) {
$dict['Revisions'] = $this->renderHandleList(
array_select_keys($handles, $revs));
}
if ($commit_phids) {
$dict['Commits'] = $this->renderHandleList(
array_select_keys($handles, $commit_phids));
}
$file_infos = idx($attached, PhabricatorPHIDConstants::PHID_TYPE_FILE);
if ($file_infos) {
$file_phids = array_keys($file_infos);
$files = id(new PhabricatorFile())->loadAllWhere(
'phid IN (%Ls)',
$file_phids);
$views = array();
foreach ($files as $file) {
$view = new AphrontFilePreviewView();
$view->setFile($file);
$views[] = $view->render();
}
$dict['Files'] = implode('', $views);
}
$context_bar = null;
if ($parent_task) {
$context_bar = new AphrontContextBarView();
$context_bar->addButton(
phutil_render_tag(
'a',
array(
'href' => '/maniphest/task/create/?parent='.$parent_task->getID(),
'class' => 'green button',
),
'Create Another Subtask'));
$context_bar->appendChild(
'Created a subtask of <strong>'.
$handles[$parent_task->getPHID()]->renderLink().
'</strong>');
} else if ($workflow == 'create') {
$context_bar = new AphrontContextBarView();
$context_bar->addButton('<label>Create Another:</label>');
$context_bar->addButton(
phutil_render_tag(
'a',
array(
'href' => '/maniphest/task/create/?template='.$task->getID(),
'class' => 'green button',
),
'Similar Task'));
$context_bar->addButton(
phutil_render_tag(
'a',
array(
'href' => '/maniphest/task/create/',
'class' => 'green button',
),
'Empty Task'));
$context_bar->appendChild('New task created.');
}
$actions = array();
$action = new AphrontHeadsupActionView();
$action->setName('Edit Task');
$action->setURI('/maniphest/task/edit/'.$task->getID().'/');
$action->setClass('action-edit');
$actions[] = $action;
require_celerity_resource('phabricator-flag-css');
$flag = PhabricatorFlagQuery::loadUserFlag($user, $task->getPHID());
if ($flag) {
$class = PhabricatorFlagColor::getCSSClass($flag->getColor());
$color = PhabricatorFlagColor::getColorName($flag->getColor());
$action = new AphrontHeadsupActionView();
$action->setClass('flag-clear '.$class);
$action->setURI('/flag/delete/'.$flag->getID().'/');
$action->setName('Remove '.$color.' Flag');
$action->setWorkflow(true);
$actions[] = $action;
} else {
$action = new AphrontHeadsupActionView();
$action->setClass('phabricator-flag-ghost');
$action->setURI('/flag/edit/'.$task->getPHID().'/');
$action->setName('Flag Task');
$action->setWorkflow(true);
$actions[] = $action;
}
require_celerity_resource('phabricator-object-selector-css');
require_celerity_resource('javelin-behavior-phabricator-object-selector');
$action = new AphrontHeadsupActionView();
$action->setName('Merge Duplicates');
$action->setURI('/search/attach/'.$task->getPHID().'/TASK/merge/');
$action->setWorkflow(true);
$action->setClass('action-merge');
$actions[] = $action;
$action = new AphrontHeadsupActionView();
$action->setName('Create Subtask');
$action->setURI('/maniphest/task/create/?parent='.$task->getID());
$action->setClass('action-branch');
$actions[] = $action;
$action = new AphrontHeadsupActionView();
$action->setName('Edit Dependencies');
$action->setURI('/search/attach/'.$task->getPHID().'/TASK/dependencies/');
$action->setWorkflow(true);
$action->setClass('action-dependencies');
$actions[] = $action;
$action = new AphrontHeadsupActionView();
$action->setName('Edit Differential Revisions');
$action->setURI('/search/attach/'.$task->getPHID().'/DREV/');
$action->setWorkflow(true);
$action->setClass('action-attach');
$actions[] = $action;
$action_list = new AphrontHeadsupActionListView();
$action_list->setActions($actions);
$headsup_panel = new AphrontHeadsupView();
$headsup_panel->setObjectName('T'.$task->getID());
$headsup_panel->setHeader($task->getTitle());
$headsup_panel->setActionList($action_list);
$headsup_panel->setProperties($dict);
$engine = new PhabricatorMarkupEngine();
+ $engine->setViewer($user);
$engine->addObject($task, ManiphestTask::MARKUP_FIELD_DESCRIPTION);
foreach ($transactions as $xaction) {
if ($xaction->hasComments()) {
$engine->addObject($xaction, ManiphestTransaction::MARKUP_FIELD_BODY);
}
}
$engine->process();
$headsup_panel->appendChild(
'<div class="phabricator-remarkup">'.
$engine->getOutput($task, ManiphestTask::MARKUP_FIELD_DESCRIPTION).
'</div>');
$transaction_types = ManiphestTransactionType::getTransactionTypeMap();
$resolution_types = ManiphestTaskStatus::getTaskStatusMap();
if ($task->getStatus() == ManiphestTaskStatus::STATUS_OPEN) {
$resolution_types = array_select_keys(
$resolution_types,
array(
ManiphestTaskStatus::STATUS_CLOSED_RESOLVED,
ManiphestTaskStatus::STATUS_CLOSED_WONTFIX,
ManiphestTaskStatus::STATUS_CLOSED_INVALID,
ManiphestTaskStatus::STATUS_CLOSED_SPITE,
));
} else {
$resolution_types = array(
ManiphestTaskStatus::STATUS_OPEN => 'Reopened',
);
$transaction_types[ManiphestTransactionType::TYPE_STATUS] =
'Reopen Task';
unset($transaction_types[ManiphestTransactionType::TYPE_PRIORITY]);
unset($transaction_types[ManiphestTransactionType::TYPE_OWNER]);
}
$default_claim = array(
$user->getPHID() => $user->getUsername().' ('.$user->getRealName().')',
);
$draft = id(new PhabricatorDraft())->loadOneWhere(
'authorPHID = %s AND draftKey = %s',
$user->getPHID(),
$task->getPHID());
if ($draft) {
$draft_text = $draft->getDraft();
} else {
$draft_text = null;
}
$panel_id = celerity_generate_unique_node_id();
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
if ($is_serious) {
// Prevent tasks from being closed "out of spite" in serious business
// installs.
unset($resolution_types[ManiphestTaskStatus::STATUS_CLOSED_SPITE]);
}
$remarkup_href = PhabricatorEnv::getDoclink(
'article/Remarkup_Reference.html');
$comment_form = new AphrontFormView();
$comment_form
->setUser($user)
->setAction('/maniphest/transaction/save/')
->setEncType('multipart/form-data')
->addHiddenInput('taskID', $task->getID())
->appendChild(
id(new AphrontFormSelectControl())
->setLabel('Action')
->setName('action')
->setOptions($transaction_types)
->setID('transaction-action'))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel('Resolution')
->setName('resolution')
->setControlID('resolution')
->setControlStyle('display: none')
->setOptions($resolution_types))
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel('Assign To')
->setName('assign_to')
->setControlID('assign_to')
->setControlStyle('display: none')
->setID('assign-tokenizer')
->setDisableBehavior(true))
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel('CCs')
->setName('ccs')
->setControlID('ccs')
->setControlStyle('display: none')
->setID('cc-tokenizer')
->setDisableBehavior(true))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel('Priority')
->setName('priority')
->setOptions($priority_map)
->setControlID('priority')
->setControlStyle('display: none')
->setValue($task->getPriority()))
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel('Projects')
->setName('projects')
->setControlID('projects')
->setControlStyle('display: none')
->setID('projects-tokenizer')
->setDisableBehavior(true))
->appendChild(
id(new AphrontFormFileControl())
->setLabel('File')
->setName('file')
->setControlID('file')
->setControlStyle('display: none'))
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel('Comments')
->setName('comments')
->setValue($draft_text)
->setCaption(
phutil_render_tag(
'a',
array(
'href' => $remarkup_href,
'tabindex' => '-1',
'target' => '_blank',
),
'Formatting Reference'))
->setID('transaction-comments'))
->appendChild(
id(new AphrontFormDragAndDropUploadControl())
->setLabel('Attached Files')
->setName('files')
->setDragAndDropTarget($panel_id)
->setActivatedClass('aphront-panel-view-drag-and-drop'))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue($is_serious ? 'Submit' : 'Avast!'));
$control_map = array(
ManiphestTransactionType::TYPE_STATUS => 'resolution',
ManiphestTransactionType::TYPE_OWNER => 'assign_to',
ManiphestTransactionType::TYPE_CCS => 'ccs',
ManiphestTransactionType::TYPE_PRIORITY => 'priority',
ManiphestTransactionType::TYPE_PROJECTS => 'projects',
ManiphestTransactionType::TYPE_ATTACH => 'file',
);
$tokenizer_map = array(
ManiphestTransactionType::TYPE_PROJECTS => array(
'id' => 'projects-tokenizer',
'src' => '/typeahead/common/projects/',
'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'),
'placeholder' => 'Type a project name...',
),
ManiphestTransactionType::TYPE_OWNER => array(
'id' => 'assign-tokenizer',
'src' => '/typeahead/common/users/',
'value' => $default_claim,
'limit' => 1,
'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'),
'placeholder' => 'Type a user name...',
),
ManiphestTransactionType::TYPE_CCS => array(
'id' => 'cc-tokenizer',
'src' => '/typeahead/common/mailable/',
'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'),
'placeholder' => 'Type a user or mailing list...',
),
);
Javelin::initBehavior('maniphest-transaction-controls', array(
'select' => 'transaction-action',
'controlMap' => $control_map,
'tokenizers' => $tokenizer_map,
));
Javelin::initBehavior('maniphest-transaction-preview', array(
'uri' => '/maniphest/transaction/preview/'.$task->getID().'/',
'preview' => 'transaction-preview',
'comments' => 'transaction-comments',
'action' => 'transaction-action',
'map' => $control_map,
'tokenizers' => $tokenizer_map,
));
$comment_panel = new AphrontPanelView();
$comment_panel->appendChild($comment_form);
$comment_panel->setID($panel_id);
$comment_panel->addClass('aphront-panel-accent');
$comment_panel->setHeader($is_serious ? 'Add Comment' : 'Weigh In');
$preview_panel =
'<div class="aphront-panel-preview">
<div id="transaction-preview">
<div class="aphront-panel-preview-loading-text">
Loading preview...
</div>
</div>
</div>';
$transaction_view = new ManiphestTransactionListView();
$transaction_view->setTransactions($transactions);
$transaction_view->setHandles($handles);
$transaction_view->setUser($user);
$transaction_view->setAuxiliaryFields($aux_fields);
$transaction_view->setMarkupEngine($engine);
PhabricatorFeedStoryNotification::updateObjectNotificationViews(
$user, $task->getPHID());
return $this->buildStandardPageResponse(
array(
$context_bar,
$headsup_panel,
$transaction_view,
$comment_panel,
$preview_panel,
),
array(
'title' => 'T'.$task->getID().' '.$task->getTitle(),
'pageObjects' => array($task->getPHID()),
));
}
private function renderHandleList(array $handles) {
$links = array();
foreach ($handles as $handle) {
$links[] = $handle->renderLink();
}
return implode('<br />', $links);
}
}
diff --git a/src/applications/maniphest/controller/ManiphestTransactionPreviewController.php b/src/applications/maniphest/controller/ManiphestTransactionPreviewController.php
index 82c41f6ea4..2206933e78 100644
--- a/src/applications/maniphest/controller/ManiphestTransactionPreviewController.php
+++ b/src/applications/maniphest/controller/ManiphestTransactionPreviewController.php
@@ -1,136 +1,137 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group maniphest
*/
final class ManiphestTransactionPreviewController extends ManiphestController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$comments = $request->getStr('comments');
$task = id(new ManiphestTask())->load($this->id);
if (!$task) {
return new Aphront404Response();
}
$draft = id(new PhabricatorDraft())->loadOneWhere(
'authorPHID = %s AND draftKey = %s',
$user->getPHID(),
$task->getPHID());
if (!$draft) {
$draft = new PhabricatorDraft();
$draft->setAuthorPHID($user->getPHID());
$draft->setDraftKey($task->getPHID());
}
$draft->setDraft($comments);
$draft->save();
$action = $request->getStr('action');
$transaction = new ManiphestTransaction();
$transaction->setAuthorPHID($user->getPHID());
$transaction->setComments($comments);
$transaction->setTransactionType($action);
$value = $request->getStr('value');
// grab phids for handles and set transaction values based on action and
// value (empty or control-specific format) coming in from the wire
switch ($action) {
case ManiphestTransactionType::TYPE_PRIORITY:
$transaction->setOldValue($task->getPriority());
$transaction->setNewValue($value);
break;
case ManiphestTransactionType::TYPE_OWNER:
if ($value) {
$value = current(json_decode($value));
$phids = array($value);
} else {
$phids = array();
}
$transaction->setNewValue($value);
break;
case ManiphestTransactionType::TYPE_CCS:
if ($value) {
$value = json_decode($value);
$phids = $value;
foreach ($task->getCCPHIDs() as $cc_phid) {
$phids[] = $cc_phid;
$value[] = $cc_phid;
}
$transaction->setNewValue($value);
} else {
$phids = array();
$transaction->setNewValue(array());
}
$transaction->setOldValue($task->getCCPHIDs());
break;
case ManiphestTransactionType::TYPE_PROJECTS:
if ($value) {
$value = json_decode($value);
$phids = $value;
foreach ($task->getProjectPHIDs() as $project_phid) {
$phids[] = $project_phid;
$value[] = $project_phid;
}
$transaction->setNewValue($value);
} else {
$phids = array();
$transaction->setNewValue(array());
}
$transaction->setOldValue($task->getProjectPHIDs());
break;
default:
$phids = array();
$transaction->setNewValue($value);
break;
}
$phids[] = $user->getPHID();
$handles = $this->loadViewerHandles($phids);
$transactions = array();
$transactions[] = $transaction;
$engine = new PhabricatorMarkupEngine();
+ $engine->setViewer($user);
$engine->addObject($transaction, ManiphestTransaction::MARKUP_FIELD_BODY);
$engine->process();
$transaction_view = new ManiphestTransactionListView();
$transaction_view->setTransactions($transactions);
$transaction_view->setHandles($handles);
$transaction_view->setUser($user);
$transaction_view->setMarkupEngine($engine);
$transaction_view->setPreview(true);
return id(new AphrontAjaxResponse())
->setContent($transaction_view->render());
}
}
diff --git a/src/applications/phriction/controller/PhrictionDocumentController.php b/src/applications/phriction/controller/PhrictionDocumentController.php
index 5f3253d44e..3ff4ea3b60 100644
--- a/src/applications/phriction/controller/PhrictionDocumentController.php
+++ b/src/applications/phriction/controller/PhrictionDocumentController.php
@@ -1,399 +1,399 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group phriction
*/
final class PhrictionDocumentController
extends PhrictionController {
private $slug;
public function willProcessRequest(array $data) {
$this->slug = $data['slug'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$slug = PhabricatorSlug::normalize($this->slug);
if ($slug != $this->slug) {
$uri = PhrictionDocument::getSlugURI($slug);
// Canonicalize pages to their one true URI.
return id(new AphrontRedirectResponse())->setURI($uri);
}
require_celerity_resource('phriction-document-css');
$document = id(new PhrictionDocument())->loadOneWhere(
'slug = %s',
$slug);
$breadcrumbs = $this->renderBreadcrumbs($slug);
$version_note = null;
if (!$document) {
if (PhrictionDocument::isProjectSlug($slug)) {
$project = id(new PhabricatorProject())->loadOneWhere(
'phrictionSlug = %s',
PhrictionDocument::getProjectSlugIdentifier($slug));
if (!$project) {
return new Aphront404Response();
}
}
$create_uri = '/phriction/edit/?slug='.$slug;
$create_sentence =
'You can <strong>'.
phutil_render_tag(
'a',
array(
'href' => $create_uri,
),
'create a new document').
'</strong>.';
$button = phutil_render_tag(
'a',
array(
'href' => $create_uri,
'class' => 'green button',
),
'Create Page');
$page_content =
'<div class="phriction-content">'.
'<em>No content here!</em><br />'.
'No document found at <tt>'.phutil_escape_html($slug).'</tt>. '.
$create_sentence.
'</div>';
$page_title = 'Page Not Found';
$buttons = $button;
} else {
$version = $request->getInt('v');
if ($version) {
$content = id(new PhrictionContent())->loadOneWhere(
'documentID = %d AND version = %d',
$document->getID(),
$version);
if (!$content) {
return new Aphront404Response();
}
if ($content->getID() != $document->getContentID()) {
$version_note = new AphrontErrorView();
$version_note->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$version_note->setTitle('Older Version');
$version_note->appendChild(
'You are viewing an older version of this document, as it '.
'appeared on '.
phabricator_datetime($content->getDateCreated(), $user).'.');
}
} else {
$content = id(new PhrictionContent())->load($document->getContentID());
}
$page_title = $content->getTitle();
$project_phid = null;
if (PhrictionDocument::isProjectSlug($slug)) {
$project = id(new PhabricatorProject())->loadOneWhere(
'phrictionSlug = %s',
PhrictionDocument::getProjectSlugIdentifier($slug));
if ($project) {
$project_phid = $project->getPHID();
}
}
$phids = array_filter(
array(
$content->getAuthorPHID(),
$project_phid,
));
$handles = $this->loadViewerHandles($phids);
$age = time() - $content->getDateCreated();
$age = floor($age / (60 * 60 * 24));
if ($age < 1) {
$when = 'today';
} else if ($age == 1) {
$when = 'yesterday';
} else {
$when = "{$age} days ago";
}
$project_info = null;
if ($project_phid) {
$project_info =
'<br />This document is about the project '.
$handles[$project_phid]->renderLink().'.';
}
$byline =
'<div class="phriction-byline">'.
"Last updated {$when} by ".
$handles[$content->getAuthorPHID()]->renderLink().'.'.
$project_info.
'</div>';
$doc_status = $document->getStatus();
if ($doc_status == PhrictionDocumentStatus::STATUS_EXISTS) {
- $core_content = $content->renderContent();
+ $core_content = $content->renderContent($user);
} else if ($doc_status == PhrictionDocumentStatus::STATUS_DELETED) {
$notice = new AphrontErrorView();
$notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$notice->setTitle('Document Deleted');
$notice->appendChild(
'This document has been deleted. You can edit it to put new content '.
'here, or use history to revert to an earlier version.');
$core_content = $notice->render();
} else {
throw new Exception("Unknown document status '{$doc_status}'!");
}
$page_content =
'<div class="phriction-content">'.
$byline.
$core_content.
'</div>';
$edit_button = phutil_render_tag(
'a',
array(
'href' => '/phriction/edit/'.$document->getID().'/',
'class' => 'button',
),
'Edit Document');
$history_button = phutil_render_tag(
'a',
array(
'href' => PhrictionDocument::getSlugURI($slug, 'history'),
'class' => 'button grey',
),
'View History');
// these float right so history_button which is right most goes first
$buttons = $history_button.$edit_button;
}
if ($version_note) {
$version_note = $version_note->render();
}
$children = $this->renderChildren($slug);
$page =
'<div class="phriction-header">'.
$buttons.
'<h1>'.phutil_escape_html($page_title).'</h1>'.
$breadcrumbs.
'</div>'.
$version_note.
$page_content.
$children;
return $this->buildStandardPageResponse(
$page,
array(
'title' => 'Phriction - '.$page_title,
));
}
private 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 PhrictionDocument())->loadAllWhere(
'slug IN (%Ls)',
$ancestral_slugs);
$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[] = $ancestor_handle->renderLink();
}
$list = phutil_render_tag(
'a',
array(
'href' => '/phriction/',
),
'Document Index');
return
'<div class="phriction-breadcrumbs">'.
$list.' &middot; '.
'<span class="phriction-document-crumbs">'.
implode(" \xC2\xBB ", $breadcrumbs).
'</span>'.
'</div>';
}
private function renderChildren($slug) {
$document_dao = new PhrictionDocument();
$content_dao = new PhrictionContent();
$conn = $document_dao->establishConnection('r');
$limit = 50;
$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 = %d
ORDER BY d.depth, c.title LIMIT %d',
$document_dao->getTableName(),
$content_dao->getTableName(),
($slug == '/' ? '' : $slug),
$d_child,
$d_grandchild,
PhrictionDocumentStatus::STATUS_EXISTS,
$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();
$list[] = '<ul>';
foreach ($children as $child) {
$list[] = $this->renderChildDocumentLink($child);
$grand = idx($grandchildren, $child['slug'], array());
if ($grand) {
$list[] = '<ul>';
foreach ($grand as $grandchild) {
$list[] = $this->renderChildDocumentLink($grandchild);
}
$list[] = '</ul>';
}
}
if ($more_children) {
$list[] = '<li>More...</li>';
}
$list[] = '</ul>';
$list = implode("\n", $list);
return
'<div class="phriction-children">'.
'<div class="phriction-children-header">Document Hierarchy</div>'.
$list.
'</div>';
}
private function renderChildDocumentLink(array $info) {
$title = nonempty($info['title'], '(Untitled Document)');
$item = phutil_render_tag(
'a',
array(
'href' => PhrictionDocument::getSlugURI($info['slug']),
),
phutil_escape_html($title));
if (isset($info['empty'])) {
$item = '<em>'.$item.'</em>';
}
return '<li>'.$item.'</li>';
}
}
diff --git a/src/applications/phriction/controller/PhrictionDocumentPreviewController.php b/src/applications/phriction/controller/PhrictionDocumentPreviewController.php
index 3790a67cf2..e5583cc890 100644
--- a/src/applications/phriction/controller/PhrictionDocumentPreviewController.php
+++ b/src/applications/phriction/controller/PhrictionDocumentPreviewController.php
@@ -1,51 +1,49 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group phriction
*/
final class PhrictionDocumentPreviewController
extends PhrictionController {
public function processRequest() {
$request = $this->getRequest();
$document = $request->getStr('document');
$draft_key = $request->getStr('draftkey');
if ($draft_key) {
$table = new PhabricatorDraft();
queryfx(
$table->establishConnection('w'),
'INSERT INTO %T (authorPHID, draftKey, draft) VALUES (%s, %s, %s)
ON DUPLICATE KEY UPDATE draft = VALUES(draft)',
$table->getTableName(),
$request->getUser()->getPHID(),
$draft_key,
$document);
}
$content_obj = new PhrictionContent();
$content_obj->setContent($document);
-
- $engine = PhabricatorMarkupEngine::newPhrictionMarkupEngine();
- $content = $content_obj->renderContent();
+ $content = $content_obj->renderContent($request->getUser());
return id(new AphrontAjaxResponse())->setContent($content);
}
}
diff --git a/src/applications/phriction/storage/PhrictionContent.php b/src/applications/phriction/storage/PhrictionContent.php
index da84fb1ce0..18d907db69 100644
--- a/src/applications/phriction/storage/PhrictionContent.php
+++ b/src/applications/phriction/storage/PhrictionContent.php
@@ -1,118 +1,119 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @task markup Markup Interface
*
* @group phriction
*/
final class PhrictionContent extends PhrictionDAO
implements PhabricatorMarkupInterface {
const MARKUP_FIELD_BODY = 'markup:body';
protected $id;
protected $documentID;
protected $version;
protected $authorPHID;
protected $title;
protected $slug;
protected $content;
protected $description;
protected $changeType;
protected $changeRef;
- public function renderContent() {
+ public function renderContent(PhabricatorUser $viewer) {
return PhabricatorMarkupEngine::renderOneObject(
$this,
- self::MARKUP_FIELD_BODY);
+ self::MARKUP_FIELD_BODY,
+ $viewer);
}
/* -( Markup Interface )--------------------------------------------------- */
/**
* @task markup
*/
public function getMarkupFieldKey($field) {
if ($this->shouldUseMarkupCache($field)) {
$id = $this->getID();
} else {
$id = PhabricatorHash::digest($this->getMarkupText($field));
}
return "phriction:{$field}:{$id}";
}
/**
* @task markup
*/
public function getMarkupText($field) {
return $this->getContent();
}
/**
* @task markup
*/
public function newMarkupEngine($field) {
return PhabricatorMarkupEngine::newPhrictionMarkupEngine();
}
/**
* @task markup
*/
public function didMarkupText(
$field,
$output,
PhutilMarkupEngine $engine) {
$toc = PhutilRemarkupEngineRemarkupHeaderBlockRule::renderTableOfContents(
$engine);
if ($toc) {
$toc =
'<div class="phabricator-remarkup-toc">'.
'<div class="phabricator-remarkup-toc-header">'.
'Table of Contents'.
'</div>'.
$toc.
'</div>';
}
return
'<div class="phabricator-remarkup">'.
$toc.
$output.
'</div>';
}
/**
* @task markup
*/
public function shouldUseMarkupCache($field) {
return (bool)$this->getID();
}
}
diff --git a/src/applications/ponder/view/PonderCommentBodyView.php b/src/applications/ponder/view/PonderCommentBodyView.php
index 4bf0a57ad3..b35407414f 100644
--- a/src/applications/ponder/view/PonderCommentBodyView.php
+++ b/src/applications/ponder/view/PonderCommentBodyView.php
@@ -1,149 +1,150 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class PonderCommentBodyView extends AphrontView {
private $target;
private $question;
private $handles;
private $preview;
private $anchorName;
private $user;
private $action;
public function setQuestion($question) {
$this->question = $question;
return $this;
}
public function setTarget($target) {
$this->target = $target;
return $this;
}
public function setAction($action) {
$this->action = $action;
return $this;
}
public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
public function setPreview($preview) {
$this->preview = $preview;
return $this;
}
public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
public function render() {
if (!$this->user) {
throw new Exception("Call setUser() before rendering!");
}
require_celerity_resource('phabricator-remarkup-css');
require_celerity_resource('ponder-post-css');
$user = $this->user;
$question = $this->question;
$target = $this->target;
$content = $target->getContent();
$info = array();
$content = PhabricatorMarkupEngine::renderOneObject(
$target,
- $target->getMarkupField());
+ $target->getMarkupField(),
+ $this->user);
$content =
'<div class="phabricator-remarkup">'.
$content.
'</div>';
$author = $this->handles[$target->getAuthorPHID()];
$actions = array($author->renderLink().' '.$this->action);
$author_link = $author->renderLink();
$xaction_view = id(new PhabricatorTransactionView())
->setUser($user)
->setImageURI($author->getImageURI())
->setContentSource($target->getContentSource())
->setActions($actions);
if ($this->target instanceof PonderAnswer) {
$xaction_view->addClass("ponder-answer");
}
else {
$xaction_view->addClass("ponder-question");
}
if ($this->preview) {
$xaction_view->setIsPreview($this->preview);
} else {
$xaction_view->setEpoch($target->getDateCreated());
if ($this->target instanceof PonderAnswer) {
$anchor_text = 'Q' . $question->getID(). '#A' . $target->getID();
$xaction_view->setAnchor('A'.$target->getID(), $anchor_text);
$xaction_view->addClass("ponder-answer");
}
}
$xaction_view->appendChild(
'<div class="ponder-post-core">'.
$content.
'</div>'
);
$outerview = $xaction_view;
if (!$this->preview) {
$outerview =
id(new PonderVotableView())
->setPHID($target->getPHID())
->setCount($target->getVoteCount())
->setVote($target->getUserVote());
if ($this->target instanceof PonderAnswer) {
$outerview->setURI('/ponder/answer/vote/');
}
else {
$outerview->setURI('/ponder/question/vote/');
}
$outerview->appendChild($xaction_view);
}
return $outerview->render();
}
private function renderHandleList(array $phids) {
$result = array();
foreach ($phids as $phid) {
$result[] = $this->handles[$phid]->renderLink();
}
return implode(', ', $result);
}
}
diff --git a/src/infrastructure/markup/PhabricatorMarkupEngine.php b/src/infrastructure/markup/PhabricatorMarkupEngine.php
index f9e11d520d..3a3c297d1e 100644
--- a/src/infrastructure/markup/PhabricatorMarkupEngine.php
+++ b/src/infrastructure/markup/PhabricatorMarkupEngine.php
@@ -1,479 +1,498 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Manages markup engine selection, configuration, application, caching and
* pipelining.
*
* @{class:PhabricatorMarkupEngine} can be used to render objects which
* implement @{interface:PhabricatorMarkupInterface} in a batched, cache-aware
* way. For example, if you have a list of comments written in remarkup (and
* the objects implement the correct interface) you can render them by first
* building an engine and adding the fields with @{method:addObject}.
*
* $field = 'field:body'; // Field you want to render. Each object exposes
* // one or more fields of markup.
*
* $engine = new PhabricatorMarkupEngine();
* foreach ($comments as $comment) {
* $engine->addObject($comment, $field);
* }
*
* Now, call @{method:process} to perform the actual cache/rendering
* step. This is a heavyweight call which does batched data access and
* transforms the markup into output.
*
* $engine->process();
*
* Finally, do something with the results:
*
* $results = array();
* foreach ($comments as $comment) {
* $results[] = $engine->getOutput($comment, $field);
* }
*
* If you have a single object to render, you can use the convenience method
* @{method:renderOneObject}.
*
* @task markup Markup Pipeline
* @task engine Engine Construction
*/
final class PhabricatorMarkupEngine {
private $objects = array();
+ private $viewer;
/* -( Markup Pipeline )---------------------------------------------------- */
/**
* Convenience method for pushing a single object through the markup
* pipeline.
*
* @param PhabricatorMarkupInterface The object to render.
* @param string The field to render.
+ * @param PhabricatorUser User viewing the markup.
* @return string Marked up output.
* @task markup
*/
public static function renderOneObject(
PhabricatorMarkupInterface $object,
- $field) {
+ $field,
+ PhabricatorUser $viewer) {
return id(new PhabricatorMarkupEngine())
+ ->setViewer($viewer)
->addObject($object, $field)
->process()
->getOutput($object, $field);
}
/**
* Queue an object for markup generation when @{method:process} is
* called. You can retrieve the output later with @{method:getOutput}.
*
* @param PhabricatorMarkupInterface The object to render.
* @param string The field to render.
* @return this
* @task markup
*/
public function addObject(PhabricatorMarkupInterface $object, $field) {
$key = $this->getMarkupFieldKey($object, $field);
$this->objects[$key] = array(
'object' => $object,
'field' => $field,
);
return $this;
}
/**
* Process objects queued with @{method:addObject}. You can then retrieve
* the output with @{method:getOutput}.
*
* @return this
* @task markup
*/
public function process() {
$keys = array();
foreach ($this->objects as $key => $info) {
if (!isset($info['markup'])) {
$keys[] = $key;
}
}
if (!$keys) {
return;
}
$objects = array_select_keys($this->objects, $keys);
// Build all the markup engines. We need an engine for each field whether
// we have a cache or not, since we still need to postprocess the cache.
$engines = array();
foreach ($objects as $key => $info) {
$engines[$key] = $info['object']->newMarkupEngine($info['field']);
+ $engines[$key]->setConfig('viewer', $this->viewer);
}
// Load or build the preprocessor caches.
$blocks = $this->loadPreprocessorCaches($engines, $objects);
// Finalize the output.
foreach ($objects as $key => $info) {
$data = $blocks[$key]->getCacheData();
$engine = $engines[$key];
$field = $info['field'];
$object = $info['object'];
$output = $engine->postprocessText($data);
$output = $object->didMarkupText($field, $output, $engine);
$this->objects[$key]['output'] = $output;
}
return $this;
}
/**
* Get the output of markup processing for a field queued with
* @{method:addObject}. Before you can call this method, you must call
* @{method:process}.
*
* @param PhabricatorMarkupInterface The object to retrieve.
* @param string The field to retrieve.
* @return string Processed output.
* @task markup
*/
public function getOutput(PhabricatorMarkupInterface $object, $field) {
$key = $this->getMarkupFieldKey($object, $field);
if (empty($this->objects[$key])) {
throw new Exception(
"Call addObject() before getOutput() (key = '{$key}').");
}
if (!isset($this->objects[$key]['output'])) {
throw new Exception(
"Call process() before getOutput().");
}
return $this->objects[$key]['output'];
}
/**
* @task markup
*/
private function getMarkupFieldKey(
PhabricatorMarkupInterface $object,
$field) {
return $object->getMarkupFieldKey($field);
}
/**
* @task markup
*/
private function loadPreprocessorCaches(array $engines, array $objects) {
$blocks = array();
$use_cache = array();
foreach ($objects as $key => $info) {
if ($info['object']->shouldUseMarkupCache($info['field'])) {
$use_cache[$key] = true;
}
}
if ($use_cache) {
$blocks = id(new PhabricatorMarkupCache())->loadAllWhere(
'cacheKey IN (%Ls)',
array_keys($use_cache));
$blocks = mpull($blocks, null, 'getCacheKey');
}
foreach ($objects as $key => $info) {
if (isset($blocks[$key])) {
// If we already have a preprocessing cache, we don't need to rebuild
// it.
continue;
}
$text = $info['object']->getMarkupText($info['field']);
$data = $engines[$key]->preprocessText($text);
// NOTE: This is just debugging information to help sort out cache issues.
// If one machine is misconfigured and poisoning caches you can use this
// field to hunt it down.
$metadata = array(
'host' => php_uname('n'),
);
$blocks[$key] = id(new PhabricatorMarkupCache())
->setCacheKey($key)
->setCacheData($data)
->setMetadata($metadata);
if (isset($use_cache[$key])) {
// This is just filling a cache and always safe, even on a read pathway.
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
try {
$blocks[$key]->save();
} catch (AphrontQueryDuplicateKeyException $ex) {
// Ignore this, we just raced to write the cache.
}
unset($unguarded);
}
}
return $blocks;
}
+ /**
+ * Set the viewing user. Used to implement object permissions.
+ *
+ * @param PhabricatorUser The viewing user.
+ * @return this
+ * @task markup
+ */
+ public function setViewer(PhabricatorUser $viewer) {
+ $this->viewer = $viewer;
+ return $this;
+ }
+
+
/* -( Engine Construction )------------------------------------------------ */
+
/**
* @task engine
*/
public static function newManiphestMarkupEngine() {
return self::newMarkupEngine(array(
));
}
/**
* @task engine
*/
public static function newPhrictionMarkupEngine() {
return self::newMarkupEngine(array(
'header.generate-toc' => true,
));
}
/**
* @task engine
*/
public static function newPhameMarkupEngine() {
return self::newMarkupEngine(array(
'macros' => false,
));
}
/**
* @task engine
*/
public static function newFeedMarkupEngine() {
return self::newMarkupEngine(
array(
'macros' => false,
'fileproxy' => false,
'youtube' => false,
));
}
/**
* @task engine
*/
public static function newDifferentialMarkupEngine(array $options = array()) {
return self::newMarkupEngine(array(
'custom-inline' => PhabricatorEnv::getEnvConfig(
'differential.custom-remarkup-rules'),
'custom-block' => PhabricatorEnv::getEnvConfig(
'differential.custom-remarkup-block-rules'),
'differential.diff' => idx($options, 'differential.diff'),
));
}
/**
* @task engine
*/
public static function newDiffusionMarkupEngine(array $options = array()) {
return self::newMarkupEngine(array(
));
}
/**
* @task engine
*/
public static function newProfileMarkupEngine() {
return self::newMarkupEngine(array(
));
}
/**
* @task engine
*/
public static function newSlowvoteMarkupEngine() {
return self::newMarkupEngine(array(
));
}
public static function newPonderMarkupEngine(array $options = array()) {
return self::newMarkupEngine($options);
}
/**
* @task engine
*/
private static function getMarkupEngineDefaultConfiguration() {
return array(
'pygments' => PhabricatorEnv::getEnvConfig('pygments.enabled'),
'fileproxy' => PhabricatorEnv::getEnvConfig('files.enable-proxy'),
'youtube' => PhabricatorEnv::getEnvConfig(
'remarkup.enable-embedded-youtube'),
'custom-inline' => array(),
'custom-block' => array(),
'differential.diff' => null,
'header.generate-toc' => false,
'macros' => true,
'uri.allowed-protocols' => PhabricatorEnv::getEnvConfig(
'uri.allowed-protocols'),
'syntax-highlighter.engine' => PhabricatorEnv::getEnvConfig(
'syntax-highlighter.engine'),
);
}
/**
* @task engine
*/
private static function newMarkupEngine(array $options) {
$options += self::getMarkupEngineDefaultConfiguration();
$engine = new PhutilRemarkupEngine();
$engine->setConfig('preserve-linebreaks', true);
$engine->setConfig('pygments.enabled', $options['pygments']);
$engine->setConfig(
'uri.allowed-protocols',
$options['uri.allowed-protocols']);
$engine->setConfig('differential.diff', $options['differential.diff']);
$engine->setConfig('header.generate-toc', $options['header.generate-toc']);
$engine->setConfig(
'syntax-highlighter.engine',
$options['syntax-highlighter.engine']);
$rules = array();
$rules[] = new PhutilRemarkupRuleEscapeRemarkup();
$rules[] = new PhutilRemarkupRuleMonospace();
$custom_rule_classes = $options['custom-inline'];
if ($custom_rule_classes) {
foreach ($custom_rule_classes as $custom_rule_class) {
$rules[] = newv($custom_rule_class, array());
}
}
$rules[] = new PhutilRemarkupRuleDocumentLink();
if ($options['fileproxy']) {
$rules[] = new PhabricatorRemarkupRuleProxyImage();
}
if ($options['youtube']) {
$rules[] = new PhabricatorRemarkupRuleYoutube();
}
$rules[] = new PhutilRemarkupRuleHyperlink();
$rules[] = new PhabricatorRemarkupRulePhriction();
$rules[] = new PhabricatorRemarkupRuleDifferentialHandle();
$rules[] = new PhabricatorRemarkupRuleManiphestHandle();
$rules[] = new PhabricatorRemarkupRuleEmbedFile();
$rules[] = new PhabricatorRemarkupRuleDifferential();
$rules[] = new PhabricatorRemarkupRuleDiffusion();
$rules[] = new PhabricatorRemarkupRuleManiphest();
$rules[] = new PhabricatorRemarkupRulePaste();
$rules[] = new PhabricatorRemarkupRuleCountdown();
$rules[] = new PonderRuleQuestion();
if ($options['macros']) {
$rules[] = new PhabricatorRemarkupRuleImageMacro();
}
$rules[] = new PhabricatorRemarkupRuleMention();
$rules[] = new PhutilRemarkupRuleEscapeHTML();
$rules[] = new PhutilRemarkupRuleBold();
$rules[] = new PhutilRemarkupRuleItalic();
$rules[] = new PhutilRemarkupRuleDel();
$blocks = array();
$blocks[] = new PhutilRemarkupEngineRemarkupQuotesBlockRule();
$blocks[] = new PhutilRemarkupEngineRemarkupLiteralBlockRule();
$blocks[] = new PhutilRemarkupEngineRemarkupHeaderBlockRule();
$blocks[] = new PhutilRemarkupEngineRemarkupListBlockRule();
$blocks[] = new PhutilRemarkupEngineRemarkupCodeBlockRule();
$blocks[] = new PhutilRemarkupEngineRemarkupNoteBlockRule();
$blocks[] = new PhutilRemarkupEngineRemarkupTableBlockRule();
$blocks[] = new PhutilRemarkupEngineRemarkupDefaultBlockRule();
$custom_block_rule_classes = $options['custom-block'];
if ($custom_block_rule_classes) {
foreach ($custom_block_rule_classes as $custom_block_rule_class) {
$blocks[] = newv($custom_block_rule_class, array());
}
}
foreach ($blocks as $block) {
if ($block instanceof PhutilRemarkupEngineRemarkupLiteralBlockRule) {
$literal_rules = array();
$literal_rules[] = new PhutilRemarkupRuleEscapeHTML();
$literal_rules[] = new PhutilRemarkupRuleLinebreaks();
$block->setMarkupRules($literal_rules);
} else if (
!($block instanceof PhutilRemarkupEngineRemarkupCodeBlockRule)) {
$block->setMarkupRules($rules);
}
}
$engine->setBlockRules($blocks);
return $engine;
}
public static function extractPHIDsFromMentions(array $content_blocks) {
$mentions = array();
$engine = self::newDifferentialMarkupEngine();
foreach ($content_blocks as $content_block) {
$engine->markupText($content_block);
$phids = $engine->getTextMetadata(
PhabricatorRemarkupRuleMention::KEY_MENTIONED,
array());
$mentions += $phids;
}
return $mentions;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Jan 19 2025, 22:21 (6 w, 3 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1129341
Default Alt Text
(66 KB)

Event Timeline