Path: blob/master/src/applications/base/PhabricatorApplication.php
13411 views
<?php12/**3* @task info Application Information4* @task ui UI Integration5* @task uri URI Routing6* @task mail Email integration7* @task fact Fact Integration8* @task meta Application Management9*/10abstract class PhabricatorApplication11extends PhabricatorLiskDAO12implements13PhabricatorPolicyInterface,14PhabricatorApplicationTransactionInterface {1516const GROUP_CORE = 'core';17const GROUP_UTILITIES = 'util';18const GROUP_ADMIN = 'admin';19const GROUP_DEVELOPER = 'developer';2021final public static function getApplicationGroups() {22return array(23self::GROUP_CORE => pht('Core Applications'),24self::GROUP_UTILITIES => pht('Utilities'),25self::GROUP_ADMIN => pht('Administration'),26self::GROUP_DEVELOPER => pht('Developer Tools'),27);28}2930final public function getApplicationName() {31return 'application';32}3334final public function getTableName() {35return 'application_application';36}3738final protected function getConfiguration() {39return array(40self::CONFIG_AUX_PHID => true,41) + parent::getConfiguration();42}4344final public function generatePHID() {45return $this->getPHID();46}4748final public function save() {49// When "save()" is called on applications, we just return without50// actually writing anything to the database.51return $this;52}535455/* -( Application Information )-------------------------------------------- */5657abstract public function getName();5859public function getShortDescription() {60return pht('%s Application', $this->getName());61}6263final public function isInstalled() {64if (!$this->canUninstall()) {65return true;66}6768$prototypes = PhabricatorEnv::getEnvConfig('phabricator.show-prototypes');69if (!$prototypes && $this->isPrototype()) {70return false;71}7273$uninstalled = PhabricatorEnv::getEnvConfig(74'phabricator.uninstalled-applications');7576return empty($uninstalled[get_class($this)]);77}787980public function isPrototype() {81return false;82}838485/**86* Return `true` if this application should never appear in application lists87* in the UI. Primarily intended for unit test applications or other88* pseudo-applications.89*90* Few applications should be unlisted. For most applications, use91* @{method:isLaunchable} to hide them from main launch views instead.92*93* @return bool True to remove application from UI lists.94*/95public function isUnlisted() {96return false;97}9899100/**101* Return `true` if this application is a normal application with a base102* URI and a web interface.103*104* Launchable applications can be pinned to the home page, and show up in the105* "Launcher" view of the Applications application. Making an application106* unlaunchable prevents pinning and hides it from this view.107*108* Usually, an application should be marked unlaunchable if:109*110* - it is available on every page anyway (like search); or111* - it does not have a web interface (like subscriptions); or112* - it is still pre-release and being intentionally buried.113*114* To hide applications more completely, use @{method:isUnlisted}.115*116* @return bool True if the application is launchable.117*/118public function isLaunchable() {119return true;120}121122123/**124* Return `true` if this application should be pinned by default.125*126* Users who have not yet set preferences see a default list of applications.127*128* @param PhabricatorUser User viewing the pinned application list.129* @return bool True if this application should be pinned by default.130*/131public function isPinnedByDefault(PhabricatorUser $viewer) {132return false;133}134135136/**137* Returns true if an application is first-party and false otherwise.138*139* @return bool True if this application is first-party.140*/141final public function isFirstParty() {142$where = id(new ReflectionClass($this))->getFileName();143$root = phutil_get_library_root('phabricator');144145if (!Filesystem::isDescendant($where, $root)) {146return false;147}148149if (Filesystem::isDescendant($where, $root.'/extensions')) {150return false;151}152153return true;154}155156public function canUninstall() {157return true;158}159160final public function getPHID() {161return 'PHID-APPS-'.get_class($this);162}163164public function getTypeaheadURI() {165return $this->isLaunchable() ? $this->getBaseURI() : null;166}167168public function getBaseURI() {169return null;170}171172final public function getApplicationURI($path = '') {173return $this->getBaseURI().ltrim($path, '/');174}175176public function getIcon() {177return 'fa-puzzle-piece';178}179180public function getApplicationOrder() {181return PHP_INT_MAX;182}183184public function getApplicationGroup() {185return self::GROUP_CORE;186}187188public function getTitleGlyph() {189return null;190}191192final public function getHelpMenuItems(PhabricatorUser $viewer) {193$items = array();194195$articles = $this->getHelpDocumentationArticles($viewer);196if ($articles) {197foreach ($articles as $article) {198$item = id(new PhabricatorActionView())199->setName($article['name'])200->setHref($article['href'])201->addSigil('help-item')202->setOpenInNewWindow(true);203$items[] = $item;204}205}206207$command_specs = $this->getMailCommandObjects();208if ($command_specs) {209foreach ($command_specs as $key => $spec) {210$object = $spec['object'];211212$class = get_class($this);213$href = '/applications/mailcommands/'.$class.'/'.$key.'/';214$item = id(new PhabricatorActionView())215->setName($spec['name'])216->setHref($href)217->addSigil('help-item')218->setOpenInNewWindow(true);219$items[] = $item;220}221}222223if ($items) {224$divider = id(new PhabricatorActionView())225->addSigil('help-item')226->setType(PhabricatorActionView::TYPE_DIVIDER);227array_unshift($items, $divider);228}229230return array_values($items);231}232233public function getHelpDocumentationArticles(PhabricatorUser $viewer) {234return array();235}236237public function getOverview() {238return null;239}240241public function getEventListeners() {242return array();243}244245public function getRemarkupRules() {246return array();247}248249public function getQuicksandURIPatternBlacklist() {250return array();251}252253public function getMailCommandObjects() {254return array();255}256257258/* -( URI Routing )-------------------------------------------------------- */259260261public function getRoutes() {262return array();263}264265public function getResourceRoutes() {266return array();267}268269270/* -( Email Integration )-------------------------------------------------- */271272273public function supportsEmailIntegration() {274return false;275}276277final protected function getInboundEmailSupportLink() {278return PhabricatorEnv::getDoclink('Configuring Inbound Email');279}280281public function getAppEmailBlurb() {282throw new PhutilMethodNotImplementedException();283}284285/* -( Fact Integration )--------------------------------------------------- */286287288public function getFactObjectsForAnalysis() {289return array();290}291292293/* -( UI Integration )----------------------------------------------------- */294295296/**297* You can provide an optional piece of flavor text for the application. This298* is currently rendered in application launch views if the application has no299* status elements.300*301* @return string|null Flavor text.302* @task ui303*/304public function getFlavorText() {305return null;306}307308309/**310* Build items for the main menu.311*312* @param PhabricatorUser The viewing user.313* @param AphrontController The current controller. May be null for special314* pages like 404, exception handlers, etc.315* @return list<PHUIListItemView> List of menu items.316* @task ui317*/318public function buildMainMenuItems(319PhabricatorUser $user,320PhabricatorController $controller = null) {321return array();322}323324325/* -( Application Management )--------------------------------------------- */326327328final public static function getByClass($class_name) {329$selected = null;330$applications = self::getAllApplications();331332foreach ($applications as $application) {333if (get_class($application) == $class_name) {334$selected = $application;335break;336}337}338339if (!$selected) {340throw new Exception(pht("No application '%s'!", $class_name));341}342343return $selected;344}345346final public static function getAllApplications() {347static $applications;348349if ($applications === null) {350$apps = id(new PhutilClassMapQuery())351->setAncestorClass(__CLASS__)352->setSortMethod('getApplicationOrder')353->execute();354355// Reorder the applications into "application order". Notably, this356// ensures their event handlers register in application order.357$apps = mgroup($apps, 'getApplicationGroup');358359$group_order = array_keys(self::getApplicationGroups());360$apps = array_select_keys($apps, $group_order) + $apps;361362$apps = array_mergev($apps);363364$applications = $apps;365}366367return $applications;368}369370final public static function getAllInstalledApplications() {371$all_applications = self::getAllApplications();372$apps = array();373foreach ($all_applications as $app) {374if (!$app->isInstalled()) {375continue;376}377378$apps[] = $app;379}380381return $apps;382}383384385/**386* Determine if an application is installed, by application class name.387*388* To check if an application is installed //and// available to a particular389* viewer, user @{method:isClassInstalledForViewer}.390*391* @param string Application class name.392* @return bool True if the class is installed.393* @task meta394*/395final public static function isClassInstalled($class) {396return self::getByClass($class)->isInstalled();397}398399400/**401* Determine if an application is installed and available to a viewer, by402* application class name.403*404* To check if an application is installed at all, use405* @{method:isClassInstalled}.406*407* @param string Application class name.408* @param PhabricatorUser Viewing user.409* @return bool True if the class is installed for the viewer.410* @task meta411*/412final public static function isClassInstalledForViewer(413$class,414PhabricatorUser $viewer) {415416if ($viewer->isOmnipotent()) {417return true;418}419420$cache = PhabricatorCaches::getRequestCache();421$viewer_fragment = $viewer->getCacheFragment();422$key = 'app.'.$class.'.installed.'.$viewer_fragment;423424$result = $cache->getKey($key);425if ($result === null) {426if (!self::isClassInstalled($class)) {427$result = false;428} else {429$application = self::getByClass($class);430if (!$application->canUninstall()) {431// If the application can not be uninstalled, always allow viewers432// to see it. In particular, this allows logged-out viewers to see433// Settings and load global default settings even if the install434// does not allow public viewers.435$result = true;436} else {437$result = PhabricatorPolicyFilter::hasCapability(438$viewer,439self::getByClass($class),440PhabricatorPolicyCapability::CAN_VIEW);441}442}443444$cache->setKey($key, $result);445}446447return $result;448}449450451/* -( PhabricatorPolicyInterface )----------------------------------------- */452453454public function getCapabilities() {455return array_merge(456array(457PhabricatorPolicyCapability::CAN_VIEW,458PhabricatorPolicyCapability::CAN_EDIT,459),460array_keys($this->getCustomCapabilities()));461}462463public function getPolicy($capability) {464$default = $this->getCustomPolicySetting($capability);465if ($default) {466return $default;467}468469switch ($capability) {470case PhabricatorPolicyCapability::CAN_VIEW:471return PhabricatorPolicies::getMostOpenPolicy();472case PhabricatorPolicyCapability::CAN_EDIT:473return PhabricatorPolicies::POLICY_ADMIN;474default:475$spec = $this->getCustomCapabilitySpecification($capability);476return idx($spec, 'default', PhabricatorPolicies::POLICY_USER);477}478}479480public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {481return false;482}483484485/* -( Policies )----------------------------------------------------------- */486487protected function getCustomCapabilities() {488return array();489}490491private function getCustomPolicySetting($capability) {492if (!$this->isCapabilityEditable($capability)) {493return null;494}495496$policy_locked = PhabricatorEnv::getEnvConfig('policy.locked');497if (isset($policy_locked[$capability])) {498return $policy_locked[$capability];499}500501$config = PhabricatorEnv::getEnvConfig('phabricator.application-settings');502503$app = idx($config, $this->getPHID());504if (!$app) {505return null;506}507508$policy = idx($app, 'policy');509if (!$policy) {510return null;511}512513return idx($policy, $capability);514}515516517private function getCustomCapabilitySpecification($capability) {518$custom = $this->getCustomCapabilities();519if (!isset($custom[$capability])) {520throw new Exception(pht("Unknown capability '%s'!", $capability));521}522return $custom[$capability];523}524525final public function getCapabilityLabel($capability) {526switch ($capability) {527case PhabricatorPolicyCapability::CAN_VIEW:528return pht('Can Use Application');529case PhabricatorPolicyCapability::CAN_EDIT:530return pht('Can Configure Application');531}532533$capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);534if ($capobj) {535return $capobj->getCapabilityName();536}537538return null;539}540541final public function isCapabilityEditable($capability) {542switch ($capability) {543case PhabricatorPolicyCapability::CAN_VIEW:544return $this->canUninstall();545case PhabricatorPolicyCapability::CAN_EDIT:546return true;547default:548$spec = $this->getCustomCapabilitySpecification($capability);549return idx($spec, 'edit', true);550}551}552553final public function getCapabilityCaption($capability) {554switch ($capability) {555case PhabricatorPolicyCapability::CAN_VIEW:556if (!$this->canUninstall()) {557return pht(558'This application is required, so all '.559'users must have access to it.');560} else {561return null;562}563case PhabricatorPolicyCapability::CAN_EDIT:564return null;565default:566$spec = $this->getCustomCapabilitySpecification($capability);567return idx($spec, 'caption');568}569}570571final public function getCapabilityTemplatePHIDType($capability) {572switch ($capability) {573case PhabricatorPolicyCapability::CAN_VIEW:574case PhabricatorPolicyCapability::CAN_EDIT:575return null;576}577578$spec = $this->getCustomCapabilitySpecification($capability);579return idx($spec, 'template');580}581582final public function getDefaultObjectTypePolicyMap() {583$map = array();584585foreach ($this->getCustomCapabilities() as $capability => $spec) {586if (empty($spec['template'])) {587continue;588}589if (empty($spec['capability'])) {590continue;591}592$default = $this->getPolicy($capability);593$map[$spec['template']][$spec['capability']] = $default;594}595596return $map;597}598599public function getApplicationSearchDocumentTypes() {600return array();601}602603protected function getEditRoutePattern($base = null) {604return $base.'(?:'.605'(?P<id>[0-9]\d*)/)?'.606'(?:'.607'(?:'.608'(?P<editAction>parameters|nodefault|nocreate|nomanage|comment)/'.609'|'.610'(?:form/(?P<formKey>[^/]+)/)?(?:page/(?P<pageKey>[^/]+)/)?'.611')'.612')?';613}614615protected function getBulkRoutePattern($base = null) {616return $base.'(?:query/(?P<queryKey>[^/]+)/)?';617}618619protected function getQueryRoutePattern($base = null) {620return $base.'(?:query/(?P<queryKey>[^/]+)/(?:(?P<queryAction>[^/]+)/)?)?';621}622623protected function getProfileMenuRouting($controller) {624$edit_route = $this->getEditRoutePattern();625626$mode_route = '(?P<itemEditMode>global|custom)/';627628return array(629'(?P<itemAction>view)/(?P<itemID>[^/]+)/' => $controller,630'(?P<itemAction>hide)/(?P<itemID>[^/]+)/' => $controller,631'(?P<itemAction>default)/(?P<itemID>[^/]+)/' => $controller,632'(?P<itemAction>configure)/' => $controller,633'(?P<itemAction>configure)/'.$mode_route => $controller,634'(?P<itemAction>reorder)/'.$mode_route => $controller,635'(?P<itemAction>edit)/'.$edit_route => $controller,636'(?P<itemAction>new)/'.$mode_route.'(?<itemKey>[^/]+)/'.$edit_route637=> $controller,638'(?P<itemAction>builtin)/(?<itemID>[^/]+)/'.$edit_route639=> $controller,640);641}642643/* -( PhabricatorApplicationTransactionInterface )------------------------- */644645646public function getApplicationTransactionEditor() {647return new PhabricatorApplicationEditor();648}649650public function getApplicationTransactionTemplate() {651return new PhabricatorApplicationApplicationTransaction();652}653654}655656657