Remove product literal strings in "pht()", part 6
[phabricator.git] / src / applications / base / PhabricatorApplication.php
blob6c5e424bdd38bda61ed5a7770c961aee988cf49b
1 <?php
3 /**
4 * @task info Application Information
5 * @task ui UI Integration
6 * @task uri URI Routing
7 * @task mail Email integration
8 * @task fact Fact Integration
9 * @task meta Application Management
11 abstract class PhabricatorApplication
12 extends PhabricatorLiskDAO
13 implements
14 PhabricatorPolicyInterface,
15 PhabricatorApplicationTransactionInterface {
17 const GROUP_CORE = 'core';
18 const GROUP_UTILITIES = 'util';
19 const GROUP_ADMIN = 'admin';
20 const GROUP_DEVELOPER = 'developer';
22 final public static function getApplicationGroups() {
23 return array(
24 self::GROUP_CORE => pht('Core Applications'),
25 self::GROUP_UTILITIES => pht('Utilities'),
26 self::GROUP_ADMIN => pht('Administration'),
27 self::GROUP_DEVELOPER => pht('Developer Tools'),
31 final public function getApplicationName() {
32 return 'application';
35 final public function getTableName() {
36 return 'application_application';
39 final protected function getConfiguration() {
40 return array(
41 self::CONFIG_AUX_PHID => true,
42 ) + parent::getConfiguration();
45 final public function generatePHID() {
46 return $this->getPHID();
49 final public function save() {
50 // When "save()" is called on applications, we just return without
51 // actually writing anything to the database.
52 return $this;
56 /* -( Application Information )-------------------------------------------- */
58 abstract public function getName();
60 public function getShortDescription() {
61 return pht('%s Application', $this->getName());
64 final public function isInstalled() {
65 if (!$this->canUninstall()) {
66 return true;
69 $prototypes = PhabricatorEnv::getEnvConfig('phabricator.show-prototypes');
70 if (!$prototypes && $this->isPrototype()) {
71 return false;
74 $uninstalled = PhabricatorEnv::getEnvConfig(
75 'phabricator.uninstalled-applications');
77 return empty($uninstalled[get_class($this)]);
81 public function isPrototype() {
82 return false;
86 /**
87 * Return `true` if this application should never appear in application lists
88 * in the UI. Primarily intended for unit test applications or other
89 * pseudo-applications.
91 * Few applications should be unlisted. For most applications, use
92 * @{method:isLaunchable} to hide them from main launch views instead.
94 * @return bool True to remove application from UI lists.
96 public function isUnlisted() {
97 return false;
102 * Return `true` if this application is a normal application with a base
103 * URI and a web interface.
105 * Launchable applications can be pinned to the home page, and show up in the
106 * "Launcher" view of the Applications application. Making an application
107 * unlaunchable prevents pinning and hides it from this view.
109 * Usually, an application should be marked unlaunchable if:
111 * - it is available on every page anyway (like search); or
112 * - it does not have a web interface (like subscriptions); or
113 * - it is still pre-release and being intentionally buried.
115 * To hide applications more completely, use @{method:isUnlisted}.
117 * @return bool True if the application is launchable.
119 public function isLaunchable() {
120 return true;
125 * Return `true` if this application should be pinned by default.
127 * Users who have not yet set preferences see a default list of applications.
129 * @param PhabricatorUser User viewing the pinned application list.
130 * @return bool True if this application should be pinned by default.
132 public function isPinnedByDefault(PhabricatorUser $viewer) {
133 return false;
138 * Returns true if an application is first-party and false otherwise.
140 * @return bool True if this application is first-party.
142 final public function isFirstParty() {
143 $where = id(new ReflectionClass($this))->getFileName();
144 $root = phutil_get_library_root('phabricator');
146 if (!Filesystem::isDescendant($where, $root)) {
147 return false;
150 if (Filesystem::isDescendant($where, $root.'/extensions')) {
151 return false;
154 return true;
157 public function canUninstall() {
158 return true;
161 final public function getPHID() {
162 return 'PHID-APPS-'.get_class($this);
165 public function getTypeaheadURI() {
166 return $this->isLaunchable() ? $this->getBaseURI() : null;
169 public function getBaseURI() {
170 return null;
173 final public function getApplicationURI($path = '') {
174 return $this->getBaseURI().ltrim($path, '/');
177 public function getIcon() {
178 return 'fa-puzzle-piece';
181 public function getApplicationOrder() {
182 return PHP_INT_MAX;
185 public function getApplicationGroup() {
186 return self::GROUP_CORE;
189 public function getTitleGlyph() {
190 return null;
193 final public function getHelpMenuItems(PhabricatorUser $viewer) {
194 $items = array();
196 $articles = $this->getHelpDocumentationArticles($viewer);
197 if ($articles) {
198 foreach ($articles as $article) {
199 $item = id(new PhabricatorActionView())
200 ->setName($article['name'])
201 ->setHref($article['href'])
202 ->addSigil('help-item')
203 ->setOpenInNewWindow(true);
204 $items[] = $item;
208 $command_specs = $this->getMailCommandObjects();
209 if ($command_specs) {
210 foreach ($command_specs as $key => $spec) {
211 $object = $spec['object'];
213 $class = get_class($this);
214 $href = '/applications/mailcommands/'.$class.'/'.$key.'/';
215 $item = id(new PhabricatorActionView())
216 ->setName($spec['name'])
217 ->setHref($href)
218 ->addSigil('help-item')
219 ->setOpenInNewWindow(true);
220 $items[] = $item;
224 if ($items) {
225 $divider = id(new PhabricatorActionView())
226 ->addSigil('help-item')
227 ->setType(PhabricatorActionView::TYPE_DIVIDER);
228 array_unshift($items, $divider);
231 return array_values($items);
234 public function getHelpDocumentationArticles(PhabricatorUser $viewer) {
235 return array();
238 public function getOverview() {
239 return null;
242 public function getEventListeners() {
243 return array();
246 public function getRemarkupRules() {
247 return array();
250 public function getQuicksandURIPatternBlacklist() {
251 return array();
254 public function getMailCommandObjects() {
255 return array();
259 /* -( URI Routing )-------------------------------------------------------- */
262 public function getRoutes() {
263 return array();
266 public function getResourceRoutes() {
267 return array();
271 /* -( Email Integration )-------------------------------------------------- */
274 public function supportsEmailIntegration() {
275 return false;
278 final protected function getInboundEmailSupportLink() {
279 return PhabricatorEnv::getDoclink('Configuring Inbound Email');
282 public function getAppEmailBlurb() {
283 throw new PhutilMethodNotImplementedException();
286 /* -( Fact Integration )--------------------------------------------------- */
289 public function getFactObjectsForAnalysis() {
290 return array();
294 /* -( UI Integration )----------------------------------------------------- */
298 * You can provide an optional piece of flavor text for the application. This
299 * is currently rendered in application launch views if the application has no
300 * status elements.
302 * @return string|null Flavor text.
303 * @task ui
305 public function getFlavorText() {
306 return null;
311 * Build items for the main menu.
313 * @param PhabricatorUser The viewing user.
314 * @param AphrontController The current controller. May be null for special
315 * pages like 404, exception handlers, etc.
316 * @return list<PHUIListItemView> List of menu items.
317 * @task ui
319 public function buildMainMenuItems(
320 PhabricatorUser $user,
321 PhabricatorController $controller = null) {
322 return array();
326 /* -( Application Management )--------------------------------------------- */
329 final public static function getByClass($class_name) {
330 $selected = null;
331 $applications = self::getAllApplications();
333 foreach ($applications as $application) {
334 if (get_class($application) == $class_name) {
335 $selected = $application;
336 break;
340 if (!$selected) {
341 throw new Exception(pht("No application '%s'!", $class_name));
344 return $selected;
347 final public static function getAllApplications() {
348 static $applications;
350 if ($applications === null) {
351 $apps = id(new PhutilClassMapQuery())
352 ->setAncestorClass(__CLASS__)
353 ->setSortMethod('getApplicationOrder')
354 ->execute();
356 // Reorder the applications into "application order". Notably, this
357 // ensures their event handlers register in application order.
358 $apps = mgroup($apps, 'getApplicationGroup');
360 $group_order = array_keys(self::getApplicationGroups());
361 $apps = array_select_keys($apps, $group_order) + $apps;
363 $apps = array_mergev($apps);
365 $applications = $apps;
368 return $applications;
371 final public static function getAllInstalledApplications() {
372 $all_applications = self::getAllApplications();
373 $apps = array();
374 foreach ($all_applications as $app) {
375 if (!$app->isInstalled()) {
376 continue;
379 $apps[] = $app;
382 return $apps;
387 * Determine if an application is installed, by application class name.
389 * To check if an application is installed //and// available to a particular
390 * viewer, user @{method:isClassInstalledForViewer}.
392 * @param string Application class name.
393 * @return bool True if the class is installed.
394 * @task meta
396 final public static function isClassInstalled($class) {
397 return self::getByClass($class)->isInstalled();
402 * Determine if an application is installed and available to a viewer, by
403 * application class name.
405 * To check if an application is installed at all, use
406 * @{method:isClassInstalled}.
408 * @param string Application class name.
409 * @param PhabricatorUser Viewing user.
410 * @return bool True if the class is installed for the viewer.
411 * @task meta
413 final public static function isClassInstalledForViewer(
414 $class,
415 PhabricatorUser $viewer) {
417 if ($viewer->isOmnipotent()) {
418 return true;
421 $cache = PhabricatorCaches::getRequestCache();
422 $viewer_fragment = $viewer->getCacheFragment();
423 $key = 'app.'.$class.'.installed.'.$viewer_fragment;
425 $result = $cache->getKey($key);
426 if ($result === null) {
427 if (!self::isClassInstalled($class)) {
428 $result = false;
429 } else {
430 $application = self::getByClass($class);
431 if (!$application->canUninstall()) {
432 // If the application can not be uninstalled, always allow viewers
433 // to see it. In particular, this allows logged-out viewers to see
434 // Settings and load global default settings even if the install
435 // does not allow public viewers.
436 $result = true;
437 } else {
438 $result = PhabricatorPolicyFilter::hasCapability(
439 $viewer,
440 self::getByClass($class),
441 PhabricatorPolicyCapability::CAN_VIEW);
445 $cache->setKey($key, $result);
448 return $result;
452 /* -( PhabricatorPolicyInterface )----------------------------------------- */
455 public function getCapabilities() {
456 return array_merge(
457 array(
458 PhabricatorPolicyCapability::CAN_VIEW,
459 PhabricatorPolicyCapability::CAN_EDIT,
461 array_keys($this->getCustomCapabilities()));
464 public function getPolicy($capability) {
465 $default = $this->getCustomPolicySetting($capability);
466 if ($default) {
467 return $default;
470 switch ($capability) {
471 case PhabricatorPolicyCapability::CAN_VIEW:
472 return PhabricatorPolicies::getMostOpenPolicy();
473 case PhabricatorPolicyCapability::CAN_EDIT:
474 return PhabricatorPolicies::POLICY_ADMIN;
475 default:
476 $spec = $this->getCustomCapabilitySpecification($capability);
477 return idx($spec, 'default', PhabricatorPolicies::POLICY_USER);
481 public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
482 return false;
486 /* -( Policies )----------------------------------------------------------- */
488 protected function getCustomCapabilities() {
489 return array();
492 private function getCustomPolicySetting($capability) {
493 if (!$this->isCapabilityEditable($capability)) {
494 return null;
497 $policy_locked = PhabricatorEnv::getEnvConfig('policy.locked');
498 if (isset($policy_locked[$capability])) {
499 return $policy_locked[$capability];
502 $config = PhabricatorEnv::getEnvConfig('phabricator.application-settings');
504 $app = idx($config, $this->getPHID());
505 if (!$app) {
506 return null;
509 $policy = idx($app, 'policy');
510 if (!$policy) {
511 return null;
514 return idx($policy, $capability);
518 private function getCustomCapabilitySpecification($capability) {
519 $custom = $this->getCustomCapabilities();
520 if (!isset($custom[$capability])) {
521 throw new Exception(pht("Unknown capability '%s'!", $capability));
523 return $custom[$capability];
526 final public function getCapabilityLabel($capability) {
527 switch ($capability) {
528 case PhabricatorPolicyCapability::CAN_VIEW:
529 return pht('Can Use Application');
530 case PhabricatorPolicyCapability::CAN_EDIT:
531 return pht('Can Configure Application');
534 $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
535 if ($capobj) {
536 return $capobj->getCapabilityName();
539 return null;
542 final public function isCapabilityEditable($capability) {
543 switch ($capability) {
544 case PhabricatorPolicyCapability::CAN_VIEW:
545 return $this->canUninstall();
546 case PhabricatorPolicyCapability::CAN_EDIT:
547 return true;
548 default:
549 $spec = $this->getCustomCapabilitySpecification($capability);
550 return idx($spec, 'edit', true);
554 final public function getCapabilityCaption($capability) {
555 switch ($capability) {
556 case PhabricatorPolicyCapability::CAN_VIEW:
557 if (!$this->canUninstall()) {
558 return pht(
559 'This application is required for Phabricator to operate, so all '.
560 'users must have access to it.');
561 } else {
562 return null;
564 case PhabricatorPolicyCapability::CAN_EDIT:
565 return null;
566 default:
567 $spec = $this->getCustomCapabilitySpecification($capability);
568 return idx($spec, 'caption');
572 final public function getCapabilityTemplatePHIDType($capability) {
573 switch ($capability) {
574 case PhabricatorPolicyCapability::CAN_VIEW:
575 case PhabricatorPolicyCapability::CAN_EDIT:
576 return null;
579 $spec = $this->getCustomCapabilitySpecification($capability);
580 return idx($spec, 'template');
583 final public function getDefaultObjectTypePolicyMap() {
584 $map = array();
586 foreach ($this->getCustomCapabilities() as $capability => $spec) {
587 if (empty($spec['template'])) {
588 continue;
590 if (empty($spec['capability'])) {
591 continue;
593 $default = $this->getPolicy($capability);
594 $map[$spec['template']][$spec['capability']] = $default;
597 return $map;
600 public function getApplicationSearchDocumentTypes() {
601 return array();
604 protected function getEditRoutePattern($base = null) {
605 return $base.'(?:'.
606 '(?P<id>[0-9]\d*)/)?'.
607 '(?:'.
608 '(?:'.
609 '(?P<editAction>parameters|nodefault|nocreate|nomanage|comment)/'.
610 '|'.
611 '(?:form/(?P<formKey>[^/]+)/)?(?:page/(?P<pageKey>[^/]+)/)?'.
612 ')'.
613 ')?';
616 protected function getBulkRoutePattern($base = null) {
617 return $base.'(?:query/(?P<queryKey>[^/]+)/)?';
620 protected function getQueryRoutePattern($base = null) {
621 return $base.'(?:query/(?P<queryKey>[^/]+)/(?:(?P<queryAction>[^/]+)/)?)?';
624 protected function getProfileMenuRouting($controller) {
625 $edit_route = $this->getEditRoutePattern();
627 $mode_route = '(?P<itemEditMode>global|custom)/';
629 return array(
630 '(?P<itemAction>view)/(?P<itemID>[^/]+)/' => $controller,
631 '(?P<itemAction>hide)/(?P<itemID>[^/]+)/' => $controller,
632 '(?P<itemAction>default)/(?P<itemID>[^/]+)/' => $controller,
633 '(?P<itemAction>configure)/' => $controller,
634 '(?P<itemAction>configure)/'.$mode_route => $controller,
635 '(?P<itemAction>reorder)/'.$mode_route => $controller,
636 '(?P<itemAction>edit)/'.$edit_route => $controller,
637 '(?P<itemAction>new)/'.$mode_route.'(?<itemKey>[^/]+)/'.$edit_route
638 => $controller,
639 '(?P<itemAction>builtin)/(?<itemID>[^/]+)/'.$edit_route
640 => $controller,
644 /* -( PhabricatorApplicationTransactionInterface )------------------------- */
647 public function getApplicationTransactionEditor() {
648 return new PhabricatorApplicationEditor();
651 public function getApplicationTransactionTemplate() {
652 return new PhabricatorApplicationApplicationTransaction();