2012-07-30 19:43:49 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @task info Application Information
|
2012-08-06 21:46:51 +02:00
|
|
|
* @task ui UI Integration
|
2012-07-30 19:44:08 +02:00
|
|
|
* @task uri URI Routing
|
2015-01-20 01:07:26 +01:00
|
|
|
* @task mail Email integration
|
2012-07-30 19:43:49 +02:00
|
|
|
* @task fact Fact Integration
|
|
|
|
* @task meta Application Management
|
|
|
|
*/
|
2015-06-15 10:02:26 +02:00
|
|
|
abstract class PhabricatorApplication
|
|
|
|
extends Phobject
|
|
|
|
implements PhabricatorPolicyInterface {
|
2012-07-30 19:43:49 +02:00
|
|
|
|
2012-10-04 00:46:19 +02:00
|
|
|
const GROUP_CORE = 'core';
|
|
|
|
const GROUP_UTILITIES = 'util';
|
|
|
|
const GROUP_ADMIN = 'admin';
|
|
|
|
const GROUP_DEVELOPER = 'developer';
|
|
|
|
|
2015-06-14 23:30:05 +02:00
|
|
|
final public static function getApplicationGroups() {
|
2012-10-04 00:46:19 +02:00
|
|
|
return array(
|
|
|
|
self::GROUP_CORE => pht('Core Applications'),
|
|
|
|
self::GROUP_UTILITIES => pht('Utilities'),
|
|
|
|
self::GROUP_ADMIN => pht('Administration'),
|
|
|
|
self::GROUP_DEVELOPER => pht('Developer Tools'),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2012-07-30 19:43:49 +02:00
|
|
|
|
|
|
|
/* -( Application Information )-------------------------------------------- */
|
|
|
|
|
2015-06-02 14:14:01 +02:00
|
|
|
abstract public function getName();
|
2012-08-01 02:58:21 +02:00
|
|
|
|
|
|
|
public function getShortDescription() {
|
2015-05-22 09:27:56 +02:00
|
|
|
return pht('%s Application', $this->getName());
|
2012-07-30 19:43:49 +02:00
|
|
|
}
|
|
|
|
|
2015-06-14 23:30:05 +02:00
|
|
|
final public function isInstalled() {
|
2013-03-13 15:09:05 +01:00
|
|
|
if (!$this->canUninstall()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2014-09-18 03:25:57 +02:00
|
|
|
$prototypes = PhabricatorEnv::getEnvConfig('phabricator.show-prototypes');
|
|
|
|
if (!$prototypes && $this->isPrototype()) {
|
2013-04-06 18:25:13 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$uninstalled = PhabricatorEnv::getEnvConfig(
|
|
|
|
'phabricator.uninstalled-applications');
|
|
|
|
|
2013-03-13 15:09:05 +01:00
|
|
|
return empty($uninstalled[get_class($this)]);
|
2013-01-29 18:14:03 +01:00
|
|
|
}
|
|
|
|
|
(Redesign) Clean up older "Tile" code
Summary:
This does some backend cleanup of the tile stuff, and some general cleanup of other application things:
- Users who haven't customized preferences get a small, specific set of pinned applications: Differential, Maniphest, Diffusion, Audit, Phriction, Projects (and, for administrators, Auth, Config and People).
- Old tile size methods are replaced with `isPinnnedByDefault()`.
- Shortened some short descriptions.
- `shouldAppearInLaunchView()` replaced by less ambiguous `isLaunchable()`.
- Added a marker for third-party / extension applications.
Test Plan: Faked away my preferences and viewed the home page, saw a smaller set of default pins.
Reviewers: chad
Reviewed By: chad
Subscribers: epriestley
Differential Revision: https://secure.phabricator.com/D9358
2014-06-04 00:47:27 +02:00
|
|
|
|
2014-09-18 03:25:57 +02:00
|
|
|
public function isPrototype() {
|
2013-01-19 19:12:44 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
(Redesign) Clean up older "Tile" code
Summary:
This does some backend cleanup of the tile stuff, and some general cleanup of other application things:
- Users who haven't customized preferences get a small, specific set of pinned applications: Differential, Maniphest, Diffusion, Audit, Phriction, Projects (and, for administrators, Auth, Config and People).
- Old tile size methods are replaced with `isPinnnedByDefault()`.
- Shortened some short descriptions.
- `shouldAppearInLaunchView()` replaced by less ambiguous `isLaunchable()`.
- Added a marker for third-party / extension applications.
Test Plan: Faked away my preferences and viewed the home page, saw a smaller set of default pins.
Reviewers: chad
Reviewed By: chad
Subscribers: epriestley
Differential Revision: https://secure.phabricator.com/D9358
2014-06-04 00:47:27 +02:00
|
|
|
|
2013-10-04 15:46:47 +02:00
|
|
|
/**
|
(Redesign) Clean up older "Tile" code
Summary:
This does some backend cleanup of the tile stuff, and some general cleanup of other application things:
- Users who haven't customized preferences get a small, specific set of pinned applications: Differential, Maniphest, Diffusion, Audit, Phriction, Projects (and, for administrators, Auth, Config and People).
- Old tile size methods are replaced with `isPinnnedByDefault()`.
- Shortened some short descriptions.
- `shouldAppearInLaunchView()` replaced by less ambiguous `isLaunchable()`.
- Added a marker for third-party / extension applications.
Test Plan: Faked away my preferences and viewed the home page, saw a smaller set of default pins.
Reviewers: chad
Reviewed By: chad
Subscribers: epriestley
Differential Revision: https://secure.phabricator.com/D9358
2014-06-04 00:47:27 +02:00
|
|
|
* Return `true` if this application should never appear in application lists
|
|
|
|
* in the UI. Primarily intended for unit test applications or other
|
2013-10-04 15:46:47 +02:00
|
|
|
* pseudo-applications.
|
|
|
|
*
|
(Redesign) Clean up older "Tile" code
Summary:
This does some backend cleanup of the tile stuff, and some general cleanup of other application things:
- Users who haven't customized preferences get a small, specific set of pinned applications: Differential, Maniphest, Diffusion, Audit, Phriction, Projects (and, for administrators, Auth, Config and People).
- Old tile size methods are replaced with `isPinnnedByDefault()`.
- Shortened some short descriptions.
- `shouldAppearInLaunchView()` replaced by less ambiguous `isLaunchable()`.
- Added a marker for third-party / extension applications.
Test Plan: Faked away my preferences and viewed the home page, saw a smaller set of default pins.
Reviewers: chad
Reviewed By: chad
Subscribers: epriestley
Differential Revision: https://secure.phabricator.com/D9358
2014-06-04 00:47:27 +02:00
|
|
|
* Few applications should be unlisted. For most applications, use
|
|
|
|
* @{method:isLaunchable} to hide them from main launch views instead.
|
|
|
|
*
|
2013-10-04 15:46:47 +02:00
|
|
|
* @return bool True to remove application from UI lists.
|
|
|
|
*/
|
2013-10-04 04:05:47 +02:00
|
|
|
public function isUnlisted() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
(Redesign) Clean up older "Tile" code
Summary:
This does some backend cleanup of the tile stuff, and some general cleanup of other application things:
- Users who haven't customized preferences get a small, specific set of pinned applications: Differential, Maniphest, Diffusion, Audit, Phriction, Projects (and, for administrators, Auth, Config and People).
- Old tile size methods are replaced with `isPinnnedByDefault()`.
- Shortened some short descriptions.
- `shouldAppearInLaunchView()` replaced by less ambiguous `isLaunchable()`.
- Added a marker for third-party / extension applications.
Test Plan: Faked away my preferences and viewed the home page, saw a smaller set of default pins.
Reviewers: chad
Reviewed By: chad
Subscribers: epriestley
Differential Revision: https://secure.phabricator.com/D9358
2014-06-04 00:47:27 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-10-02 22:13:07 +02:00
|
|
|
/**
|
|
|
|
* Returns true if an application is first-party (developed by Phacility)
|
|
|
|
* and false otherwise.
|
2013-10-04 15:46:47 +02:00
|
|
|
*
|
|
|
|
* @return bool True if this application is developed by Phacility.
|
2013-10-02 22:13:07 +02:00
|
|
|
*/
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2013-01-29 18:14:03 +01:00
|
|
|
public function canUninstall() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-06-14 23:30:05 +02:00
|
|
|
final public function getPHID() {
|
2012-08-01 02:58:21 +02:00
|
|
|
return 'PHID-APPS-'.get_class($this);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getTypeaheadURI() {
|
(Redesign) Clean up older "Tile" code
Summary:
This does some backend cleanup of the tile stuff, and some general cleanup of other application things:
- Users who haven't customized preferences get a small, specific set of pinned applications: Differential, Maniphest, Diffusion, Audit, Phriction, Projects (and, for administrators, Auth, Config and People).
- Old tile size methods are replaced with `isPinnnedByDefault()`.
- Shortened some short descriptions.
- `shouldAppearInLaunchView()` replaced by less ambiguous `isLaunchable()`.
- Added a marker for third-party / extension applications.
Test Plan: Faked away my preferences and viewed the home page, saw a smaller set of default pins.
Reviewers: chad
Reviewed By: chad
Subscribers: epriestley
Differential Revision: https://secure.phabricator.com/D9358
2014-06-04 00:47:27 +02:00
|
|
|
return $this->isLaunchable() ? $this->getBaseURI() : null;
|
2012-08-01 02:58:21 +02:00
|
|
|
}
|
2012-07-30 19:43:49 +02:00
|
|
|
|
2012-08-01 02:58:21 +02:00
|
|
|
public function getBaseURI() {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2015-06-14 23:30:05 +02:00
|
|
|
final public function getApplicationURI($path = '') {
|
2013-05-31 01:37:51 +02:00
|
|
|
return $this->getBaseURI().ltrim($path, '/');
|
|
|
|
}
|
|
|
|
|
2016-01-28 17:40:22 +01:00
|
|
|
public function getIcon() {
|
2015-01-25 08:41:43 +01:00
|
|
|
return 'fa-puzzle-piece';
|
|
|
|
}
|
|
|
|
|
2012-10-04 00:46:19 +02:00
|
|
|
public function getApplicationOrder() {
|
|
|
|
return PHP_INT_MAX;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getApplicationGroup() {
|
2014-05-30 00:25:26 +02:00
|
|
|
return self::GROUP_CORE;
|
Add basic support for new navigation menu
Summary:
Add a new left-side application menu. This menu shows which application you're in and provides a quick way to get to other applications.
On desktops, menus are always shown but the app menu can be collapsed to be very small.
On tablets, navigation buttons allow you to choose between the menus and the content.
On phones, navigation buttons allow you to choose between the app menu, the local menu, and the content.
This needs some code and UI cleanup, but has no effect yet so I think it's okay to land as-is, I'll clean it up a bit as I start integrating it. I want to play around with it a bit and see if it's good/useful or horrible anyway.
Test Plan: Will include screenshots.
Reviewers: vrana, btrahan, chad
Reviewed By: btrahan
CC: aran, alanh
Maniphest Tasks: T1569
Differential Revision: https://secure.phabricator.com/D3223
2012-08-11 16:06:12 +02:00
|
|
|
}
|
|
|
|
|
2012-08-13 04:19:46 +02:00
|
|
|
public function getTitleGlyph() {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2015-06-14 23:30:05 +02:00
|
|
|
final public function getHelpMenuItems(PhabricatorUser $viewer) {
|
2015-04-01 17:13:12 +02:00
|
|
|
$items = array();
|
|
|
|
|
|
|
|
$articles = $this->getHelpDocumentationArticles($viewer);
|
|
|
|
if ($articles) {
|
|
|
|
foreach ($articles as $article) {
|
2017-01-17 21:04:28 +01:00
|
|
|
$item = id(new PhabricatorActionView())
|
2015-04-01 17:13:12 +02:00
|
|
|
->setName($article['name'])
|
2017-01-17 22:59:56 +01:00
|
|
|
->setHref($article['href'])
|
|
|
|
->addSigil('help-item')
|
|
|
|
->setOpenInNewWindow(true);
|
2015-04-01 17:13:12 +02:00
|
|
|
$items[] = $item;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$command_specs = $this->getMailCommandObjects();
|
|
|
|
if ($command_specs) {
|
|
|
|
foreach ($command_specs as $key => $spec) {
|
|
|
|
$object = $spec['object'];
|
|
|
|
|
|
|
|
$class = get_class($this);
|
|
|
|
$href = '/applications/mailcommands/'.$class.'/'.$key.'/';
|
2017-01-17 21:04:28 +01:00
|
|
|
$item = id(new PhabricatorActionView())
|
2015-04-01 17:13:12 +02:00
|
|
|
->setName($spec['name'])
|
2017-01-17 22:59:56 +01:00
|
|
|
->setHref($href)
|
|
|
|
->addSigil('help-item')
|
|
|
|
->setOpenInNewWindow(true);
|
2015-04-01 17:13:12 +02:00
|
|
|
$items[] = $item;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-17 22:59:56 +01:00
|
|
|
if ($items) {
|
|
|
|
$divider = id(new PhabricatorActionView())
|
|
|
|
->addSigil('help-item')
|
|
|
|
->setType(PhabricatorActionView::TYPE_DIVIDER);
|
|
|
|
array_unshift($items, $divider);
|
|
|
|
}
|
|
|
|
|
|
|
|
return array_values($items);
|
2015-04-01 17:13:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function getHelpDocumentationArticles(PhabricatorUser $viewer) {
|
|
|
|
return array();
|
2014-05-29 21:17:54 +02:00
|
|
|
}
|
2012-08-14 00:28:41 +02:00
|
|
|
|
2014-05-29 21:17:54 +02:00
|
|
|
public function getOverview() {
|
2012-08-14 00:28:41 +02:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2012-08-24 22:19:47 +02:00
|
|
|
public function getEventListeners() {
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
|
2013-02-26 23:59:31 +01:00
|
|
|
public function getRemarkupRules() {
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
|
2015-03-10 23:32:15 +01:00
|
|
|
public function getQuicksandURIPatternBlacklist() {
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
|
2015-04-01 16:36:16 +02:00
|
|
|
public function getMailCommandObjects() {
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
|
2012-08-01 02:58:21 +02:00
|
|
|
|
|
|
|
/* -( URI Routing )-------------------------------------------------------- */
|
2012-07-30 19:44:08 +02:00
|
|
|
|
|
|
|
|
|
|
|
public function getRoutes() {
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
|
2015-08-31 13:01:01 +02:00
|
|
|
public function getResourceRoutes() {
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
|
2012-07-30 19:44:08 +02:00
|
|
|
|
2015-01-20 01:07:26 +01:00
|
|
|
/* -( Email Integration )-------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
public function supportsEmailIntegration() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-06-14 23:30:05 +02:00
|
|
|
final protected function getInboundEmailSupportLink() {
|
2015-01-20 01:07:26 +01:00
|
|
|
return PhabricatorEnv::getDocLink('Configuring Inbound Email');
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getAppEmailBlurb() {
|
2015-05-22 09:27:56 +02:00
|
|
|
throw new PhutilMethodNotImplementedException();
|
2015-01-20 01:07:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-07-30 19:43:49 +02:00
|
|
|
/* -( Fact Integration )--------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
public function getFactObjectsForAnalysis() {
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-08-06 21:46:51 +02:00
|
|
|
/* -( UI Integration )----------------------------------------------------- */
|
2012-08-02 23:07:21 +02:00
|
|
|
|
|
|
|
|
2012-10-04 00:16:26 +02:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-08-06 21:46:51 +02:00
|
|
|
/**
|
|
|
|
* 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.
|
2014-01-29 05:18:01 +01:00
|
|
|
* @return list<PHUIListItemView> List of menu items.
|
2012-10-04 00:16:26 +02:00
|
|
|
* @task ui
|
2012-08-06 21:46:51 +02:00
|
|
|
*/
|
2012-08-05 23:12:43 +02:00
|
|
|
public function buildMainMenuItems(
|
|
|
|
PhabricatorUser $user,
|
2012-08-06 21:46:51 +02:00
|
|
|
PhabricatorController $controller = null) {
|
2012-08-05 23:12:43 +02:00
|
|
|
return array();
|
|
|
|
}
|
|
|
|
|
2012-08-02 23:07:21 +02:00
|
|
|
|
2012-07-30 19:43:49 +02:00
|
|
|
/* -( Application Management )--------------------------------------------- */
|
|
|
|
|
2014-03-15 19:28:02 +01:00
|
|
|
|
2015-06-14 23:30:05 +02:00
|
|
|
final public static function getByClass($class_name) {
|
2013-01-30 19:53:43 +01:00
|
|
|
$selected = null;
|
2015-05-13 22:50:28 +02:00
|
|
|
$applications = self::getAllApplications();
|
2013-01-30 19:53:43 +01:00
|
|
|
|
|
|
|
foreach ($applications as $application) {
|
|
|
|
if (get_class($application) == $class_name) {
|
|
|
|
$selected = $application;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2013-05-14 19:57:41 +02:00
|
|
|
|
|
|
|
if (!$selected) {
|
2015-05-22 09:27:56 +02:00
|
|
|
throw new Exception(pht("No application '%s'!", $class_name));
|
2013-05-14 19:57:41 +02:00
|
|
|
}
|
|
|
|
|
2013-01-30 19:53:43 +01:00
|
|
|
return $selected;
|
|
|
|
}
|
|
|
|
|
2015-06-14 23:30:05 +02:00
|
|
|
final public static function getAllApplications() {
|
2013-05-16 21:25:26 +02:00
|
|
|
static $applications;
|
2013-01-29 18:14:03 +01:00
|
|
|
|
2013-05-16 21:25:26 +02:00
|
|
|
if ($applications === null) {
|
2015-07-07 14:34:30 +02:00
|
|
|
$apps = id(new PhutilClassMapQuery())
|
2013-05-16 21:25:26 +02:00
|
|
|
->setAncestorClass(__CLASS__)
|
2015-07-07 14:34:30 +02:00
|
|
|
->setSortMethod('getApplicationOrder')
|
|
|
|
->execute();
|
2013-01-29 18:14:03 +01:00
|
|
|
|
2013-05-16 21:25:26 +02:00
|
|
|
// Reorder the applications into "application order". Notably, this
|
|
|
|
// ensures their event handlers register in application order.
|
|
|
|
$apps = mgroup($apps, 'getApplicationGroup');
|
2013-09-30 18:38:04 +02:00
|
|
|
|
|
|
|
$group_order = array_keys(self::getApplicationGroups());
|
|
|
|
$apps = array_select_keys($apps, $group_order) + $apps;
|
|
|
|
|
2013-05-16 21:25:26 +02:00
|
|
|
$apps = array_mergev($apps);
|
|
|
|
|
|
|
|
$applications = $apps;
|
2013-01-29 18:14:03 +01:00
|
|
|
}
|
|
|
|
|
2013-05-16 21:25:26 +02:00
|
|
|
return $applications;
|
2013-01-29 18:14:03 +01:00
|
|
|
}
|
2012-07-30 19:43:49 +02:00
|
|
|
|
2015-06-14 23:30:05 +02:00
|
|
|
final public static function getAllInstalledApplications() {
|
2013-05-16 21:25:26 +02:00
|
|
|
$all_applications = self::getAllApplications();
|
|
|
|
$apps = array();
|
|
|
|
foreach ($all_applications as $app) {
|
|
|
|
if (!$app->isInstalled()) {
|
|
|
|
continue;
|
2012-07-30 19:43:49 +02:00
|
|
|
}
|
2013-01-29 18:14:03 +01:00
|
|
|
|
2013-05-16 21:25:26 +02:00
|
|
|
$apps[] = $app;
|
2012-07-30 19:43:49 +02:00
|
|
|
}
|
|
|
|
|
2013-05-16 21:25:26 +02:00
|
|
|
return $apps;
|
2012-07-30 19:43:49 +02:00
|
|
|
}
|
|
|
|
|
2013-01-29 18:14:03 +01:00
|
|
|
|
2014-03-15 19:28:02 +01:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2015-06-14 23:30:05 +02:00
|
|
|
final public static function isClassInstalled($class) {
|
2014-03-15 19:28:02 +01:00
|
|
|
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
|
|
|
|
*/
|
2015-06-14 23:30:05 +02:00
|
|
|
final public static function isClassInstalledForViewer(
|
2014-03-15 19:28:02 +01:00
|
|
|
$class,
|
|
|
|
PhabricatorUser $viewer) {
|
|
|
|
|
2015-06-27 13:26:04 +02:00
|
|
|
if ($viewer->isOmnipotent()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-06-17 04:32:37 +02:00
|
|
|
$cache = PhabricatorCaches::getRequestCache();
|
2017-01-01 20:03:16 +01:00
|
|
|
$viewer_fragment = $viewer->getCacheFragment();
|
|
|
|
$key = 'app.'.$class.'.installed.'.$viewer_fragment;
|
2015-06-17 04:32:37 +02:00
|
|
|
|
|
|
|
$result = $cache->getKey($key);
|
|
|
|
if ($result === null) {
|
|
|
|
if (!self::isClassInstalled($class)) {
|
|
|
|
$result = false;
|
|
|
|
} else {
|
2016-12-05 17:54:15 +01:00
|
|
|
$application = self::getByClass($class);
|
|
|
|
if (!$application->canUninstall()) {
|
|
|
|
// If the application can not be uninstalled, always allow viewers
|
|
|
|
// to see it. In particular, this allows logged-out viewers to see
|
|
|
|
// Settings and load global default settings even if the install
|
|
|
|
// does not allow public viewers.
|
|
|
|
$result = true;
|
|
|
|
} else {
|
|
|
|
$result = PhabricatorPolicyFilter::hasCapability(
|
|
|
|
$viewer,
|
|
|
|
self::getByClass($class),
|
|
|
|
PhabricatorPolicyCapability::CAN_VIEW);
|
|
|
|
}
|
2015-06-17 04:32:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$cache->setKey($key, $result);
|
2014-03-15 19:28:02 +01:00
|
|
|
}
|
|
|
|
|
2015-06-17 04:32:37 +02:00
|
|
|
return $result;
|
2014-03-15 19:28:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-10-02 22:13:07 +02:00
|
|
|
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
public function getCapabilities() {
|
2013-10-03 21:40:08 +02:00
|
|
|
return array_merge(
|
|
|
|
array(
|
|
|
|
PhabricatorPolicyCapability::CAN_VIEW,
|
|
|
|
PhabricatorPolicyCapability::CAN_EDIT,
|
|
|
|
),
|
|
|
|
array_keys($this->getCustomCapabilities()));
|
2013-10-02 22:13:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function getPolicy($capability) {
|
2013-10-03 21:40:08 +02:00
|
|
|
$default = $this->getCustomPolicySetting($capability);
|
|
|
|
if ($default) {
|
|
|
|
return $default;
|
|
|
|
}
|
|
|
|
|
2013-10-02 22:13:07 +02:00
|
|
|
switch ($capability) {
|
|
|
|
case PhabricatorPolicyCapability::CAN_VIEW:
|
2013-10-16 19:35:52 +02:00
|
|
|
return PhabricatorPolicies::getMostOpenPolicy();
|
2013-10-02 22:13:07 +02:00
|
|
|
case PhabricatorPolicyCapability::CAN_EDIT:
|
|
|
|
return PhabricatorPolicies::POLICY_ADMIN;
|
2013-10-03 21:40:08 +02:00
|
|
|
default:
|
|
|
|
$spec = $this->getCustomCapabilitySpecification($capability);
|
|
|
|
return idx($spec, 'default', PhabricatorPolicies::POLICY_USER);
|
2013-10-02 22:13:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-10-03 21:40:08 +02:00
|
|
|
/* -( Policies )----------------------------------------------------------- */
|
|
|
|
|
|
|
|
protected function getCustomCapabilities() {
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
|
2015-06-14 23:30:05 +02:00
|
|
|
final private function getCustomPolicySetting($capability) {
|
2013-10-03 21:40:08 +02:00
|
|
|
if (!$this->isCapabilityEditable($capability)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2015-01-13 21:39:58 +01:00
|
|
|
$policy_locked = PhabricatorEnv::getEnvConfig('policy.locked');
|
|
|
|
if (isset($policy_locked[$capability])) {
|
|
|
|
return $policy_locked[$capability];
|
|
|
|
}
|
|
|
|
|
2013-10-03 21:40:08 +02:00
|
|
|
$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);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-06-14 23:30:05 +02:00
|
|
|
final private function getCustomCapabilitySpecification($capability) {
|
2013-10-03 21:40:08 +02:00
|
|
|
$custom = $this->getCustomCapabilities();
|
2013-10-09 22:44:41 +02:00
|
|
|
if (!isset($custom[$capability])) {
|
2015-05-22 09:27:56 +02:00
|
|
|
throw new Exception(pht("Unknown capability '%s'!", $capability));
|
2013-10-03 21:40:08 +02:00
|
|
|
}
|
|
|
|
return $custom[$capability];
|
|
|
|
}
|
|
|
|
|
2015-06-14 23:30:05 +02:00
|
|
|
final public function getCapabilityLabel($capability) {
|
2013-10-09 22:44:41 +02:00
|
|
|
switch ($capability) {
|
|
|
|
case PhabricatorPolicyCapability::CAN_VIEW:
|
|
|
|
return pht('Can Use Application');
|
|
|
|
case PhabricatorPolicyCapability::CAN_EDIT:
|
|
|
|
return pht('Can Configure Application');
|
|
|
|
}
|
2013-10-03 21:40:08 +02:00
|
|
|
|
2013-10-09 22:44:41 +02:00
|
|
|
$capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
|
|
|
|
if ($capobj) {
|
|
|
|
return $capobj->getCapabilityName();
|
|
|
|
}
|
2013-10-03 21:40:08 +02:00
|
|
|
|
2013-10-09 22:44:41 +02:00
|
|
|
return null;
|
2013-10-03 21:40:08 +02:00
|
|
|
}
|
|
|
|
|
2015-06-14 23:30:05 +02:00
|
|
|
final public function isCapabilityEditable($capability) {
|
2013-10-03 21:40:08 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-14 23:30:05 +02:00
|
|
|
final public function getCapabilityCaption($capability) {
|
2013-10-03 21:40:08 +02:00
|
|
|
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');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-14 23:30:05 +02:00
|
|
|
final public function getCapabilityTemplatePHIDType($capability) {
|
2015-06-14 00:44:03 +02:00
|
|
|
switch ($capability) {
|
|
|
|
case PhabricatorPolicyCapability::CAN_VIEW:
|
|
|
|
case PhabricatorPolicyCapability::CAN_EDIT:
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2015-06-11 22:25:30 +02:00
|
|
|
$spec = $this->getCustomCapabilitySpecification($capability);
|
|
|
|
return idx($spec, 'template');
|
|
|
|
}
|
|
|
|
|
2015-06-22 20:46:59 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
Implement a scope selector for the global search
Summary:
See M1433. Fixes T7266. Fixes T4475. Ref T7314.
Future work/notes/etc:
- Write the User Guide (see TODO).
- This might needs some design tweaks -- I think it's functionally almost-equivalent to the mock, but the UI isn't quite the same.
- (Mobile design is a touch off-looking I think?)
- When you use a custom query, the duplicate "magnifying glass" icons are a little weird. Maybe change one or the other.
- Maybe worth adding an "Open Documents in Current Application" option? Planning to wait for feedback on that.
- Need a Quicksand integration to change the current application at some point.
- Searching in "Current Application" from, e.g., the 404 page just searches all documents. Current plan is to just document this behavior, since the icon is a pretty good callout and it seems plausible that this is intuitive enough that users won't have a hard time with it.
Test Plan:
New dropdown:
{F379150}
Device-ish:
{F379151}
Normal search (current application, from maniphest, selects tasks):
{F379153}
Application search from non-application:
{F379154}
Reviewers: btrahan, chad
Reviewed By: chad
Subscribers: johnny-bit, epriestley
Maniphest Tasks: T7266, T7314, T4475
Differential Revision: https://secure.phabricator.com/D12509
2015-04-22 23:31:36 +02:00
|
|
|
public function getApplicationSearchDocumentTypes() {
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
|
Allow ApplicationEditor forms to be reconfigured
Summary:
Ref T9132. This diff doesn't do anything interesting, it just lays the groundwork for more interesting future diffs.
Broadly, the idea here is to let you create multiple views of each edit form. For example, we might create several different "Create Task" forms, like:
- "New Bug Report"
- "New Feature Request"
These would be views of the "Create Task" form, but with various adjustments:
- A form might have additional instructions ("how to file a good bug report").
- A form might have prefilled values for some fields (like particular projects, subscribers, or policies).
- A form might have some fields locked (so they can not be edited) or hidden.
- A form might have a different field order.
- A form might have a limited visibility policy, so only some users can access it.
This diff adds a new storage object (`EditEngineConfiguration`) to keep track of all those customizations and represent "a form which has been configured to look and work a certain way".
This doesn't let these configurations do anything useful/interesting, and you can't access them directly yet, it's just all the boring plumbing to enable more interesting behavior in the future.
Test Plan:
ApplicationEditor forms now let you manage available forms and edit the current form:
{F959025}
There's a new (bare bones) list of all available engines:
{F959030}
And if you jump into an engine, you can see all the forms for it:
{F959038}
The actual form configurations have standard detail/edit pages. The edit pages are themselves driven by ApplicationEditor, of course, so you can edit the form for editing forms.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T9132
Differential Revision: https://secure.phabricator.com/D14453
2015-11-04 21:52:52 +01:00
|
|
|
protected function getEditRoutePattern($base = null) {
|
2015-11-17 18:33:06 +01:00
|
|
|
return $base.'(?:'.
|
|
|
|
'(?P<id>[0-9]\d*)/)?'.
|
|
|
|
'(?:'.
|
|
|
|
'(?:'.
|
2016-05-02 14:35:05 +02:00
|
|
|
'(?P<editAction>parameters|nodefault|nocreate|nomanage|comment)/'.
|
2015-11-17 18:33:06 +01:00
|
|
|
'|'.
|
2016-05-02 14:35:05 +02:00
|
|
|
'(?:form/(?P<formKey>[^/]+)/)?(?:page/(?P<pageKey>[^/]+)/)?'.
|
2015-11-17 18:33:06 +01:00
|
|
|
')'.
|
2016-05-02 14:35:05 +02:00
|
|
|
')?';
|
2015-11-03 14:38:06 +01:00
|
|
|
}
|
|
|
|
|
Allow ApplicationEditor forms to be reconfigured
Summary:
Ref T9132. This diff doesn't do anything interesting, it just lays the groundwork for more interesting future diffs.
Broadly, the idea here is to let you create multiple views of each edit form. For example, we might create several different "Create Task" forms, like:
- "New Bug Report"
- "New Feature Request"
These would be views of the "Create Task" form, but with various adjustments:
- A form might have additional instructions ("how to file a good bug report").
- A form might have prefilled values for some fields (like particular projects, subscribers, or policies).
- A form might have some fields locked (so they can not be edited) or hidden.
- A form might have a different field order.
- A form might have a limited visibility policy, so only some users can access it.
This diff adds a new storage object (`EditEngineConfiguration`) to keep track of all those customizations and represent "a form which has been configured to look and work a certain way".
This doesn't let these configurations do anything useful/interesting, and you can't access them directly yet, it's just all the boring plumbing to enable more interesting behavior in the future.
Test Plan:
ApplicationEditor forms now let you manage available forms and edit the current form:
{F959025}
There's a new (bare bones) list of all available engines:
{F959030}
And if you jump into an engine, you can see all the forms for it:
{F959038}
The actual form configurations have standard detail/edit pages. The edit pages are themselves driven by ApplicationEditor, of course, so you can edit the form for editing forms.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T9132
Differential Revision: https://secure.phabricator.com/D14453
2015-11-04 21:52:52 +01:00
|
|
|
protected function getQueryRoutePattern($base = null) {
|
|
|
|
return $base.'(?:query/(?P<queryKey>[^/]+)/)?';
|
|
|
|
}
|
|
|
|
|
2016-12-11 19:08:26 +01:00
|
|
|
protected function getProfileMenuRouting($controller) {
|
2016-01-13 00:06:43 +01:00
|
|
|
$edit_route = $this->getEditRoutePattern();
|
|
|
|
|
|
|
|
return array(
|
2016-12-11 19:08:26 +01:00
|
|
|
'(?P<itemAction>view)/(?P<itemID>[^/]+)/' => $controller,
|
|
|
|
'(?P<itemAction>hide)/(?P<itemID>[^/]+)/' => $controller,
|
|
|
|
'(?P<itemAction>default)/(?P<itemID>[^/]+)/' => $controller,
|
|
|
|
'(?P<itemAction>configure)/' => $controller,
|
|
|
|
'(?P<itemAction>reorder)/' => $controller,
|
|
|
|
'(?P<itemAction>edit)/'.$edit_route => $controller,
|
|
|
|
'(?P<itemAction>new)/(?<itemKey>[^/]+)/'.$edit_route => $controller,
|
|
|
|
'(?P<itemAction>builtin)/(?<itemID>[^/]+)/'.$edit_route
|
2016-01-13 00:06:43 +01:00
|
|
|
=> $controller,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2013-10-02 22:13:07 +02:00
|
|
|
}
|