Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/base/PhabricatorApplication.php
13411 views
1
<?php
2
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
10
*/
11
abstract class PhabricatorApplication
12
extends PhabricatorLiskDAO
13
implements
14
PhabricatorPolicyInterface,
15
PhabricatorApplicationTransactionInterface {
16
17
const GROUP_CORE = 'core';
18
const GROUP_UTILITIES = 'util';
19
const GROUP_ADMIN = 'admin';
20
const GROUP_DEVELOPER = 'developer';
21
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'),
28
);
29
}
30
31
final public function getApplicationName() {
32
return 'application';
33
}
34
35
final public function getTableName() {
36
return 'application_application';
37
}
38
39
final protected function getConfiguration() {
40
return array(
41
self::CONFIG_AUX_PHID => true,
42
) + parent::getConfiguration();
43
}
44
45
final public function generatePHID() {
46
return $this->getPHID();
47
}
48
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;
53
}
54
55
56
/* -( Application Information )-------------------------------------------- */
57
58
abstract public function getName();
59
60
public function getShortDescription() {
61
return pht('%s Application', $this->getName());
62
}
63
64
final public function isInstalled() {
65
if (!$this->canUninstall()) {
66
return true;
67
}
68
69
$prototypes = PhabricatorEnv::getEnvConfig('phabricator.show-prototypes');
70
if (!$prototypes && $this->isPrototype()) {
71
return false;
72
}
73
74
$uninstalled = PhabricatorEnv::getEnvConfig(
75
'phabricator.uninstalled-applications');
76
77
return empty($uninstalled[get_class($this)]);
78
}
79
80
81
public function isPrototype() {
82
return false;
83
}
84
85
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.
90
*
91
* Few applications should be unlisted. For most applications, use
92
* @{method:isLaunchable} to hide them from main launch views instead.
93
*
94
* @return bool True to remove application from UI lists.
95
*/
96
public function isUnlisted() {
97
return false;
98
}
99
100
101
/**
102
* Return `true` if this application is a normal application with a base
103
* URI and a web interface.
104
*
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.
108
*
109
* Usually, an application should be marked unlaunchable if:
110
*
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.
114
*
115
* To hide applications more completely, use @{method:isUnlisted}.
116
*
117
* @return bool True if the application is launchable.
118
*/
119
public function isLaunchable() {
120
return true;
121
}
122
123
124
/**
125
* Return `true` if this application should be pinned by default.
126
*
127
* Users who have not yet set preferences see a default list of applications.
128
*
129
* @param PhabricatorUser User viewing the pinned application list.
130
* @return bool True if this application should be pinned by default.
131
*/
132
public function isPinnedByDefault(PhabricatorUser $viewer) {
133
return false;
134
}
135
136
137
/**
138
* Returns true if an application is first-party and false otherwise.
139
*
140
* @return bool True if this application is first-party.
141
*/
142
final public function isFirstParty() {
143
$where = id(new ReflectionClass($this))->getFileName();
144
$root = phutil_get_library_root('phabricator');
145
146
if (!Filesystem::isDescendant($where, $root)) {
147
return false;
148
}
149
150
if (Filesystem::isDescendant($where, $root.'/extensions')) {
151
return false;
152
}
153
154
return true;
155
}
156
157
public function canUninstall() {
158
return true;
159
}
160
161
final public function getPHID() {
162
return 'PHID-APPS-'.get_class($this);
163
}
164
165
public function getTypeaheadURI() {
166
return $this->isLaunchable() ? $this->getBaseURI() : null;
167
}
168
169
public function getBaseURI() {
170
return null;
171
}
172
173
final public function getApplicationURI($path = '') {
174
return $this->getBaseURI().ltrim($path, '/');
175
}
176
177
public function getIcon() {
178
return 'fa-puzzle-piece';
179
}
180
181
public function getApplicationOrder() {
182
return PHP_INT_MAX;
183
}
184
185
public function getApplicationGroup() {
186
return self::GROUP_CORE;
187
}
188
189
public function getTitleGlyph() {
190
return null;
191
}
192
193
final public function getHelpMenuItems(PhabricatorUser $viewer) {
194
$items = array();
195
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;
205
}
206
}
207
208
$command_specs = $this->getMailCommandObjects();
209
if ($command_specs) {
210
foreach ($command_specs as $key => $spec) {
211
$object = $spec['object'];
212
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;
221
}
222
}
223
224
if ($items) {
225
$divider = id(new PhabricatorActionView())
226
->addSigil('help-item')
227
->setType(PhabricatorActionView::TYPE_DIVIDER);
228
array_unshift($items, $divider);
229
}
230
231
return array_values($items);
232
}
233
234
public function getHelpDocumentationArticles(PhabricatorUser $viewer) {
235
return array();
236
}
237
238
public function getOverview() {
239
return null;
240
}
241
242
public function getEventListeners() {
243
return array();
244
}
245
246
public function getRemarkupRules() {
247
return array();
248
}
249
250
public function getQuicksandURIPatternBlacklist() {
251
return array();
252
}
253
254
public function getMailCommandObjects() {
255
return array();
256
}
257
258
259
/* -( URI Routing )-------------------------------------------------------- */
260
261
262
public function getRoutes() {
263
return array();
264
}
265
266
public function getResourceRoutes() {
267
return array();
268
}
269
270
271
/* -( Email Integration )-------------------------------------------------- */
272
273
274
public function supportsEmailIntegration() {
275
return false;
276
}
277
278
final protected function getInboundEmailSupportLink() {
279
return PhabricatorEnv::getDoclink('Configuring Inbound Email');
280
}
281
282
public function getAppEmailBlurb() {
283
throw new PhutilMethodNotImplementedException();
284
}
285
286
/* -( Fact Integration )--------------------------------------------------- */
287
288
289
public function getFactObjectsForAnalysis() {
290
return array();
291
}
292
293
294
/* -( UI Integration )----------------------------------------------------- */
295
296
297
/**
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.
301
*
302
* @return string|null Flavor text.
303
* @task ui
304
*/
305
public function getFlavorText() {
306
return null;
307
}
308
309
310
/**
311
* Build items for the main menu.
312
*
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
318
*/
319
public function buildMainMenuItems(
320
PhabricatorUser $user,
321
PhabricatorController $controller = null) {
322
return array();
323
}
324
325
326
/* -( Application Management )--------------------------------------------- */
327
328
329
final public static function getByClass($class_name) {
330
$selected = null;
331
$applications = self::getAllApplications();
332
333
foreach ($applications as $application) {
334
if (get_class($application) == $class_name) {
335
$selected = $application;
336
break;
337
}
338
}
339
340
if (!$selected) {
341
throw new Exception(pht("No application '%s'!", $class_name));
342
}
343
344
return $selected;
345
}
346
347
final public static function getAllApplications() {
348
static $applications;
349
350
if ($applications === null) {
351
$apps = id(new PhutilClassMapQuery())
352
->setAncestorClass(__CLASS__)
353
->setSortMethod('getApplicationOrder')
354
->execute();
355
356
// Reorder the applications into "application order". Notably, this
357
// ensures their event handlers register in application order.
358
$apps = mgroup($apps, 'getApplicationGroup');
359
360
$group_order = array_keys(self::getApplicationGroups());
361
$apps = array_select_keys($apps, $group_order) + $apps;
362
363
$apps = array_mergev($apps);
364
365
$applications = $apps;
366
}
367
368
return $applications;
369
}
370
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;
377
}
378
379
$apps[] = $app;
380
}
381
382
return $apps;
383
}
384
385
386
/**
387
* Determine if an application is installed, by application class name.
388
*
389
* To check if an application is installed //and// available to a particular
390
* viewer, user @{method:isClassInstalledForViewer}.
391
*
392
* @param string Application class name.
393
* @return bool True if the class is installed.
394
* @task meta
395
*/
396
final public static function isClassInstalled($class) {
397
return self::getByClass($class)->isInstalled();
398
}
399
400
401
/**
402
* Determine if an application is installed and available to a viewer, by
403
* application class name.
404
*
405
* To check if an application is installed at all, use
406
* @{method:isClassInstalled}.
407
*
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
412
*/
413
final public static function isClassInstalledForViewer(
414
$class,
415
PhabricatorUser $viewer) {
416
417
if ($viewer->isOmnipotent()) {
418
return true;
419
}
420
421
$cache = PhabricatorCaches::getRequestCache();
422
$viewer_fragment = $viewer->getCacheFragment();
423
$key = 'app.'.$class.'.installed.'.$viewer_fragment;
424
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);
442
}
443
}
444
445
$cache->setKey($key, $result);
446
}
447
448
return $result;
449
}
450
451
452
/* -( PhabricatorPolicyInterface )----------------------------------------- */
453
454
455
public function getCapabilities() {
456
return array_merge(
457
array(
458
PhabricatorPolicyCapability::CAN_VIEW,
459
PhabricatorPolicyCapability::CAN_EDIT,
460
),
461
array_keys($this->getCustomCapabilities()));
462
}
463
464
public function getPolicy($capability) {
465
$default = $this->getCustomPolicySetting($capability);
466
if ($default) {
467
return $default;
468
}
469
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);
478
}
479
}
480
481
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
482
return false;
483
}
484
485
486
/* -( Policies )----------------------------------------------------------- */
487
488
protected function getCustomCapabilities() {
489
return array();
490
}
491
492
private function getCustomPolicySetting($capability) {
493
if (!$this->isCapabilityEditable($capability)) {
494
return null;
495
}
496
497
$policy_locked = PhabricatorEnv::getEnvConfig('policy.locked');
498
if (isset($policy_locked[$capability])) {
499
return $policy_locked[$capability];
500
}
501
502
$config = PhabricatorEnv::getEnvConfig('phabricator.application-settings');
503
504
$app = idx($config, $this->getPHID());
505
if (!$app) {
506
return null;
507
}
508
509
$policy = idx($app, 'policy');
510
if (!$policy) {
511
return null;
512
}
513
514
return idx($policy, $capability);
515
}
516
517
518
private function getCustomCapabilitySpecification($capability) {
519
$custom = $this->getCustomCapabilities();
520
if (!isset($custom[$capability])) {
521
throw new Exception(pht("Unknown capability '%s'!", $capability));
522
}
523
return $custom[$capability];
524
}
525
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');
532
}
533
534
$capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
535
if ($capobj) {
536
return $capobj->getCapabilityName();
537
}
538
539
return null;
540
}
541
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);
551
}
552
}
553
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, so all '.
560
'users must have access to it.');
561
} else {
562
return null;
563
}
564
case PhabricatorPolicyCapability::CAN_EDIT:
565
return null;
566
default:
567
$spec = $this->getCustomCapabilitySpecification($capability);
568
return idx($spec, 'caption');
569
}
570
}
571
572
final public function getCapabilityTemplatePHIDType($capability) {
573
switch ($capability) {
574
case PhabricatorPolicyCapability::CAN_VIEW:
575
case PhabricatorPolicyCapability::CAN_EDIT:
576
return null;
577
}
578
579
$spec = $this->getCustomCapabilitySpecification($capability);
580
return idx($spec, 'template');
581
}
582
583
final public function getDefaultObjectTypePolicyMap() {
584
$map = array();
585
586
foreach ($this->getCustomCapabilities() as $capability => $spec) {
587
if (empty($spec['template'])) {
588
continue;
589
}
590
if (empty($spec['capability'])) {
591
continue;
592
}
593
$default = $this->getPolicy($capability);
594
$map[$spec['template']][$spec['capability']] = $default;
595
}
596
597
return $map;
598
}
599
600
public function getApplicationSearchDocumentTypes() {
601
return array();
602
}
603
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
')?';
614
}
615
616
protected function getBulkRoutePattern($base = null) {
617
return $base.'(?:query/(?P<queryKey>[^/]+)/)?';
618
}
619
620
protected function getQueryRoutePattern($base = null) {
621
return $base.'(?:query/(?P<queryKey>[^/]+)/(?:(?P<queryAction>[^/]+)/)?)?';
622
}
623
624
protected function getProfileMenuRouting($controller) {
625
$edit_route = $this->getEditRoutePattern();
626
627
$mode_route = '(?P<itemEditMode>global|custom)/';
628
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,
641
);
642
}
643
644
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
645
646
647
public function getApplicationTransactionEditor() {
648
return new PhabricatorApplicationEditor();
649
}
650
651
public function getApplicationTransactionTemplate() {
652
return new PhabricatorApplicationApplicationTransaction();
653
}
654
655
}
656
657