Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php
index 4fc3e10314..0ed4917800 100644
--- a/src/applications/base/PhabricatorApplication.php
+++ b/src/applications/base/PhabricatorApplication.php
@@ -1,653 +1,654 @@
<?php
/**
* @task info Application Information
* @task ui UI Integration
* @task uri URI Routing
* @task mail Email integration
* @task fact Fact Integration
* @task meta Application Management
*/
abstract class PhabricatorApplication
extends Phobject
implements PhabricatorPolicyInterface {
const MAX_STATUS_ITEMS = 100;
const GROUP_CORE = 'core';
const GROUP_UTILITIES = 'util';
const GROUP_ADMIN = 'admin';
const GROUP_DEVELOPER = 'developer';
final public static function getApplicationGroups() {
return array(
self::GROUP_CORE => pht('Core Applications'),
self::GROUP_UTILITIES => pht('Utilities'),
self::GROUP_ADMIN => pht('Administration'),
self::GROUP_DEVELOPER => pht('Developer Tools'),
);
}
/* -( Application Information )-------------------------------------------- */
abstract public function getName();
public function getShortDescription() {
return pht('%s Application', $this->getName());
}
final public function isInstalled() {
if (!$this->canUninstall()) {
return true;
}
$prototypes = PhabricatorEnv::getEnvConfig('phabricator.show-prototypes');
if (!$prototypes && $this->isPrototype()) {
return false;
}
$uninstalled = PhabricatorEnv::getEnvConfig(
'phabricator.uninstalled-applications');
return empty($uninstalled[get_class($this)]);
}
public function isPrototype() {
return false;
}
/**
* Return `true` if this application should never appear in application lists
* in the UI. Primarily intended for unit test applications or other
* pseudo-applications.
*
* Few applications should be unlisted. For most applications, use
* @{method:isLaunchable} to hide them from main launch views instead.
*
* @return bool True to remove application from UI lists.
*/
public function isUnlisted() {
return false;
}
/**
* Return `true` if this application is a normal application with a base
* URI and a web interface.
*
* Launchable applications can be pinned to the home page, and show up in the
* "Launcher" view of the Applications application. Making an application
* unlauncahble prevents pinning and hides it from this view.
*
* Usually, an application should be marked unlaunchable if:
*
* - it is available on every page anyway (like search); or
* - it does not have a web interface (like subscriptions); or
* - it is still pre-release and being intentionally buried.
*
* To hide applications more completely, use @{method:isUnlisted}.
*
* @return bool True if the application is launchable.
*/
public function isLaunchable() {
return true;
}
/**
* Return `true` if this application should be pinned by default.
*
* Users who have not yet set preferences see a default list of applications.
*
* @param PhabricatorUser User viewing the pinned application list.
* @return bool True if this application should be pinned by default.
*/
public function isPinnedByDefault(PhabricatorUser $viewer) {
return false;
}
/**
* Returns true if an application is first-party (developed by Phacility)
* and false otherwise.
*
* @return bool True if this application is developed by Phacility.
*/
final public function isFirstParty() {
$where = id(new ReflectionClass($this))->getFileName();
$root = phutil_get_library_root('phabricator');
if (!Filesystem::isDescendant($where, $root)) {
return false;
}
if (Filesystem::isDescendant($where, $root.'/extensions')) {
return false;
}
return true;
}
public function canUninstall() {
return true;
}
final public function getPHID() {
return 'PHID-APPS-'.get_class($this);
}
public function getTypeaheadURI() {
return $this->isLaunchable() ? $this->getBaseURI() : null;
}
public function getBaseURI() {
return null;
}
final public function getApplicationURI($path = '') {
return $this->getBaseURI().ltrim($path, '/');
}
public function getIconURI() {
return null;
}
public function getFontIcon() {
return 'fa-puzzle-piece';
}
public function getApplicationOrder() {
return PHP_INT_MAX;
}
public function getApplicationGroup() {
return self::GROUP_CORE;
}
public function getTitleGlyph() {
return null;
}
final public function getHelpMenuItems(PhabricatorUser $viewer) {
$items = array();
$articles = $this->getHelpDocumentationArticles($viewer);
if ($articles) {
$items[] = id(new PHUIListItemView())
->setType(PHUIListItemView::TYPE_LABEL)
->setName(pht('%s Documentation', $this->getName()));
foreach ($articles as $article) {
$item = id(new PHUIListItemView())
->setName($article['name'])
->setIcon('fa-book')
->setHref($article['href']);
$items[] = $item;
}
}
$command_specs = $this->getMailCommandObjects();
if ($command_specs) {
$items[] = id(new PHUIListItemView())
->setType(PHUIListItemView::TYPE_LABEL)
->setName(pht('Email Help'));
foreach ($command_specs as $key => $spec) {
$object = $spec['object'];
$class = get_class($this);
$href = '/applications/mailcommands/'.$class.'/'.$key.'/';
$item = id(new PHUIListItemView())
->setName($spec['name'])
->setIcon('fa-envelope-o')
->setHref($href);
$items[] = $item;
}
}
return $items;
}
public function getHelpDocumentationArticles(PhabricatorUser $viewer) {
return array();
}
public function getOverview() {
return null;
}
public function getEventListeners() {
return array();
}
public function getRemarkupRules() {
return array();
}
public function getQuicksandURIPatternBlacklist() {
return array();
}
public function getMailCommandObjects() {
return array();
}
/* -( URI Routing )-------------------------------------------------------- */
public function getRoutes() {
return array();
}
public function getResourceRoutes() {
return array();
}
/* -( Email Integration )-------------------------------------------------- */
public function supportsEmailIntegration() {
return false;
}
final protected function getInboundEmailSupportLink() {
return PhabricatorEnv::getDocLink('Configuring Inbound Email');
}
public function getAppEmailBlurb() {
throw new PhutilMethodNotImplementedException();
}
/* -( Fact Integration )--------------------------------------------------- */
public function getFactObjectsForAnalysis() {
return array();
}
/* -( UI Integration )----------------------------------------------------- */
/**
* Render status elements (like "3 Waiting Reviews") for application list
* views. These provide a way to alert users to new or pending action items
* in applications.
*
* @param PhabricatorUser Viewing user.
* @return list<PhabricatorApplicationStatusView> Application status elements.
* @task ui
*/
public function loadStatus(PhabricatorUser $user) {
return array();
}
/**
* You can provide an optional piece of flavor text for the application. This
* is currently rendered in application launch views if the application has no
* status elements.
*
* @return string|null Flavor text.
* @task ui
*/
public function getFlavorText() {
return null;
}
/**
* Build items for the main menu.
*
* @param PhabricatorUser The viewing user.
* @param AphrontController The current controller. May be null for special
* pages like 404, exception handlers, etc.
* @return list<PHUIListItemView> List of menu items.
* @task ui
*/
public function buildMainMenuItems(
PhabricatorUser $user,
PhabricatorController $controller = null) {
return array();
}
/**
* Build extra items for the main menu. Generally, this is used to render
* static dropdowns.
*
* @param PhabricatorUser The viewing user.
* @param AphrontController The current controller. May be null for special
* pages like 404, exception handlers, etc.
* @return view List of menu items.
* @task ui
*/
public function buildMainMenuExtraNodes(
PhabricatorUser $viewer,
PhabricatorController $controller = null) {
return array();
}
/**
* Build items for the "quick create" menu.
*
* @param PhabricatorUser The viewing user.
* @return list<PHUIListItemView> List of menu items.
*/
public function getQuickCreateItems(PhabricatorUser $viewer) {
return array();
}
/* -( Application Management )--------------------------------------------- */
final public static function getByClass($class_name) {
$selected = null;
$applications = self::getAllApplications();
foreach ($applications as $application) {
if (get_class($application) == $class_name) {
$selected = $application;
break;
}
}
if (!$selected) {
throw new Exception(pht("No application '%s'!", $class_name));
}
return $selected;
}
final public static function getAllApplications() {
static $applications;
if ($applications === null) {
$apps = id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setSortMethod('getApplicationOrder')
->execute();
// Reorder the applications into "application order". Notably, this
// ensures their event handlers register in application order.
$apps = mgroup($apps, 'getApplicationGroup');
$group_order = array_keys(self::getApplicationGroups());
$apps = array_select_keys($apps, $group_order) + $apps;
$apps = array_mergev($apps);
$applications = $apps;
}
return $applications;
}
final public static function getAllInstalledApplications() {
$all_applications = self::getAllApplications();
$apps = array();
foreach ($all_applications as $app) {
if (!$app->isInstalled()) {
continue;
}
$apps[] = $app;
}
return $apps;
}
/**
* Determine if an application is installed, by application class name.
*
* To check if an application is installed //and// available to a particular
* viewer, user @{method:isClassInstalledForViewer}.
*
* @param string Application class name.
* @return bool True if the class is installed.
* @task meta
*/
final public static function isClassInstalled($class) {
return self::getByClass($class)->isInstalled();
}
/**
* Determine if an application is installed and available to a viewer, by
* application class name.
*
* To check if an application is installed at all, use
* @{method:isClassInstalled}.
*
* @param string Application class name.
* @param PhabricatorUser Viewing user.
* @return bool True if the class is installed for the viewer.
* @task meta
*/
final public static function isClassInstalledForViewer(
$class,
PhabricatorUser $viewer) {
if ($viewer->isOmnipotent()) {
return true;
}
$cache = PhabricatorCaches::getRequestCache();
$viewer_phid = $viewer->getPHID();
$key = 'app.'.$class.'.installed.'.$viewer_phid;
$result = $cache->getKey($key);
if ($result === null) {
if (!self::isClassInstalled($class)) {
$result = false;
} else {
$result = PhabricatorPolicyFilter::hasCapability(
$viewer,
self::getByClass($class),
PhabricatorPolicyCapability::CAN_VIEW);
}
$cache->setKey($key, $result);
}
return $result;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array_merge(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
),
array_keys($this->getCustomCapabilities()));
}
public function getPolicy($capability) {
$default = $this->getCustomPolicySetting($capability);
if ($default) {
return $default;
}
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return PhabricatorPolicies::getMostOpenPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
return PhabricatorPolicies::POLICY_ADMIN;
default:
$spec = $this->getCustomCapabilitySpecification($capability);
return idx($spec, 'default', PhabricatorPolicies::POLICY_USER);
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
/* -( Policies )----------------------------------------------------------- */
protected function getCustomCapabilities() {
return array();
}
final private function getCustomPolicySetting($capability) {
if (!$this->isCapabilityEditable($capability)) {
return null;
}
$policy_locked = PhabricatorEnv::getEnvConfig('policy.locked');
if (isset($policy_locked[$capability])) {
return $policy_locked[$capability];
}
$config = PhabricatorEnv::getEnvConfig('phabricator.application-settings');
$app = idx($config, $this->getPHID());
if (!$app) {
return null;
}
$policy = idx($app, 'policy');
if (!$policy) {
return null;
}
return idx($policy, $capability);
}
final private function getCustomCapabilitySpecification($capability) {
$custom = $this->getCustomCapabilities();
if (!isset($custom[$capability])) {
throw new Exception(pht("Unknown capability '%s'!", $capability));
}
return $custom[$capability];
}
final public function getCapabilityLabel($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return pht('Can Use Application');
case PhabricatorPolicyCapability::CAN_EDIT:
return pht('Can Configure Application');
}
$capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
if ($capobj) {
return $capobj->getCapabilityName();
}
return null;
}
final public function isCapabilityEditable($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->canUninstall();
case PhabricatorPolicyCapability::CAN_EDIT:
return false;
default:
$spec = $this->getCustomCapabilitySpecification($capability);
return idx($spec, 'edit', true);
}
}
final public function getCapabilityCaption($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
if (!$this->canUninstall()) {
return pht(
'This application is required for Phabricator to operate, so all '.
'users must have access to it.');
} else {
return null;
}
case PhabricatorPolicyCapability::CAN_EDIT:
return null;
default:
$spec = $this->getCustomCapabilitySpecification($capability);
return idx($spec, 'caption');
}
}
final public function getCapabilityTemplatePHIDType($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
case PhabricatorPolicyCapability::CAN_EDIT:
return null;
}
$spec = $this->getCustomCapabilitySpecification($capability);
return idx($spec, 'template');
}
final public function getDefaultObjectTypePolicyMap() {
$map = array();
foreach ($this->getCustomCapabilities() as $capability => $spec) {
if (empty($spec['template'])) {
continue;
}
if (empty($spec['capability'])) {
continue;
}
$default = $this->getPolicy($capability);
$map[$spec['template']][$spec['capability']] = $default;
}
return $map;
}
public function getApplicationSearchDocumentTypes() {
return array();
}
protected function getEditRoutePattern($base = null) {
return $base.'(?:'.
'(?P<id>[0-9]\d*)/)?'.
'(?:'.
'(?:'.
'(?P<editAction>parameters|nodefault|nocreate|nomanage|comment)'.
'|'.
'(?:form/(?P<formKey>[^/]+))'.
')'.
'/)?';
}
protected function getQueryRoutePattern($base = null) {
return $base.'(?:query/(?P<queryKey>[^/]+)/)?';
}
protected function getPanelRouting($controller) {
$edit_route = $this->getEditRoutePattern();
return array(
'(?P<panelAction>view)/(?P<panelID>[^/]+)/' => $controller,
'(?P<panelAction>hide)/(?P<panelID>[^/]+)/' => $controller,
+ '(?P<panelAction>default)/(?P<panelID>[^/]+)/' => $controller,
'(?P<panelAction>configure)/' => $controller,
'(?P<panelAction>reorder)/' => $controller,
'(?P<panelAction>edit)/'.$edit_route => $controller,
'(?P<panelAction>new)/(?<panelKey>[^/]+)/'.$edit_route => $controller,
'(?P<panelAction>builtin)/(?<panelID>[^/]+)/'.$edit_route
=> $controller,
);
}
}
diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php
index 571d5b866d..1b2d9d65e2 100644
--- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php
+++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php
@@ -1,789 +1,788 @@
<?php
final class PhabricatorProjectBoardViewController
extends PhabricatorProjectBoardController {
const BATCH_EDIT_ALL = 'all';
private $id;
private $slug;
private $handles;
private $queryKey;
private $filter;
private $sortKey;
private $showHidden;
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getUser();
$id = $request->getURIData('id');
$show_hidden = $request->getBool('hidden');
$this->showHidden = $show_hidden;
$project = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->needImages(true);
$id = $request->getURIData('id');
$slug = $request->getURIData('slug');
if ($slug) {
$project->withSlugs(array($slug));
} else {
$project->withIDs(array($id));
}
$project = $project->executeOne();
if (!$project) {
return new Aphront404Response();
}
$this->setProject($project);
$this->id = $project->getID();
$sort_key = $request->getStr('order');
switch ($sort_key) {
case PhabricatorProjectColumn::ORDER_NATURAL:
case PhabricatorProjectColumn::ORDER_PRIORITY:
break;
default:
$sort_key = PhabricatorProjectColumn::DEFAULT_ORDER;
break;
}
$this->sortKey = $sort_key;
$column_query = id(new PhabricatorProjectColumnQuery())
->setViewer($viewer)
->withProjectPHIDs(array($project->getPHID()));
if (!$show_hidden) {
$column_query->withStatuses(
array(PhabricatorProjectColumn::STATUS_ACTIVE));
}
$columns = $column_query->execute();
$columns = mpull($columns, null, 'getSequence');
// TODO: Expand the checks here if we add the ability
// to hide the Backlog column
if (!$columns) {
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_EDIT);
if (!$can_edit) {
return $this->noAccessDialog($project);
}
switch ($request->getStr('initialize-type')) {
case 'backlog-only':
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$column = PhabricatorProjectColumn::initializeNewColumn($viewer)
->setSequence(0)
->setProperty('isDefault', true)
->setProjectPHID($project->getPHID())
->save();
$column->attachProject($project);
$columns[0] = $column;
unset($unguarded);
break;
case 'import':
return id(new AphrontRedirectResponse())
->setURI(
$this->getApplicationURI('board/'.$project->getID().'/import/'));
break;
default:
return $this->initializeWorkboardDialog($project);
break;
}
}
ksort($columns);
$board_uri = $this->getApplicationURI('board/'.$project->getID().'/');
$engine = id(new ManiphestTaskSearchEngine())
->setViewer($viewer)
->setBaseURI($board_uri)
->setIsBoardView(true);
if ($request->isFormPost()) {
$saved = $engine->buildSavedQueryFromRequest($request);
$engine->saveQuery($saved);
$filter_form = id(new AphrontFormView())
->setUser($viewer);
$engine->buildSearchForm($filter_form, $saved);
if ($engine->getErrors()) {
return $this->newDialog()
->setWidth(AphrontDialogView::WIDTH_FULL)
->setTitle(pht('Advanced Filter'))
->appendChild($filter_form->buildLayoutView())
->setErrors($engine->getErrors())
->setSubmitURI($board_uri)
->addSubmitButton(pht('Apply Filter'))
->addCancelButton($board_uri);
}
return id(new AphrontRedirectResponse())->setURI(
$this->getURIWithState(
$engine->getQueryResultsPageURI($saved->getQueryKey())));
}
$query_key = $request->getURIData('queryKey');
if (!$query_key) {
$query_key = 'open';
}
$this->queryKey = $query_key;
$custom_query = null;
if ($engine->isBuiltinQuery($query_key)) {
$saved = $engine->buildSavedQueryFromBuiltin($query_key);
} else {
$saved = id(new PhabricatorSavedQueryQuery())
->setViewer($viewer)
->withQueryKeys(array($query_key))
->executeOne();
if (!$saved) {
return new Aphront404Response();
}
$custom_query = $saved;
}
if ($request->getURIData('filter')) {
$filter_form = id(new AphrontFormView())
->setUser($viewer);
$engine->buildSearchForm($filter_form, $saved);
return $this->newDialog()
->setWidth(AphrontDialogView::WIDTH_FULL)
->setTitle(pht('Advanced Filter'))
->appendChild($filter_form->buildLayoutView())
->setSubmitURI($board_uri)
->addSubmitButton(pht('Apply Filter'))
->addCancelButton($board_uri);
}
$task_query = $engine->buildQueryFromSavedQuery($saved);
$tasks = $task_query
->withEdgeLogicPHIDs(
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
PhabricatorQueryConstraint::OPERATOR_AND,
array($project->getPHID()))
->setOrder(ManiphestTaskQuery::ORDER_PRIORITY)
->setViewer($viewer)
->execute();
$tasks = mpull($tasks, null, 'getPHID');
if ($tasks) {
$positions = id(new PhabricatorProjectColumnPositionQuery())
->setViewer($viewer)
->withObjectPHIDs(mpull($tasks, 'getPHID'))
->withColumns($columns)
->execute();
$positions = mpull($positions, null, 'getObjectPHID');
} else {
$positions = array();
}
$task_map = array();
foreach ($tasks as $task) {
$task_phid = $task->getPHID();
if (empty($positions[$task_phid])) {
// This shouldn't normally be possible because we create positions on
// demand, but we might have raced as an object was removed from the
// board. Just drop the task if we don't have a position for it.
continue;
}
$position = $positions[$task_phid];
$task_map[$position->getColumnPHID()][] = $task_phid;
}
// If we're showing the board in "natural" order, sort columns by their
// column positions.
if ($this->sortKey == PhabricatorProjectColumn::ORDER_NATURAL) {
foreach ($task_map as $column_phid => $task_phids) {
$order = array();
foreach ($task_phids as $task_phid) {
if (isset($positions[$task_phid])) {
$order[$task_phid] = $positions[$task_phid]->getOrderingKey();
} else {
$order[$task_phid] = 0;
}
}
asort($order);
$task_map[$column_phid] = array_keys($order);
}
}
$task_can_edit_map = id(new PhabricatorPolicyFilter())
->setViewer($viewer)
->requireCapabilities(array(PhabricatorPolicyCapability::CAN_EDIT))
->apply($tasks);
// If this is a batch edit, select the editable tasks in the chosen column
// and ship the user into the batch editor.
$batch_edit = $request->getStr('batch');
if ($batch_edit) {
if ($batch_edit !== self::BATCH_EDIT_ALL) {
$column_id_map = mpull($columns, null, 'getID');
$batch_column = idx($column_id_map, $batch_edit);
if (!$batch_column) {
return new Aphront404Response();
}
$batch_task_phids = idx($task_map, $batch_column->getPHID(), array());
foreach ($batch_task_phids as $key => $batch_task_phid) {
if (empty($task_can_edit_map[$batch_task_phid])) {
unset($batch_task_phids[$key]);
}
}
$batch_tasks = array_select_keys($tasks, $batch_task_phids);
} else {
$batch_tasks = $task_can_edit_map;
}
if (!$batch_tasks) {
$cancel_uri = $this->getURIWithState($board_uri);
return $this->newDialog()
->setTitle(pht('No Editable Tasks'))
->appendParagraph(
pht(
'The selected column contains no visible tasks which you '.
'have permission to edit.'))
->addCancelButton($board_uri);
}
$batch_ids = mpull($batch_tasks, 'getID');
$batch_ids = implode(',', $batch_ids);
$batch_uri = new PhutilURI('/maniphest/batch/');
$batch_uri->setQueryParam('board', $this->id);
$batch_uri->setQueryParam('batch', $batch_ids);
return id(new AphrontRedirectResponse())
->setURI($batch_uri);
}
$board_id = celerity_generate_unique_node_id();
$board = id(new PHUIWorkboardView())
->setUser($viewer)
->setID($board_id);
$behavior_config = array(
'boardID' => $board_id,
'projectPHID' => $project->getPHID(),
'moveURI' => $this->getApplicationURI('move/'.$project->getID().'/'),
'createURI' => $this->getCreateURI(),
'order' => $this->sortKey,
);
$this->initBehavior(
'project-boards',
$behavior_config);
$this->handles = ManiphestTaskListView::loadTaskHandles($viewer, $tasks);
foreach ($columns as $column) {
$task_phids = idx($task_map, $column->getPHID(), array());
$column_tasks = array_select_keys($tasks, $task_phids);
$panel = id(new PHUIWorkpanelView())
->setHeader($column->getDisplayName())
->setSubHeader($column->getDisplayType())
->addSigil('workpanel');
$header_icon = $column->getHeaderIcon();
if ($header_icon) {
$panel->setHeaderIcon($header_icon);
}
if ($column->isHidden()) {
$panel->addClass('project-panel-hidden');
}
$column_menu = $this->buildColumnMenu($project, $column);
$panel->addHeaderAction($column_menu);
$tag_id = celerity_generate_unique_node_id();
$tag_content_id = celerity_generate_unique_node_id();
$count_tag = id(new PHUITagView())
->setType(PHUITagView::TYPE_SHADE)
->setShade(PHUITagView::COLOR_BLUE)
->setID($tag_id)
->setName(phutil_tag('span', array('id' => $tag_content_id), '-'))
->setStyle('display: none');
$panel->setHeaderTag($count_tag);
$cards = id(new PHUIObjectItemListView())
->setUser($viewer)
->setFlush(true)
->setAllowEmptyList(true)
->addSigil('project-column')
->setMetadata(
array(
'columnPHID' => $column->getPHID(),
'countTagID' => $tag_id,
'countTagContentID' => $tag_content_id,
'pointLimit' => $column->getPointLimit(),
));
foreach ($column_tasks as $task) {
$owner = null;
if ($task->getOwnerPHID()) {
$owner = $this->handles[$task->getOwnerPHID()];
}
$can_edit = idx($task_can_edit_map, $task->getPHID(), false);
$cards->addItem(id(new ProjectBoardTaskCard())
->setViewer($viewer)
->setTask($task)
->setOwner($owner)
->setCanEdit($can_edit)
->getItem());
}
$panel->setCards($cards);
$board->addPanel($panel);
}
$sort_menu = $this->buildSortMenu(
$viewer,
$sort_key);
$filter_menu = $this->buildFilterMenu(
$viewer,
$custom_query,
$engine,
$query_key);
$manage_menu = $this->buildManageMenu($project, $show_hidden);
$header_link = phutil_tag(
'a',
array(
'href' => $this->getApplicationURI('profile/'.$project->getID().'/'),
),
$project->getName());
$header = id(new PHUIHeaderView())
->setHeader($header_link)
->setUser($viewer)
->setNoBackground(true)
->addActionLink($sort_menu)
->addActionLink($filter_menu)
->addActionLink($manage_menu)
->setPolicyObject($project);
$header_box = id(new PHUIBoxView())
->appendChild($header)
->addClass('project-board-header');
$board_box = id(new PHUIBoxView())
->appendChild($board)
->addClass('project-board-wrapper');
$nav = $this->getProfileMenu();
return $this->newPage()
->setTitle(pht('%s Board', $project->getName()))
->setPageObjectPHIDs(array($project->getPHID()))
->setShowFooter(false)
->setNavigation($nav)
->addQuicksandConfig(
array(
'boardConfig' => $behavior_config,
))
->appendChild(
array(
$header_box,
$board_box,
));
}
private function buildSortMenu(
PhabricatorUser $viewer,
$sort_key) {
$sort_icon = id(new PHUIIconView())
->setIconFont('fa-sort-amount-asc bluegrey');
$named = array(
PhabricatorProjectColumn::ORDER_NATURAL => pht('Natural'),
PhabricatorProjectColumn::ORDER_PRIORITY => pht('Sort by Priority'),
);
$base_uri = $this->getURIWithState();
$items = array();
foreach ($named as $key => $name) {
$is_selected = ($key == $sort_key);
if ($is_selected) {
$active_order = $name;
}
$item = id(new PhabricatorActionView())
->setIcon('fa-sort-amount-asc')
->setSelected($is_selected)
->setName($name);
$uri = $base_uri->alter('order', $key);
$item->setHref($uri);
$items[] = $item;
}
$sort_menu = id(new PhabricatorActionListView())
->setUser($viewer);
foreach ($items as $item) {
$sort_menu->addAction($item);
}
$sort_button = id(new PHUIButtonView())
->setText(pht('Sort: %s', $active_order))
->setIcon($sort_icon)
->setTag('a')
->setHref('#')
->addSigil('boards-dropdown-menu')
->setMetadata(
array(
'items' => hsprintf('%s', $sort_menu),
));
return $sort_button;
}
private function buildFilterMenu(
PhabricatorUser $viewer,
$custom_query,
PhabricatorApplicationSearchEngine $engine,
$query_key) {
$filter_icon = id(new PHUIIconView())
->setIconFont('fa-search-plus bluegrey');
$named = array(
'open' => pht('Open Tasks'),
'all' => pht('All Tasks'),
);
if ($viewer->isLoggedIn()) {
$named['assigned'] = pht('Assigned to Me');
}
if ($custom_query) {
$named[$custom_query->getQueryKey()] = pht('Custom Filter');
}
$items = array();
foreach ($named as $key => $name) {
$is_selected = ($key == $query_key);
if ($is_selected) {
$active_filter = $name;
}
$is_custom = false;
if ($custom_query) {
$is_custom = ($key == $custom_query->getQueryKey());
}
$item = id(new PhabricatorActionView())
->setIcon('fa-search')
->setSelected($is_selected)
->setName($name);
if ($is_custom) {
$uri = $this->getApplicationURI(
'board/'.$this->id.'/filter/query/'.$key.'/');
$item->setWorkflow(true);
} else {
$uri = $engine->getQueryResultsPageURI($key);
}
$uri = $this->getURIWithState($uri);
$item->setHref($uri);
$items[] = $item;
}
$items[] = id(new PhabricatorActionView())
->setIcon('fa-cog')
->setHref($this->getApplicationURI('board/'.$this->id.'/filter/'))
->setWorkflow(true)
->setName(pht('Advanced Filter...'));
$filter_menu = id(new PhabricatorActionListView())
->setUser($viewer);
foreach ($items as $item) {
$filter_menu->addAction($item);
}
$filter_button = id(new PHUIButtonView())
->setText(pht('Filter: %s', $active_filter))
->setIcon($filter_icon)
->setTag('a')
->setHref('#')
->addSigil('boards-dropdown-menu')
->setMetadata(
array(
'items' => hsprintf('%s', $filter_menu),
));
return $filter_button;
}
private function buildManageMenu(
PhabricatorProject $project,
$show_hidden) {
$request = $this->getRequest();
$viewer = $request->getUser();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_EDIT);
$manage_icon = id(new PHUIIconView())
->setIconFont('fa-cog bluegrey');
$manage_items = array();
$manage_items[] = id(new PhabricatorActionView())
->setIcon('fa-plus')
->setName(pht('Add Column'))
->setHref($this->getApplicationURI('board/'.$this->id.'/edit/'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit);
$manage_items[] = id(new PhabricatorActionView())
->setIcon('fa-exchange')
->setName(pht('Reorder Columns'))
->setHref($this->getApplicationURI('board/'.$this->id.'/reorder/'))
->setDisabled(!$can_edit)
->setWorkflow(true);
if ($show_hidden) {
$hidden_uri = $this->getURIWithState()
->setQueryParam('hidden', null);
$hidden_icon = 'fa-eye-slash';
$hidden_text = pht('Hide Hidden Columns');
} else {
$hidden_uri = $this->getURIWithState()
->setQueryParam('hidden', 'true');
$hidden_icon = 'fa-eye';
$hidden_text = pht('Show Hidden Columns');
}
$manage_items[] = id(new PhabricatorActionView())
->setIcon($hidden_icon)
->setName($hidden_text)
->setHref($hidden_uri);
$batch_edit_uri = $request->getRequestURI();
$batch_edit_uri->setQueryParam('batch', self::BATCH_EDIT_ALL);
$can_batch_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
PhabricatorApplication::getByClass('PhabricatorManiphestApplication'),
ManiphestBulkEditCapability::CAPABILITY);
$manage_items[] = id(new PhabricatorActionView())
->setIcon('fa-list-ul')
->setName(pht('Batch Edit Visible Tasks...'))
->setHref($batch_edit_uri)
->setDisabled(!$can_batch_edit);
$manage_menu = id(new PhabricatorActionListView())
->setUser($viewer);
foreach ($manage_items as $item) {
$manage_menu->addAction($item);
}
$manage_button = id(new PHUIButtonView())
->setText(pht('Manage Board'))
->setIcon($manage_icon)
->setTag('a')
->setHref('#')
->addSigil('boards-dropdown-menu')
->setMetadata(
array(
'items' => hsprintf('%s', $manage_menu),
));
return $manage_button;
}
private function buildColumnMenu(
PhabricatorProject $project,
PhabricatorProjectColumn $column) {
$request = $this->getRequest();
$viewer = $request->getUser();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_EDIT);
$column_items = array();
$column_items[] = id(new PhabricatorActionView())
->setIcon('fa-plus')
->setName(pht('Create Task...'))
->setHref($this->getCreateURI())
->addSigil('column-add-task')
->setMetadata(
array(
'columnPHID' => $column->getPHID(),
));
$batch_edit_uri = $request->getRequestURI();
$batch_edit_uri->setQueryParam('batch', $column->getID());
$can_batch_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
PhabricatorApplication::getByClass('PhabricatorManiphestApplication'),
ManiphestBulkEditCapability::CAPABILITY);
$column_items[] = id(new PhabricatorActionView())
->setIcon('fa-list-ul')
->setName(pht('Batch Edit Tasks...'))
->setHref($batch_edit_uri)
->setDisabled(!$can_batch_edit);
$detail_uri = $this->getApplicationURI(
'board/'.$this->id.'/column/'.$column->getID().'/');
$column_items[] = id(new PhabricatorActionView())
->setIcon('fa-columns')
->setName(pht('Column Details'))
->setHref($detail_uri);
$can_hide = ($can_edit && !$column->isDefaultColumn());
$hide_uri = 'board/'.$this->id.'/hide/'.$column->getID().'/';
$hide_uri = $this->getApplicationURI($hide_uri);
$hide_uri = $this->getURIWithState($hide_uri);
if (!$column->isHidden()) {
$column_items[] = id(new PhabricatorActionView())
->setName(pht('Hide Column'))
->setIcon('fa-eye-slash')
->setHref($hide_uri)
->setDisabled(!$can_hide)
->setWorkflow(true);
} else {
$column_items[] = id(new PhabricatorActionView())
->setName(pht('Show Column'))
->setIcon('fa-eye')
->setHref($hide_uri)
->setDisabled(!$can_hide)
->setWorkflow(true);
}
$column_menu = id(new PhabricatorActionListView())
->setUser($viewer);
foreach ($column_items as $item) {
$column_menu->addAction($item);
}
$column_button = id(new PHUIIconView())
->setIconFont('fa-caret-down')
->setHref('#')
->addSigil('boards-dropdown-menu')
->setMetadata(
array(
'items' => hsprintf('%s', $column_menu),
));
return $column_button;
}
private function initializeWorkboardDialog(PhabricatorProject $project) {
$instructions = pht('This workboard has not been setup yet.');
$new_selector = id(new AphrontFormRadioButtonControl())
->setName('initialize-type')
->setValue('backlog-only')
->addButton(
'backlog-only',
pht('New Empty Board'),
pht('Create a new board with just a backlog column.'))
->addButton(
'import',
pht('Import Columns'),
pht('Import board columns from another project.'));
- $dialog = id(new AphrontDialogView())
- ->setUser($this->getRequest()->getUser())
+
+ $cancel_uri = $this->getApplicationURI('profile/'.$project->getID().'/');
+
+ return $this->newDialog()
->setTitle(pht('New Workboard'))
->addSubmitButton('Continue')
- ->addCancelButton($this->getApplicationURI('view/'.$project->getID().'/'))
+ ->addCancelButton($cancel_uri)
->appendParagraph($instructions)
->appendChild($new_selector);
-
- return id(new AphrontDialogResponse())
- ->setDialog($dialog);
}
private function noAccessDialog(PhabricatorProject $project) {
$instructions = pht('This workboard has not been setup yet.');
$dialog = id(new AphrontDialogView())
->setUser($this->getRequest()->getUser())
->setTitle(pht('No Workboard'))
->addCancelButton($this->getApplicationURI('view/'.$project->getID().'/'))
->appendParagraph($instructions);
return id(new AphrontDialogResponse())
->setDialog($dialog);
}
/**
* Add current state parameters (like order and the visibility of hidden
* columns) to a URI.
*
* This allows actions which toggle or adjust one piece of state to keep
* the rest of the board state persistent. If no URI is provided, this method
* starts with the request URI.
*
* @param string|null URI to add state parameters to.
* @return PhutilURI URI with state parameters.
*/
private function getURIWithState($base = null) {
if ($base === null) {
$base = $this->getRequest()->getRequestURI();
}
$base = new PhutilURI($base);
if ($this->sortKey != PhabricatorProjectColumn::DEFAULT_ORDER) {
$base->setQueryParam('order', $this->sortKey);
} else {
$base->setQueryParam('order', null);
}
$base->setQueryParam('hidden', $this->showHidden ? 'true' : null);
return $base;
}
private function getCreateURI() {
$viewer = $this->getViewer();
// TODO: This should be cleaned up, but maybe we're going to make options
// for each column or board?
$edit_config = id(new ManiphestEditEngine())
->setViewer($viewer)
->loadDefaultEditConfiguration();
if ($edit_config) {
$form_key = $edit_config->getIdentifier();
$create_uri = "/maniphest/task/edit/form/{$form_key}/";
} else {
$create_uri = '/maniphest/task/edit/';
}
return $create_uri;
}
}
diff --git a/src/applications/project/controller/PhabricatorProjectViewController.php b/src/applications/project/controller/PhabricatorProjectViewController.php
index 087971878b..412565c056 100644
--- a/src/applications/project/controller/PhabricatorProjectViewController.php
+++ b/src/applications/project/controller/PhabricatorProjectViewController.php
@@ -1,43 +1,36 @@
<?php
final class PhabricatorProjectViewController
extends PhabricatorProjectController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$request = $this->getRequest();
$viewer = $request->getViewer();
$response = $this->loadProject();
if ($response) {
return $response;
}
$project = $this->getProject();
- $columns = id(new PhabricatorProjectColumnQuery())
- ->setViewer($viewer)
- ->withProjectPHIDs(array($project->getPHID()))
- ->execute();
- if ($columns) {
- $controller = 'board';
- } else {
- $controller = 'profile';
- }
+ $engine = $this->getProfilePanelEngine();
+ $default = $engine->getDefaultPanel();
- switch ($controller) {
- case 'board':
+ switch ($default->getBuiltinKey()) {
+ case PhabricatorProject::PANEL_WORKBOARD:
$controller_object = new PhabricatorProjectBoardViewController();
break;
- case 'profile':
+ case PhabricatorProject::PANEL_PROFILE:
default:
$controller_object = new PhabricatorProjectProfileController();
break;
}
return $this->delegateToController($controller_object);
}
}
diff --git a/src/applications/project/profilepanel/PhabricatorProjectDetailsProfilePanel.php b/src/applications/project/profilepanel/PhabricatorProjectDetailsProfilePanel.php
index 97ba3a1aaa..07e6ce4c10 100644
--- a/src/applications/project/profilepanel/PhabricatorProjectDetailsProfilePanel.php
+++ b/src/applications/project/profilepanel/PhabricatorProjectDetailsProfilePanel.php
@@ -1,59 +1,64 @@
<?php
final class PhabricatorProjectDetailsProfilePanel
extends PhabricatorProfilePanel {
const PANELKEY = 'project.details';
public function getPanelTypeName() {
return pht('Project Details');
}
private function getDefaultName() {
return pht('Project Details');
}
+ public function canMakeDefault(
+ PhabricatorProfilePanelConfiguration $config) {
+ return true;
+ }
+
public function getDisplayName(
PhabricatorProfilePanelConfiguration $config) {
$name = $config->getPanelProperty('name');
if (strlen($name)) {
return $name;
}
return $this->getDefaultName();
}
public function buildEditEngineFields(
PhabricatorProfilePanelConfiguration $config) {
return array(
id(new PhabricatorTextEditField())
->setKey('name')
->setLabel(pht('Name'))
->setPlaceholder($this->getDefaultName())
->setValue($config->getPanelProperty('name')),
);
}
protected function newNavigationMenuItems(
PhabricatorProfilePanelConfiguration $config) {
$project = $config->getProfileObject();
$id = $project->getID();
$picture = $project->getProfileImageURI();
$name = $project->getName();
$href = "/project/profile/{$id}/";
$item = $this->newItem()
->setHref($href)
->setName($name)
->setProfileImage($picture);
return array(
$item,
);
}
}
diff --git a/src/applications/project/profilepanel/PhabricatorProjectWorkboardProfilePanel.php b/src/applications/project/profilepanel/PhabricatorProjectWorkboardProfilePanel.php
index 02b54537e5..f8b2abfc2f 100644
--- a/src/applications/project/profilepanel/PhabricatorProjectWorkboardProfilePanel.php
+++ b/src/applications/project/profilepanel/PhabricatorProjectWorkboardProfilePanel.php
@@ -1,74 +1,79 @@
<?php
final class PhabricatorProjectWorkboardProfilePanel
extends PhabricatorProfilePanel {
const PANELKEY = 'project.workboard';
public function getPanelTypeName() {
return pht('Project Workboard');
}
private function getDefaultName() {
return pht('Workboard');
}
+ public function canMakeDefault(
+ PhabricatorProfilePanelConfiguration $config) {
+ return true;
+ }
+
public function getDisplayName(
PhabricatorProfilePanelConfiguration $config) {
$name = $config->getPanelProperty('name');
if (strlen($name)) {
return $name;
}
return $this->getDefaultName();
}
public function buildEditEngineFields(
PhabricatorProfilePanelConfiguration $config) {
return array(
id(new PhabricatorTextEditField())
->setKey('name')
->setLabel(pht('Name'))
->setPlaceholder($this->getDefaultName())
->setValue($config->getPanelProperty('name')),
);
}
protected function newNavigationMenuItems(
PhabricatorProfilePanelConfiguration $config) {
$viewer = $this->getViewer();
// Workboards are only available if Maniphest is installed.
$class = 'PhabricatorManiphestApplication';
if (!PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) {
return array();
}
$project = $config->getProfileObject();
$columns = id(new PhabricatorProjectColumnQuery())
->setViewer($viewer)
->withProjectPHIDs(array($project->getPHID()))
->execute();
if ($columns) {
$icon = 'fa-columns';
} else {
$icon = 'fa-columns grey';
}
$id = $project->getID();
$href = "/project/board/{$id}/";
$name = $this->getDisplayName($config);
$item = $this->newItem()
->setHref($href)
->setName($name)
->setIcon($icon);
return array(
$item,
);
}
}
diff --git a/src/applications/search/engine/PhabricatorProfilePanelEngine.php b/src/applications/search/engine/PhabricatorProfilePanelEngine.php
index 9ab8d6c591..ea4cefce56 100644
--- a/src/applications/search/engine/PhabricatorProfilePanelEngine.php
+++ b/src/applications/search/engine/PhabricatorProfilePanelEngine.php
@@ -1,756 +1,949 @@
<?php
abstract class PhabricatorProfilePanelEngine extends Phobject {
private $viewer;
private $profileObject;
private $panels;
+ private $defaultPanel;
private $controller;
private $navigation;
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
public function setProfileObject($profile_object) {
$this->profileObject = $profile_object;
return $this;
}
public function getProfileObject() {
return $this->profileObject;
}
public function setController(PhabricatorController $controller) {
$this->controller = $controller;
return $this;
}
public function getController() {
return $this->controller;
}
+ private function setDefaultPanel(
+ PhabricatorProfilePanelConfiguration $default_panel) {
+ $this->defaultPanel = $default_panel;
+ return $this;
+ }
+
+ public function getDefaultPanel() {
+ $this->loadPanels();
+ return $this->defaultPanel;
+ }
+
abstract protected function getPanelURI($path);
protected function isPanelEngineConfigurable() {
return PhabricatorEnv::getEnvConfig('phabricator.show-prototypes');
}
public function buildResponse() {
$controller = $this->getController();
$viewer = $controller->getViewer();
$this->setViewer($viewer);
$request = $controller->getRequest();
$panel_action = $request->getURIData('panelAction');
// If the engine is not configurable, don't respond to any of the editing
// or configuration routes.
if (!$this->isPanelEngineConfigurable()) {
switch ($panel_action) {
case 'view':
break;
default:
return new Aphront404Response();
}
}
$panel_id = $request->getURIData('panelID');
$panel_list = $this->loadPanels();
$selected_panel = null;
if (strlen($panel_id)) {
$panel_id_int = (int)$panel_id;
foreach ($panel_list as $panel) {
if ($panel_id_int) {
if ((int)$panel->getID() === $panel_id_int) {
$selected_panel = $panel;
break;
}
}
$builtin_key = $panel->getBuiltinKey();
if ($builtin_key === (string)$panel_id) {
$selected_panel = $panel;
break;
}
}
}
switch ($panel_action) {
case 'view':
case 'info':
case 'hide':
+ case 'default':
case 'builtin':
if (!$selected_panel) {
return new Aphront404Response();
}
break;
}
$navigation = $this->buildNavigation();
$navigation->selectFilter('panel.configure');
$crumbs = $controller->buildApplicationCrumbsForEditEngine();
switch ($panel_action) {
case 'view':
$content = $this->buildPanelViewContent($selected_panel);
break;
case 'configure':
$content = $this->buildPanelConfigureContent($panel_list);
$crumbs->addTextCrumb(pht('Configure Menu'));
break;
case 'reorder':
$content = $this->buildPanelReorderContent($panel_list);
break;
case 'new':
$panel_key = $request->getURIData('panelKey');
$content = $this->buildPanelNewContent($panel_key);
break;
case 'builtin':
$content = $this->buildPanelBuiltinContent($selected_panel);
break;
case 'hide':
$content = $this->buildPanelHideContent($selected_panel);
break;
+ case 'default':
+ $content = $this->buildPanelDefaultContent(
+ $selected_panel,
+ $panel_list);
+ break;
case 'edit':
$content = $this->buildPanelEditContent();
break;
default:
throw new Exception(
pht(
'Unsupported panel action "%s".',
$panel_action));
}
if ($content instanceof AphrontResponse) {
return $content;
}
if ($content instanceof AphrontResponseProducerInterface) {
return $content;
}
return $controller->newPage()
->setTitle(pht('Profile Stuff'))
->setNavigation($navigation)
->setCrumbs($crumbs)
->appendChild($content);
}
public function buildNavigation() {
if ($this->navigation) {
return $this->navigation;
}
$nav = id(new AphrontSideNavFilterView())
->setIsProfileMenu(true)
->setBaseURI(new PhutilURI($this->getPanelURI('')));
$panels = $this->getPanels();
foreach ($panels as $panel) {
if ($panel->isDisabled()) {
continue;
}
$items = $panel->buildNavigationMenuItems();
foreach ($items as $item) {
$this->validateNavigationMenuItem($item);
}
// If the panel produced only a single item which does not otherwise
// have a key, try to automatically assign it a reasonable key. This
// makes selecting the correct item simpler.
if (count($items) == 1) {
$item = head($items);
if ($item->getKey() === null) {
$builtin_key = $panel->getBuiltinKey();
$panel_phid = $panel->getPHID();
if ($builtin_key !== null) {
$item->setKey($builtin_key);
} else if ($panel_phid !== null) {
$item->setKey($panel_phid);
}
}
}
foreach ($items as $item) {
$nav->addMenuItem($item);
}
}
$more_items = $this->newAutomaticMenuItems($nav);
foreach ($more_items as $item) {
$nav->addMenuItem($item);
}
$nav->selectFilter(null);
$this->navigation = $nav;
return $this->navigation;
}
private function getPanels() {
if ($this->panels === null) {
$this->panels = $this->loadPanels();
}
return $this->panels;
}
private function loadPanels() {
$viewer = $this->getViewer();
$object = $this->getProfileObject();
$panels = $this->loadBuiltinProfilePanels();
$stored_panels = id(new PhabricatorProfilePanelConfigurationQuery())
->setViewer($viewer)
->withProfilePHIDs(array($object->getPHID()))
->execute();
// Merge the stored panels into the builtin panels. If a builtin panel has
// a stored version, replace the defaults with the stored changes.
foreach ($stored_panels as $stored_panel) {
$builtin_key = $stored_panel->getBuiltinKey();
if ($builtin_key !== null) {
- $panels[$builtin_key] = $stored_panel;
+ // If this builtin actually exists, replace the builtin with the
+ // stored configuration. Otherwise, we're just going to drop the
+ // stored config: it corresponds to an out-of-date or uninstalled
+ // panel.
+ if (isset($panels[$builtin_key])) {
+ $panels[$builtin_key] = $stored_panel;
+ } else {
+ continue;
+ }
} else {
$panels[] = $stored_panel;
}
}
foreach ($panels as $panel) {
$impl = $panel->getPanel();
$impl->setViewer($viewer);
}
$panels = msort($panels, 'getSortKey');
// Normalize keys since callers shouldn't rely on this array being
// partially keyed.
$panels = array_values($panels);
+
+ // Make sure exactly one valid panel is marked as default.
+ $default = null;
+ $first = null;
+ foreach ($panels as $panel) {
+ if (!$panel->canMakeDefault()) {
+ continue;
+ }
+
+ if ($panel->isDefault()) {
+ $default = $panel;
+ break;
+ }
+
+ if ($first === null) {
+ $first = $panel;
+ }
+ }
+
+ if (!$default) {
+ $default = $first;
+ }
+
+ if ($default) {
+ $this->setDefaultPanel($default);
+ }
+
return $panels;
}
private function loadBuiltinProfilePanels() {
$object = $this->getProfileObject();
$builtins = $this->getBuiltinProfilePanels($object);
$panels = PhabricatorProfilePanel::getAllPanels();
$order = 1;
$map = array();
foreach ($builtins as $builtin) {
$builtin_key = $builtin->getBuiltinKey();
if (!$builtin_key) {
throw new Exception(
pht(
'Object produced a builtin panel with no builtin panel key! '.
'Builtin panels must have a unique key.'));
}
if (isset($map[$builtin_key])) {
throw new Exception(
pht(
'Object produced two panels with the same builtin key ("%s"). '.
'Each panel must have a unique builtin key.',
$builtin_key));
}
$panel_key = $builtin->getPanelKey();
$panel = idx($panels, $panel_key);
if (!$panel) {
throw new Exception(
pht(
'Builtin panel ("%s") specifies a bad panel key ("%s"); there '.
'is no corresponding panel implementation available.',
$builtin_key,
$panel_key));
}
$builtin
->setProfilePHID($object->getPHID())
->attachPanel($panel)
->attachProfileObject($object)
->setPanelOrder($order);
$map[$builtin_key] = $builtin;
$order++;
}
return $map;
}
private function validateNavigationMenuItem($item) {
if (!($item instanceof PHUIListItemView)) {
throw new Exception(
pht(
'Expected buildNavigationMenuItems() to return a list of '.
'PHUIListItemView objects, but got a surprise.'));
}
}
private function newAutomaticMenuItems(AphrontSideNavFilterView $nav) {
$items = array();
// NOTE: We're adding a spacer item for the fixed footer, so that if the
// menu taller than the page content you can still scroll down the page far
// enough to access the last item without the content being obscured by the
// fixed items.
$items[] = id(new PHUIListItemView())
->setHideInApplicationMenu(true)
->addClass('phui-profile-menu-spacer');
if ($this->isPanelEngineConfigurable()) {
$viewer = $this->getViewer();
$object = $this->getProfileObject();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$object,
PhabricatorPolicyCapability::CAN_EDIT);
$expanded_edit_icon = id(new PHUIIconCircleView())
->addClass('phui-list-item-icon')
->addClass('phui-profile-menu-visible-when-expanded')
->setIconFont('fa-pencil');
$collapsed_edit_icon = id(new PHUIIconCircleView())
->addClass('phui-list-item-icon')
->addClass('phui-profile-menu-visible-when-collapsed')
->setIconFont('fa-pencil')
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => pht('Edit Menu'),
'align' => 'E',
));
$items[] = id(new PHUIListItemView())
->setName('Edit Menu')
->setKey('panel.configure')
->addIcon($expanded_edit_icon)
->addIcon($collapsed_edit_icon)
->addClass('phui-profile-menu-footer')
->addClass('phui-profile-menu-footer-1')
->setHref($this->getPanelURI('configure/'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit);
}
$collapse_id = celerity_generate_unique_node_id();
$viewer = $this->getViewer();
$collapse_key =
PhabricatorUserPreferences::PREFERENCE_PROFILE_MENU_COLLAPSED;
$preferences = $viewer->loadPreferences();
$is_collapsed = $preferences->getPreference($collapse_key, false);
if ($is_collapsed) {
$nav->addClass('phui-profile-menu-collapsed');
} else {
$nav->addClass('phui-profile-menu-expanded');
}
if ($viewer->isLoggedIn()) {
$settings_uri = '/settings/adjust/?key='.$collapse_key;
} else {
$settings_uri = null;
}
Javelin::initBehavior(
'phui-profile-menu',
array(
'menuID' => $nav->getMainID(),
'collapseID' => $collapse_id,
'isCollapsed' => (bool)$is_collapsed,
'settingsURI' => $settings_uri,
));
$collapse_icon = id(new PHUIIconCircleView())
->addClass('phui-list-item-icon')
->addClass('phui-profile-menu-visible-when-expanded')
->setIconFont('fa-angle-left');
$expand_icon = id(new PHUIIconCircleView())
->addClass('phui-list-item-icon')
->addClass('phui-profile-menu-visible-when-collapsed')
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => pht('Expand'),
'align' => 'E',
))
->setIconFont('fa-angle-right');
$items[] = id(new PHUIListItemView())
->setName('Collapse')
->addIcon($collapse_icon)
->addIcon($expand_icon)
->setID($collapse_id)
->addClass('phui-profile-menu-footer')
->addClass('phui-profile-menu-footer-2')
->setHideInApplicationMenu(true)
->setHref('#');
return $items;
}
public function getConfigureURI() {
return $this->getPanelURI('configure/');
}
private function buildPanelReorderContent(array $panels) {
$viewer = $this->getViewer();
$object = $this->getProfileObject();
PhabricatorPolicyFilter::requireCapability(
$viewer,
$object,
PhabricatorPolicyCapability::CAN_EDIT);
$controller = $this->getController();
$request = $controller->getRequest();
$request->validateCSRF();
$order = $request->getStrList('order');
$by_builtin = array();
$by_id = array();
foreach ($panels as $key => $panel) {
$id = $panel->getID();
if ($id) {
$by_id[$id] = $key;
continue;
}
$builtin_key = $panel->getBuiltinKey();
if ($builtin_key) {
$by_builtin[$builtin_key] = $key;
continue;
}
}
$key_order = array();
foreach ($order as $order_item) {
if (isset($by_id[$order_item])) {
$key_order[] = $by_id[$order_item];
continue;
}
if (isset($by_builtin[$order_item])) {
$key_order[] = $by_builtin[$order_item];
continue;
}
}
$panels = array_select_keys($panels, $key_order) + $panels;
$type_order =
PhabricatorProfilePanelConfigurationTransaction::TYPE_ORDER;
$order = 1;
foreach ($panels as $panel) {
$xactions = array();
$xactions[] = id(new PhabricatorProfilePanelConfigurationTransaction())
->setTransactionType($type_order)
->setNewValue($order);
$editor = id(new PhabricatorProfilePanelEditor())
->setContentSourceFromRequest($request)
->setActor($viewer)
->setContinueOnMissingFields(true)
->setContinueOnNoEffect(true)
->applyTransactions($panel, $xactions);
$order++;
}
return id(new AphrontRedirectResponse())
->setURI($this->getConfigureURI());
}
private function buildPanelConfigureContent(array $panels) {
$viewer = $this->getViewer();
$object = $this->getProfileObject();
PhabricatorPolicyFilter::requireCapability(
$viewer,
$object,
PhabricatorPolicyCapability::CAN_EDIT);
$list_id = celerity_generate_unique_node_id();
Javelin::initBehavior(
'reorder-profile-menu-items',
array(
'listID' => $list_id,
'orderURI' => $this->getPanelURI('reorder/'),
));
$list = id(new PHUIObjectItemListView())
->setID($list_id);
foreach ($panels as $panel) {
$id = $panel->getID();
$builtin_key = $panel->getBuiltinKey();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$panel,
PhabricatorPolicyCapability::CAN_EDIT);
$item = id(new PHUIObjectItemView());
$name = $panel->getDisplayName();
$type = $panel->getPanelTypeName();
if (!strlen(trim($name))) {
$name = pht('Untitled "%s" Item', $type);
}
$item->setHeader($name);
$item->addAttribute($type);
if ($can_edit) {
$item
->setGrippable(true)
->addSigil('profile-menu-item')
->setMetadata(
array(
'key' => nonempty($id, $builtin_key),
));
+ if ($id) {
+ $default_uri = $this->getPanelURI("default/{$id}/");
+ } else {
+ $default_uri = $this->getPanelURI("default/{$builtin_key}/");
+ }
+
+ if ($panel->isDefault()) {
+ $default_icon = 'fa-thumb-tack green';
+ $default_text = pht('Current Default');
+ } else if ($panel->canMakeDefault()) {
+ $default_icon = 'fa-thumb-tack';
+ $default_text = pht('Make Default');
+ } else {
+ $default_text = null;
+ }
+
+ if ($default_text !== null) {
+ $item->addAction(
+ id(new PHUIListItemView())
+ ->setHref($default_uri)
+ ->setWorkflow(true)
+ ->setName($default_text)
+ ->setIcon($default_icon));
+ }
+
if ($id) {
$item->setHref($this->getPanelURI("edit/{$id}/"));
$hide_uri = $this->getPanelURI("hide/{$id}/");
} else {
$item->setHref($this->getPanelURI("builtin/{$builtin_key}/"));
$hide_uri = $this->getPanelURI("hide/{$builtin_key}/");
}
+ if ($panel->isDisabled()) {
+ $hide_icon = 'fa-plus';
+ $hide_text = pht('Show');
+ } else if ($panel->getBuiltinKey() !== null) {
+ $hide_icon = 'fa-times';
+ $hide_text = pht('Disable');
+ } else {
+ $hide_icon = 'fa-times';
+ $hide_text = pht('Delete');
+ }
+
$item->addAction(
id(new PHUIListItemView())
->setHref($hide_uri)
->setWorkflow(true)
- ->setIcon(pht('fa-eye')));
+ ->setName($hide_text)
+ ->setIcon($hide_icon));
}
if ($panel->isDisabled()) {
$item->setDisabled(true);
- $item->addIcon('fa-times grey', pht('Disabled'));
}
$list->addItem($item);
}
$action_view = id(new PhabricatorActionListView())
->setUser($viewer);
$panel_types = PhabricatorProfilePanel::getAllPanels();
$action_view->addAction(
id(new PhabricatorActionView())
->setLabel(true)
->setName(pht('Add New Menu Item...')));
foreach ($panel_types as $panel_type) {
if (!$panel_type->canAddToObject($object)) {
continue;
}
$panel_key = $panel_type->getPanelKey();
$action_view->addAction(
id(new PhabricatorActionView())
->setIcon($panel_type->getPanelTypeIcon())
->setName($panel_type->getPanelTypeName())
->setHref($this->getPanelURI("new/{$panel_key}/")));
}
$action_view->addAction(
id(new PhabricatorActionView())
->setLabel(true)
->setName(pht('Documentation')));
$action_view->addAction(
id(new PhabricatorActionView())
->setIcon('fa-book')
->setName(pht('TODO: Write Documentation')));
$action_button = id(new PHUIButtonView())
->setTag('a')
->setText(pht('Configure Menu'))
->setHref('#')
->setIconFont('fa-gear')
->setDropdownMenu($action_view);
$header = id(new PHUIHeaderView())
->setHeader(pht('Profile Menu Items'))
->addActionLink($action_button);
$box = id(new PHUIObjectBoxView())
->setHeader($header)
->setObjectList($list);
return $box;
}
private function buildPanelNewContent($panel_key) {
$panel_types = PhabricatorProfilePanel::getAllPanels();
$panel_type = idx($panel_types, $panel_key);
if (!$panel_type) {
return new Aphront404Response();
}
$object = $this->getProfileObject();
if (!$panel_type->canAddToObject($object)) {
return new Aphront404Response();
}
$configuration =
PhabricatorProfilePanelConfiguration::initializeNewPanelConfiguration(
$object,
$panel_type);
$viewer = $this->getViewer();
PhabricatorPolicyFilter::requireCapability(
$viewer,
$configuration,
PhabricatorPolicyCapability::CAN_EDIT);
$controller = $this->getController();
return id(new PhabricatorProfilePanelEditEngine())
->setPanelEngine($this)
->setProfileObject($object)
->setNewPanelConfiguration($configuration)
->setController($controller)
->buildResponse();
}
private function buildPanelEditContent() {
$viewer = $this->getViewer();
$object = $this->getProfileObject();
$controller = $this->getController();
return id(new PhabricatorProfilePanelEditEngine())
->setPanelEngine($this)
->setProfileObject($object)
->setController($controller)
->buildResponse();
}
private function buildPanelBuiltinContent(
PhabricatorProfilePanelConfiguration $configuration) {
// If this builtin panel has already been persisted, redirect to the
// edit page.
$id = $configuration->getID();
if ($id) {
return id(new AphrontRedirectResponse())
->setURI($this->getPanelURI("edit/{$id}/"));
}
// Otherwise, act like we're creating a new panel, we're just starting
// with the builtin template.
$viewer = $this->getViewer();
PhabricatorPolicyFilter::requireCapability(
$viewer,
$configuration,
PhabricatorPolicyCapability::CAN_EDIT);
$object = $this->getProfileObject();
$controller = $this->getController();
return id(new PhabricatorProfilePanelEditEngine())
->setIsBuiltin(true)
->setPanelEngine($this)
->setProfileObject($object)
->setNewPanelConfiguration($configuration)
->setController($controller)
->buildResponse();
}
private function buildPanelHideContent(
PhabricatorProfilePanelConfiguration $configuration) {
$controller = $this->getController();
$request = $controller->getRequest();
$viewer = $this->getViewer();
PhabricatorPolicyFilter::requireCapability(
$viewer,
$configuration,
PhabricatorPolicyCapability::CAN_EDIT);
+ if ($configuration->getBuiltinKey() === null) {
+ $new_value = null;
+
+ $title = pht('Delete Menu Item');
+ $body = pht('Delete this menu item?');
+ $button = pht('Delete Menu Item');
+ } else if ($configuration->isDisabled()) {
+ $new_value = PhabricatorProfilePanelConfiguration::VISIBILITY_VISIBLE;
+
+ $title = pht('Enable Menu Item');
+ $body = pht(
+ 'Enable this menu item? It will appear in the menu again.');
+ $button = pht('Enable Menu Item');
+ } else {
+ $new_value = PhabricatorProfilePanelConfiguration::VISIBILITY_DISABLED;
+
+ $title = pht('Disable Menu Item');
+ $body = pht(
+ 'Disable this menu item? It will no longer appear in the menu, but '.
+ 'you can re-enable it later.');
+ $button = pht('Disable Menu Item');
+ }
+
$v_visibility = $configuration->getVisibility();
if ($request->isFormPost()) {
- $v_visibility = $request->getStr('visibility');
+ if ($new_value === null) {
+ $configuration->delete();
+ } else {
+ $type_visibility =
+ PhabricatorProfilePanelConfigurationTransaction::TYPE_VISIBILITY;
+
+ $xactions = array();
+
+ $xactions[] = id(new PhabricatorProfilePanelConfigurationTransaction())
+ ->setTransactionType($type_visibility)
+ ->setNewValue($new_value);
+
+ $editor = id(new PhabricatorProfilePanelEditor())
+ ->setContentSourceFromRequest($request)
+ ->setActor($viewer)
+ ->setContinueOnMissingFields(true)
+ ->setContinueOnNoEffect(true)
+ ->applyTransactions($configuration, $xactions);
+ }
+
+ return id(new AphrontRedirectResponse())
+ ->setURI($this->getConfigureURI());
+ }
+
+ return $controller->newDialog()
+ ->setTitle($title)
+ ->appendParagraph($body)
+ ->addCancelButton($this->getConfigureURI())
+ ->addSubmitButton($button);
+ }
+
+ private function buildPanelDefaultContent(
+ PhabricatorProfilePanelConfiguration $configuration,
+ array $panels) {
+
+ $controller = $this->getController();
+ $request = $controller->getRequest();
+ $viewer = $this->getViewer();
+
+ PhabricatorPolicyFilter::requireCapability(
+ $viewer,
+ $configuration,
+ PhabricatorPolicyCapability::CAN_EDIT);
+
+ $done_uri = $this->getConfigureURI();
+
+ if (!$configuration->canMakeDefault()) {
+ return $controller->newDialog()
+ ->setTitle(pht('Not Defaultable'))
+ ->appendParagraph(
+ pht(
+ 'This item can not be set as the default item. This is usually '.
+ 'because the item has no page of its own, or links to an '.
+ 'external page.'))
+ ->addCancelButton($done_uri);
+ }
+
+ if ($configuration->isDefault()) {
+ return $controller->newDialog()
+ ->setTitle(pht('Already Default'))
+ ->appendParagraph(
+ pht(
+ 'This item is already set as the default item for this menu.'))
+ ->addCancelButton($done_uri);
+ }
+
+ $type_visibility =
+ PhabricatorProfilePanelConfigurationTransaction::TYPE_VISIBILITY;
+
+ $v_visible = PhabricatorProfilePanelConfiguration::VISIBILITY_VISIBLE;
+ $v_default = PhabricatorProfilePanelConfiguration::VISIBILITY_DEFAULT;
+
+ if ($request->isFormPost()) {
+ // First, mark any existing default panels as merely visible.
+ foreach ($panels as $panel) {
+ if (!$panel->isDefault()) {
+ continue;
+ }
+
+ $xactions = array();
- $type_visibility =
- PhabricatorProfilePanelConfigurationTransaction::TYPE_VISIBILITY;
+ $xactions[] = id(new PhabricatorProfilePanelConfigurationTransaction())
+ ->setTransactionType($type_visibility)
+ ->setNewValue($v_visible);
+
+ $editor = id(new PhabricatorProfilePanelEditor())
+ ->setContentSourceFromRequest($request)
+ ->setActor($viewer)
+ ->setContinueOnMissingFields(true)
+ ->setContinueOnNoEffect(true)
+ ->applyTransactions($panel, $xactions);
+ }
+ // Now, make this panel the default.
$xactions = array();
$xactions[] = id(new PhabricatorProfilePanelConfigurationTransaction())
->setTransactionType($type_visibility)
- ->setNewValue($v_visibility);
+ ->setNewValue($v_default);
$editor = id(new PhabricatorProfilePanelEditor())
->setContentSourceFromRequest($request)
->setActor($viewer)
->setContinueOnMissingFields(true)
->setContinueOnNoEffect(true)
->applyTransactions($configuration, $xactions);
return id(new AphrontRedirectResponse())
- ->setURI($this->getConfigureURI());
+ ->setURI($done_uri);
}
- $map = PhabricatorProfilePanelConfiguration::getVisibilityNameMap();
-
- $form = id(new AphrontFormView())
- ->setUser($viewer)
- ->appendControl(
- id(new AphrontFormSelectControl())
- ->setName('visibility')
- ->setLabel(pht('Visibility'))
- ->setValue($v_visibility)
- ->setOptions($map));
-
return $controller->newDialog()
- ->setTitle(pht('Change Item Visibility'))
- ->appendForm($form)
- ->addCancelButton($this->getConfigureURI())
- ->addSubmitButton(pht('Save Changes'));
+ ->setTitle(pht('Make Default'))
+ ->appendParagraph(
+ pht(
+ 'Set this item as the default for this menu? Users arriving on '.
+ 'this page will be shown the content of this item by default.'))
+ ->addCancelButton($done_uri)
+ ->addSubmitButton(pht('Make Default'));
}
protected function newPanel() {
return PhabricatorProfilePanelConfiguration::initializeNewBuiltin();
}
}
diff --git a/src/applications/search/profilepanel/PhabricatorProfilePanel.php b/src/applications/search/profilepanel/PhabricatorProfilePanel.php
index 43637a6678..49159dbf8d 100644
--- a/src/applications/search/profilepanel/PhabricatorProfilePanel.php
+++ b/src/applications/search/profilepanel/PhabricatorProfilePanel.php
@@ -1,57 +1,62 @@
<?php
abstract class PhabricatorProfilePanel extends Phobject {
private $viewer;
final public function buildNavigationMenuItems(
PhabricatorProfilePanelConfiguration $config) {
return $this->newNavigationMenuItems($config);
}
abstract protected function newNavigationMenuItems(
PhabricatorProfilePanelConfiguration $config);
public function getPanelTypeIcon() {
return null;
}
abstract public function getPanelTypeName();
abstract public function getDisplayName(
PhabricatorProfilePanelConfiguration $config);
public function buildEditEngineFields(
PhabricatorProfilePanelConfiguration $config) {
return array();
}
public function canAddToObject($object) {
return false;
}
+ public function canMakeDefault(
+ PhabricatorProfilePanelConfiguration $config) {
+ return false;
+ }
+
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
final public function getPanelKey() {
return $this->getPhobjectClassConstant('PANELKEY');
}
final public static function getAllPanels() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getPanelKey')
->execute();
}
protected function newItem() {
return new PHUIListItemView();
}
}
diff --git a/src/applications/search/storage/PhabricatorProfilePanelConfiguration.php b/src/applications/search/storage/PhabricatorProfilePanelConfiguration.php
index a4813592f6..127ce6b688 100644
--- a/src/applications/search/storage/PhabricatorProfilePanelConfiguration.php
+++ b/src/applications/search/storage/PhabricatorProfilePanelConfiguration.php
@@ -1,192 +1,194 @@
<?php
final class PhabricatorProfilePanelConfiguration
extends PhabricatorSearchDAO
implements
PhabricatorPolicyInterface,
PhabricatorExtendedPolicyInterface,
PhabricatorApplicationTransactionInterface {
protected $profilePHID;
protected $panelKey;
protected $builtinKey;
protected $panelOrder;
protected $visibility;
protected $panelProperties = array();
private $profileObject = self::ATTACHABLE;
private $panel = self::ATTACHABLE;
+ const VISIBILITY_DEFAULT = 'default';
const VISIBILITY_VISIBLE = 'visible';
const VISIBILITY_DISABLED = 'disabled';
public static function initializeNewBuiltin() {
return id(new self())
->setVisibility(self::VISIBILITY_VISIBLE);
}
public static function initializeNewPanelConfiguration(
$profile_object,
PhabricatorProfilePanel $panel) {
return self::initializeNewBuiltin()
->setProfilePHID($profile_object->getPHID())
->setPanelKey($panel->getPanelKey())
->attachPanel($panel)
->attachProfileObject($profile_object);
}
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'panelProperties' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'panelKey' => 'text64',
'builtinKey' => 'text64?',
'panelOrder' => 'uint32?',
'visibility' => 'text32',
),
self::CONFIG_KEY_SCHEMA => array(
'key_profile' => array(
'columns' => array('profilePHID', 'panelOrder'),
),
),
) + parent::getConfiguration();
}
- public static function getVisibilityNameMap() {
- return array(
- self::VISIBILITY_VISIBLE => pht('Visible'),
- self::VISIBILITY_DISABLED => pht('Disabled'),
- );
- }
-
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorProfilePanelPHIDType::TYPECONST);
}
public function attachPanel(PhabricatorProfilePanel $panel) {
$this->panel = $panel;
return $this;
}
public function getPanel() {
return $this->assertAttached($this->panel);
}
public function attachProfileObject($profile_object) {
$this->profileObject = $profile_object;
return $this;
}
public function getProfileObject() {
return $this->assertAttached($this->profileObject);
}
public function setPanelProperty($key, $value) {
$this->panelProperties[$key] = $value;
return $this;
}
public function getPanelProperty($key, $default = null) {
return idx($this->panelProperties, $key, $default);
}
public function buildNavigationMenuItems() {
return $this->getPanel()->buildNavigationMenuItems($this);
}
public function getPanelTypeName() {
return $this->getPanel()->getPanelTypeName();
}
public function getDisplayName() {
return $this->getPanel()->getDisplayName($this);
}
+ public function canMakeDefault() {
+ return $this->getPanel()->canMakeDefault($this);
+ }
+
public function getSortKey() {
$order = $this->getPanelOrder();
if ($order === null) {
$order = 'Z';
} else {
$order = sprintf('%020d', $order);
}
return sprintf(
'~%s%020d',
$order,
$this->getID());
}
public function isDisabled() {
return ($this->getVisibility() === self::VISIBILITY_DISABLED);
}
+ public function isDefault() {
+ return ($this->getVisibility() === self::VISIBILITY_DEFAULT);
+ }
+
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
return PhabricatorPolicies::getMostOpenPolicy();
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return $this->getProfileObject()->hasAutomaticCapability(
$capability,
$viewer);
}
public function describeAutomaticCapability($capability) {
return null;
}
/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */
public function getExtendedPolicy($capability, PhabricatorUser $viewer) {
return array(
array(
$this->getProfileObject(),
$capability,
),
);
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new PhabricatorProfilePanelEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new PhabricatorProfilePanelConfigurationTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Jul 3, 1:58 PM (3 h, 27 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
165854
Default Alt Text
(83 KB)

Event Timeline