diff --git a/src/applications/almanac/application/PhabricatorAlmanacApplication.php b/src/applications/almanac/application/PhabricatorAlmanacApplication.php index 88eafd333..a444554a0 100644 --- a/src/applications/almanac/application/PhabricatorAlmanacApplication.php +++ b/src/applications/almanac/application/PhabricatorAlmanacApplication.php @@ -1,93 +1,93 @@ pht('Alamanac User Guide'), 'href' => PhabricatorEnv::getDoclink('Almanac User Guide'), ), ); } public function isPrototype() { return true; } public function getRoutes() { return array( '/almanac/' => array( '' => 'AlmanacConsoleController', 'service/' => array( '(?:query/(?P[^/]+)/)?' => 'AlmanacServiceListController', 'edit/(?:(?P\d+)/)?' => 'AlmanacServiceEditController', 'view/(?P[^/]+)/' => 'AlmanacServiceViewController', ), 'device/' => array( '(?:query/(?P[^/]+)/)?' => 'AlmanacDeviceListController', 'edit/(?:(?P\d+)/)?' => 'AlmanacDeviceEditController', 'view/(?P[^/]+)/' => 'AlmanacDeviceViewController', ), 'interface/' => array( 'edit/(?:(?P\d+)/)?' => 'AlmanacInterfaceEditController', ), 'binding/' => array( 'edit/(?:(?P\d+)/)?' => 'AlmanacBindingEditController', '(?P\d+)/' => 'AlmanacBindingViewController', ), 'network/' => array( '(?:query/(?P[^/]+)/)?' => 'AlmanacNetworkListController', 'edit/(?:(?P\d+)/)?' => 'AlmanacNetworkEditController', '(?P\d+)/' => 'AlmanacNetworkViewController', ), 'property/' => array( 'edit/' => 'AlmanacPropertyEditController', 'delete/' => 'AlmanacPropertyDeleteController', ), ), ); } protected function getCustomCapabilities() { return array( AlmanacCreateServicesCapability::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_ADMIN, ), AlmanacCreateDevicesCapability::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_ADMIN, ), AlmanacCreateNetworksCapability::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_ADMIN, ), AlmanacCreateClusterServicesCapability::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_ADMIN, ), ); } } diff --git a/src/applications/almanac/controller/AlmanacConsoleController.php b/src/applications/almanac/controller/AlmanacConsoleController.php index 8abd8c611..9f46bd54d 100644 --- a/src/applications/almanac/controller/AlmanacConsoleController.php +++ b/src/applications/almanac/controller/AlmanacConsoleController.php @@ -1,53 +1,53 @@ getViewer(); $menu = id(new PHUIObjectItemListView()) ->setUser($viewer); $menu->addItem( id(new PHUIObjectItemView()) ->setHeader(pht('Services')) ->setHref($this->getApplicationURI('service/')) - ->setFontIcon('fa-plug') + ->setIcon('fa-plug') ->addAttribute(pht('Manage Almanac services.'))); $menu->addItem( id(new PHUIObjectItemView()) ->setHeader(pht('Devices')) ->setHref($this->getApplicationURI('device/')) - ->setFontIcon('fa-server') + ->setIcon('fa-server') ->addAttribute(pht('Manage Almanac devices.'))); $menu->addItem( id(new PHUIObjectItemView()) ->setHeader(pht('Networks')) ->setHref($this->getApplicationURI('network/')) - ->setFontIcon('fa-globe') + ->setIcon('fa-globe') ->addAttribute(pht('Manage Almanac networks.'))); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Console')); $box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Console')) ->setObjectList($menu); return $this->newPage() ->setTitle(pht('Almanac Console')) ->setCrumbs($crumbs) ->appendChild( array( $box, )); } } diff --git a/src/applications/audit/application/PhabricatorAuditApplication.php b/src/applications/audit/application/PhabricatorAuditApplication.php index 4f4c5a56d..66280a16a 100644 --- a/src/applications/audit/application/PhabricatorAuditApplication.php +++ b/src/applications/audit/application/PhabricatorAuditApplication.php @@ -1,97 +1,97 @@ pht('Audit User Guide'), 'href' => PhabricatorEnv::getDoclink('Audit User Guide'), ), ); } public function getRoutes() { return array( '/audit/' => array( '(?:query/(?P[^/]+)/)?' => 'PhabricatorAuditListController', 'addcomment/' => 'PhabricatorAuditAddCommentController', 'preview/(?P[1-9]\d*)/' => 'PhabricatorAuditPreviewController', ), ); } public function getApplicationOrder() { return 0.130; } public function loadStatus(PhabricatorUser $user) { $status = array(); $limit = self::MAX_STATUS_ITEMS; $phids = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user); $query = id(new DiffusionCommitQuery()) ->setViewer($user) ->withAuthorPHIDs(array($user->getPHID())) ->withAuditStatus(DiffusionCommitQuery::AUDIT_STATUS_CONCERN) ->setLimit($limit); $commits = $query->execute(); $count = count($commits); if ($count >= $limit) { $count_str = pht('%s+ Problem Commit(s)', new PhutilNumber($limit - 1)); } else { $count_str = pht('%s Problem Commit(s)', new PhutilNumber($count)); } $type = PhabricatorApplicationStatusView::TYPE_NEEDS_ATTENTION; $status[] = id(new PhabricatorApplicationStatusView()) ->setType($type) ->setText($count_str) ->setCount($count); $query = id(new DiffusionCommitQuery()) ->setViewer($user) ->withNeedsAuditByPHIDs($phids) ->withAuditStatus(DiffusionCommitQuery::AUDIT_STATUS_OPEN) ->setLimit($limit); $commits = $query->execute(); $count = count($commits); if ($count >= $limit) { $count_str = pht('%s+ Problem Commit(s)', new PhutilNumber($limit - 1)); } else { $count_str = pht('%s Problem Commit(s)', new PhutilNumber($count)); } $type = PhabricatorApplicationStatusView::TYPE_WARNING; $status[] = id(new PhabricatorApplicationStatusView()) ->setType($type) ->setText($count_str) ->setCount($count); return $status; } } diff --git a/src/applications/auth/application/PhabricatorAuthApplication.php b/src/applications/auth/application/PhabricatorAuthApplication.php index a9831b20e..808562e97 100644 --- a/src/applications/auth/application/PhabricatorAuthApplication.php +++ b/src/applications/auth/application/PhabricatorAuthApplication.php @@ -1,114 +1,114 @@ getIsAdmin(); } public function getName() { return pht('Auth'); } public function getShortDescription() { return pht('Login/Registration'); } public function getHelpDocumentationArticles(PhabricatorUser $viewer) { // NOTE: Although reasonable help exists for this in "Configuring Accounts // and Registration", specifying help items here means we get the menu // item in all the login/link interfaces, which is confusing and not // helpful. // TODO: Special case this, or split the auth and auth administration // applications? return array(); } public function getApplicationGroup() { return self::GROUP_ADMIN; } public function getRoutes() { return array( '/auth/' => array( '' => 'PhabricatorAuthListController', 'config/' => array( 'new/' => 'PhabricatorAuthNewController', 'new/(?P[^/]+)/' => 'PhabricatorAuthEditController', 'edit/(?P\d+)/' => 'PhabricatorAuthEditController', '(?Penable|disable)/(?P\d+)/' => 'PhabricatorAuthDisableController', ), 'login/(?P[^/]+)/(?:(?P[^/]+)/)?' => 'PhabricatorAuthLoginController', '(?Ploggedout)/' => 'PhabricatorAuthStartController', 'invite/(?P[^/]+)/' => 'PhabricatorAuthInviteController', 'register/(?:(?P[^/]+)/)?' => 'PhabricatorAuthRegisterController', 'start/' => 'PhabricatorAuthStartController', 'validate/' => 'PhabricatorAuthValidateController', 'finish/' => 'PhabricatorAuthFinishController', 'unlink/(?P[^/]+)/' => 'PhabricatorAuthUnlinkController', '(?Plink|refresh)/(?P[^/]+)/' => 'PhabricatorAuthLinkController', 'confirmlink/(?P[^/]+)/' => 'PhabricatorAuthConfirmLinkController', 'session/terminate/(?P[^/]+)/' => 'PhabricatorAuthTerminateSessionController', 'token/revoke/(?P[^/]+)/' => 'PhabricatorAuthRevokeTokenController', 'session/downgrade/' => 'PhabricatorAuthDowngradeSessionController', 'multifactor/' => 'PhabricatorAuthNeedsMultiFactorController', 'sshkey/' => array( 'generate/' => 'PhabricatorAuthSSHKeyGenerateController', 'upload/' => 'PhabricatorAuthSSHKeyEditController', 'edit/(?P\d+)/' => 'PhabricatorAuthSSHKeyEditController', 'delete/(?P\d+)/' => 'PhabricatorAuthSSHKeyDeleteController', ), ), '/oauth/(?P\w+)/login/' => 'PhabricatorAuthOldOAuthRedirectController', '/login/' => array( '' => 'PhabricatorAuthStartController', 'email/' => 'PhabricatorEmailLoginController', 'once/'. '(?P[^/]+)/'. '(?P\d+)/'. '(?P[^/]+)/'. '(?:(?P\d+)/)?' => 'PhabricatorAuthOneTimeLoginController', 'refresh/' => 'PhabricatorRefreshCSRFController', 'mustverify/' => 'PhabricatorMustVerifyEmailController', ), '/emailverify/(?P[^/]+)/' => 'PhabricatorEmailVerificationController', '/logout/' => 'PhabricatorLogoutController', ); } protected function getCustomCapabilities() { return array( AuthManageProvidersCapability::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_ADMIN, ), ); } } diff --git a/src/applications/badges/application/PhabricatorBadgesApplication.php b/src/applications/badges/application/PhabricatorBadgesApplication.php index dea41ac24..887a11833 100644 --- a/src/applications/badges/application/PhabricatorBadgesApplication.php +++ b/src/applications/badges/application/PhabricatorBadgesApplication.php @@ -1,75 +1,75 @@ array( '(?:query/(?P[^/]+)/)?' => 'PhabricatorBadgesListController', 'create/' => 'PhabricatorBadgesEditController', 'comment/(?P[1-9]\d*)/' => 'PhabricatorBadgesCommentController', 'edit/(?:(?P\d+)/)?' => 'PhabricatorBadgesEditController', 'archive/(?:(?P\d+)/)?' => 'PhabricatorBadgesArchiveController', 'view/(?:(?P\d+)/)?' => 'PhabricatorBadgesViewController', 'recipients/(?P[1-9]\d*)/' => 'PhabricatorBadgesEditRecipientsController', 'recipients/(?P[1-9]\d*)/remove/' => 'PhabricatorBadgesRemoveRecipientsController', ), ); } protected function getCustomCapabilities() { return array( PhabricatorBadgesCreateCapability::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_ADMIN, 'caption' => pht('Default create policy for badges.'), ), PhabricatorBadgesDefaultEditCapability::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_ADMIN, 'caption' => pht('Default edit policy for badges.'), 'template' => PhabricatorBadgesPHIDType::TYPECONST, ), ); } } diff --git a/src/applications/badges/query/PhabricatorBadgesSearchEngine.php b/src/applications/badges/query/PhabricatorBadgesSearchEngine.php index ab8432833..4bad4d2c6 100644 --- a/src/applications/badges/query/PhabricatorBadgesSearchEngine.php +++ b/src/applications/badges/query/PhabricatorBadgesSearchEngine.php @@ -1,163 +1,163 @@ setParameter( 'statuses', $this->readListFromRequest($request, 'statuses')); $saved->setParameter( 'qualities', $this->readListFromRequest($request, 'qualities')); return $saved; } protected function buildCustomSearchFields() { return array( id(new PhabricatorSearchCheckboxesField()) ->setKey('qualities') ->setLabel(pht('Quality')) ->setOptions( id(new PhabricatorBadgesBadge()) ->getQualityNameMap()), id(new PhabricatorSearchCheckboxesField()) ->setKey('statuses') ->setLabel(pht('Status')) ->setOptions( id(new PhabricatorBadgesBadge()) ->getStatusNameMap()), ); } protected function buildQueryFromParameters(array $map) { $query = $this->newQuery(); if ($map['statuses']) { $query->withStatuses($map['statuses']); } if ($map['qualities']) { $query->withQualities($map['qualities']); } return $query; } protected function getURI($path) { return '/badges/'.$path; } protected function getBuiltinQueryNames() { $names = array(); $names['open'] = pht('Active Badges'); $names['all'] = pht('All Badges'); return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); switch ($query_key) { case 'all': return $query; case 'open': return $query->setParameter( 'statuses', array( PhabricatorBadgesBadge::STATUS_ACTIVE, )); } return parent::buildSavedQueryFromBuiltin($query_key); } protected function getRequiredHandlePHIDsForResultList( array $badges, PhabricatorSavedQuery $query) { $phids = array(); return $phids; } protected function renderResultList( array $badges, PhabricatorSavedQuery $query, array $handles) { assert_instances_of($badges, 'PhabricatorBadgesBadge'); $viewer = $this->requireViewer(); $list = id(new PHUIObjectItemListView()); foreach ($badges as $badge) { $quality = idx($badge->getQualityNameMap(), $badge->getQuality()); $mini_badge = id(new PHUIBadgeMiniView()) ->setHeader($badge->getName()) ->setIcon($badge->getIcon()) ->setQuality($badge->getQuality()); $item = id(new PHUIObjectItemView()) ->setHeader($badge->getName()) ->setBadge($mini_badge) ->setHref('/badges/view/'.$badge->getID().'/') ->addAttribute($quality) ->addAttribute($badge->getFlavor()); if ($badge->isArchived()) { $item->setDisabled(true); $item->addIcon('fa-ban', pht('Archived')); } $list->addItem($item); } $result = new PhabricatorApplicationSearchResultView(); $result->setObjectList($list); $result->setNoDataString(pht('No badges found.')); return $result; } protected function getNewUserBody() { $create_button = id(new PHUIButtonView()) ->setTag('a') ->setText(pht('Create a Badge')) ->setHref('/badges/create/') ->setColor(PHUIButtonView::GREEN); - $icon = $this->getApplication()->getFontIcon(); + $icon = $this->getApplication()->getIcon(); $app_name = $this->getApplication()->getName(); $view = id(new PHUIBigInfoView()) ->setIcon($icon) ->setTitle(pht('Welcome to %s', $app_name)) ->setDescription( pht('Badges let you award and distinguish special users '. 'throughout your instance.')) ->addAction($create_button); return $view; } } diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php index 0ed491780..0bbb38ff8 100644 --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -1,654 +1,654 @@ 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() { + public function getIcon() { 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 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 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 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[0-9]\d*)/)?'. '(?:'. '(?:'. '(?Pparameters|nodefault|nocreate|nomanage|comment)'. '|'. '(?:form/(?P[^/]+))'. ')'. '/)?'; } protected function getQueryRoutePattern($base = null) { return $base.'(?:query/(?P[^/]+)/)?'; } protected function getPanelRouting($controller) { $edit_route = $this->getEditRoutePattern(); return array( '(?Pview)/(?P[^/]+)/' => $controller, '(?Phide)/(?P[^/]+)/' => $controller, '(?Pdefault)/(?P[^/]+)/' => $controller, '(?Pconfigure)/' => $controller, '(?Preorder)/' => $controller, '(?Pedit)/'.$edit_route => $controller, '(?Pnew)/(?[^/]+)/'.$edit_route => $controller, '(?Pbuiltin)/(?[^/]+)/'.$edit_route => $controller, ); } } diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index 1fe68abab..e6997d97a 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -1,590 +1,590 @@ shouldRequireLogin()) { return false; } if (!$this->shouldRequireEnabledUser()) { return false; } if ($this->shouldAllowPartialSessions()) { return false; } $user = $this->getRequest()->getUser(); if (!$user->getIsStandardUser()) { return false; } return PhabricatorEnv::getEnvConfig('security.require-multi-factor-auth'); } public function shouldAllowLegallyNonCompliantUsers() { return false; } public function isGlobalDragAndDropUploadEnabled() { return false; } public function willBeginExecution() { $request = $this->getRequest(); if ($request->getUser()) { // NOTE: Unit tests can set a user explicitly. Normal requests are not // permitted to do this. PhabricatorTestCase::assertExecutingUnitTests(); $user = $request->getUser(); } else { $user = new PhabricatorUser(); $session_engine = new PhabricatorAuthSessionEngine(); $phsid = $request->getCookie(PhabricatorCookies::COOKIE_SESSION); if (strlen($phsid)) { $session_user = $session_engine->loadUserForSession( PhabricatorAuthSession::TYPE_WEB, $phsid); if ($session_user) { $user = $session_user; } } else { // If the client doesn't have a session token, generate an anonymous // session. This is used to provide CSRF protection to logged-out users. $phsid = $session_engine->establishSession( PhabricatorAuthSession::TYPE_WEB, null, $partial = false); // This may be a resource request, in which case we just don't set // the cookie. if ($request->canSetCookies()) { $request->setCookie(PhabricatorCookies::COOKIE_SESSION, $phsid); } } if (!$user->isLoggedIn()) { $user->attachAlternateCSRFString(PhabricatorHash::digest($phsid)); } $request->setUser($user); } PhabricatorEnv::setLocaleCode($user->getTranslation()); $preferences = $user->loadPreferences(); if (PhabricatorEnv::getEnvConfig('darkconsole.enabled')) { $dark_console = PhabricatorUserPreferences::PREFERENCE_DARK_CONSOLE; if ($preferences->getPreference($dark_console) || PhabricatorEnv::getEnvConfig('darkconsole.always-on')) { $console = new DarkConsoleCore(); $request->getApplicationConfiguration()->setConsole($console); } } // NOTE: We want to set up the user first so we can render a real page // here, but fire this before any real logic. $restricted = array( 'code', ); foreach ($restricted as $parameter) { if ($request->getExists($parameter)) { if (!$this->shouldAllowRestrictedParameter($parameter)) { throw new Exception( pht( 'Request includes restricted parameter "%s", but this '. 'controller ("%s") does not whitelist it. Refusing to '. 'serve this request because it might be part of a redirection '. 'attack.', $parameter, get_class($this))); } } } if ($this->shouldRequireEnabledUser()) { if ($user->isLoggedIn() && !$user->getIsApproved()) { $controller = new PhabricatorAuthNeedsApprovalController(); return $this->delegateToController($controller); } if ($user->getIsDisabled()) { $controller = new PhabricatorDisabledUserController(); return $this->delegateToController($controller); } } $auth_class = 'PhabricatorAuthApplication'; $auth_application = PhabricatorApplication::getByClass($auth_class); // Require partial sessions to finish login before doing anything. if (!$this->shouldAllowPartialSessions()) { if ($user->hasSession() && $user->getSession()->getIsPartial()) { $login_controller = new PhabricatorAuthFinishController(); $this->setCurrentApplication($auth_application); return $this->delegateToController($login_controller); } } // Check if the user needs to configure MFA. $need_mfa = $this->shouldRequireMultiFactorEnrollment(); $have_mfa = $user->getIsEnrolledInMultiFactor(); if ($need_mfa && !$have_mfa) { // Check if the cache is just out of date. Otherwise, roadblock the user // and require MFA enrollment. $user->updateMultiFactorEnrollment(); if (!$user->getIsEnrolledInMultiFactor()) { $mfa_controller = new PhabricatorAuthNeedsMultiFactorController(); $this->setCurrentApplication($auth_application); return $this->delegateToController($mfa_controller); } } if ($this->shouldRequireLogin()) { // This actually means we need either: // - a valid user, or a public controller; and // - permission to see the application; and // - permission to see at least one Space if spaces are configured. $allow_public = $this->shouldAllowPublic() && PhabricatorEnv::getEnvConfig('policy.allow-public'); // If this controller isn't public, and the user isn't logged in, require // login. if (!$allow_public && !$user->isLoggedIn()) { $login_controller = new PhabricatorAuthStartController(); $this->setCurrentApplication($auth_application); return $this->delegateToController($login_controller); } if ($user->isLoggedIn()) { if ($this->shouldRequireEmailVerification()) { if (!$user->getIsEmailVerified()) { $controller = new PhabricatorMustVerifyEmailController(); $this->setCurrentApplication($auth_application); return $this->delegateToController($controller); } } } // If Spaces are configured, require that the user have access to at // least one. If we don't do this, they'll get confusing error messages // later on. $spaces = PhabricatorSpacesNamespaceQuery::getSpacesExist(); if ($spaces) { $viewer_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces( $user); if (!$viewer_spaces) { $controller = new PhabricatorSpacesNoAccessController(); return $this->delegateToController($controller); } } // If the user doesn't have access to the application, don't let them use // any of its controllers. We query the application in order to generate // a policy exception if the viewer doesn't have permission. $application = $this->getCurrentApplication(); if ($application) { id(new PhabricatorApplicationQuery()) ->setViewer($user) ->withPHIDs(array($application->getPHID())) ->executeOne(); } } if (!$this->shouldAllowLegallyNonCompliantUsers()) { $legalpad_class = 'PhabricatorLegalpadApplication'; $legalpad = id(new PhabricatorApplicationQuery()) ->setViewer($user) ->withClasses(array($legalpad_class)) ->withInstalled(true) ->execute(); $legalpad = head($legalpad); $doc_query = id(new LegalpadDocumentQuery()) ->setViewer($user) ->withSignatureRequired(1) ->needViewerSignatures(true); if ($user->hasSession() && !$user->getSession()->getIsPartial() && !$user->getSession()->getSignedLegalpadDocuments() && $user->isLoggedIn() && $legalpad) { $sign_docs = $doc_query->execute(); $must_sign_docs = array(); foreach ($sign_docs as $sign_doc) { if (!$sign_doc->getUserSignature($user->getPHID())) { $must_sign_docs[] = $sign_doc; } } if ($must_sign_docs) { $controller = new LegalpadDocumentSignController(); $this->getRequest()->setURIMap(array( 'id' => head($must_sign_docs)->getID(), )); $this->setCurrentApplication($legalpad); return $this->delegateToController($controller); } else { $engine = id(new PhabricatorAuthSessionEngine()) ->signLegalpadDocuments($user, $sign_docs); } } } // NOTE: We do this last so that users get a login page instead of a 403 // if they need to login. if ($this->shouldRequireAdmin() && !$user->getIsAdmin()) { return new Aphront403Response(); } } public function getApplicationURI($path = '') { if (!$this->getCurrentApplication()) { throw new Exception(pht('No application!')); } return $this->getCurrentApplication()->getApplicationURI($path); } public function willSendResponse(AphrontResponse $response) { $request = $this->getRequest(); if ($response instanceof AphrontDialogResponse) { if (!$request->isAjax() && !$request->isQuicksand()) { $dialog = $response->getDialog(); $title = $dialog->getTitle(); $short = $dialog->getShortTitle(); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(coalesce($short, $title)); $page_content = array( $crumbs, $response->buildResponseString(), ); $view = id(new PhabricatorStandardPageView()) ->setRequest($request) ->setController($this) ->setDeviceReady(true) ->setTitle($title) ->appendChild($page_content); $response = id(new AphrontWebpageResponse()) ->setContent($view->render()) ->setHTTPResponseCode($response->getHTTPResponseCode()); } else { $response->getDialog()->setIsStandalone(true); return id(new AphrontAjaxResponse()) ->setContent(array( 'dialog' => $response->buildResponseString(), )); } } else if ($response instanceof AphrontRedirectResponse) { if ($request->isAjax() || $request->isQuicksand()) { return id(new AphrontAjaxResponse()) ->setContent( array( 'redirect' => $response->getURI(), )); } } return $response; } /** * WARNING: Do not call this in new code. * * @deprecated See "Handles Technical Documentation". */ protected function loadViewerHandles(array $phids) { return id(new PhabricatorHandleQuery()) ->setViewer($this->getRequest()->getUser()) ->withPHIDs($phids) ->execute(); } public function buildApplicationMenu() { return null; } protected function buildApplicationCrumbs() { $crumbs = array(); $application = $this->getCurrentApplication(); if ($application) { - $icon = $application->getFontIcon(); + $icon = $application->getIcon(); if (!$icon) { $icon = 'fa-puzzle'; } $crumbs[] = id(new PHUICrumbView()) ->setHref($this->getApplicationURI()) ->setName($application->getName()) ->setIcon($icon); } $view = new PHUICrumbsView(); foreach ($crumbs as $crumb) { $view->addCrumb($crumb); } return $view; } protected function hasApplicationCapability($capability) { return PhabricatorPolicyFilter::hasCapability( $this->getRequest()->getUser(), $this->getCurrentApplication(), $capability); } protected function requireApplicationCapability($capability) { PhabricatorPolicyFilter::requireCapability( $this->getRequest()->getUser(), $this->getCurrentApplication(), $capability); } protected function explainApplicationCapability( $capability, $positive_message, $negative_message) { $can_act = $this->hasApplicationCapability($capability); if ($can_act) { $message = $positive_message; $icon_name = 'fa-play-circle-o lightgreytext'; } else { $message = $negative_message; $icon_name = 'fa-lock'; } $icon = id(new PHUIIconView()) ->setIcon($icon_name); require_celerity_resource('policy-css'); $phid = $this->getCurrentApplication()->getPHID(); $explain_uri = "/policy/explain/{$phid}/{$capability}/"; $message = phutil_tag( 'div', array( 'class' => 'policy-capability-explanation', ), array( $icon, javelin_tag( 'a', array( 'href' => $explain_uri, 'sigil' => 'workflow', ), $message), )); return array($can_act, $message); } public function getDefaultResourceSource() { return 'phabricator'; } /** * Create a new @{class:AphrontDialogView} with defaults filled in. * * @return AphrontDialogView New dialog. */ public function newDialog() { $submit_uri = new PhutilURI($this->getRequest()->getRequestURI()); $submit_uri = $submit_uri->getPath(); return id(new AphrontDialogView()) ->setUser($this->getRequest()->getUser()) ->setSubmitURI($submit_uri); } public function newPage() { $page = id(new PhabricatorStandardPageView()) ->setRequest($this->getRequest()) ->setController($this) ->setDeviceReady(true); $application = $this->getCurrentApplication(); if ($application) { $page->setApplicationName($application->getName()); if ($application->getTitleGlyph()) { $page->setGlyph($application->getTitleGlyph()); } } $viewer = $this->getRequest()->getUser(); if ($viewer) { $page->setUser($viewer); } return $page; } public function newApplicationMenu() { return id(new PHUIApplicationMenuView()) ->setViewer($this->getRequest()->getUser()); } protected function buildTransactionTimeline( PhabricatorApplicationTransactionInterface $object, PhabricatorApplicationTransactionQuery $query, PhabricatorMarkupEngine $engine = null, $render_data = array()) { $viewer = $this->getRequest()->getUser(); $xaction = $object->getApplicationTransactionTemplate(); $view = $xaction->getApplicationTransactionViewObject(); $pager = id(new AphrontCursorPagerView()) ->readFromRequest($this->getRequest()) ->setURI(new PhutilURI( '/transactions/showolder/'.$object->getPHID().'/')); $xactions = $query ->setViewer($viewer) ->withObjectPHIDs(array($object->getPHID())) ->needComments(true) ->executeWithCursorPager($pager); $xactions = array_reverse($xactions); if ($engine) { foreach ($xactions as $xaction) { if ($xaction->getComment()) { $engine->addObject( $xaction->getComment(), PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT); } } $engine->process(); $view->setMarkupEngine($engine); } $timeline = $view ->setUser($viewer) ->setObjectPHID($object->getPHID()) ->setTransactions($xactions) ->setPager($pager) ->setRenderData($render_data) ->setQuoteTargetID($this->getRequest()->getStr('quoteTargetID')) ->setQuoteRef($this->getRequest()->getStr('quoteRef')); $object->willRenderTimeline($timeline, $this->getRequest()); return $timeline; } public function buildApplicationCrumbsForEditEngine() { // TODO: This is kind of gross, I'm bascially just making this public so // I can use it in EditEngine. We could do this without making it public // by using controller delegation, or make it properly public. return $this->buildApplicationCrumbs(); } /* -( Deprecated )--------------------------------------------------------- */ /** * DEPRECATED. Use @{method:newPage}. */ public function buildStandardPageView() { return $this->newPage(); } /** * DEPRECATED. Use @{method:newPage}. */ public function buildStandardPageResponse($view, array $data) { $page = $this->buildStandardPageView(); $page->appendChild($view); return $page->produceAphrontResponse(); } /** * DEPRECATED. Use @{method:newPage}. */ public function buildApplicationPage($view, array $options) { $page = $this->newPage(); $title = PhabricatorEnv::getEnvConfig('phabricator.serious-business') ? 'Phabricator' : pht('Bacon Ice Cream for Breakfast'); $page->setTitle(idx($options, 'title', $title)); if (idx($options, 'class')) { $page->addClass($options['class']); } if (!($view instanceof AphrontSideNavFilterView)) { $nav = new AphrontSideNavFilterView(); $nav->appendChild($view); $view = $nav; } $page->appendChild($view); $object_phids = idx($options, 'pageObjects', array()); if ($object_phids) { $page->setPageObjectPHIDs($object_phids); } if (!idx($options, 'device', true)) { $page->setDeviceReady(false); } $page->setShowFooter(idx($options, 'showFooter', true)); $page->setShowChrome(idx($options, 'chrome', true)); return $page->produceAphrontResponse(); } } diff --git a/src/applications/calendar/application/PhabricatorCalendarApplication.php b/src/applications/calendar/application/PhabricatorCalendarApplication.php index 1f8ea5738..cf05e9666 100644 --- a/src/applications/calendar/application/PhabricatorCalendarApplication.php +++ b/src/applications/calendar/application/PhabricatorCalendarApplication.php @@ -1,102 +1,102 @@ [1-9]\d*)(?:/(?P\d+))?' => 'PhabricatorCalendarEventViewController', '/calendar/' => array( '(?:query/(?P[^/]+)/(?:(?P\d+)/'. '(?P\d+)/)?(?:(?P\d+)/)?)?' => 'PhabricatorCalendarEventListController', 'event/' => array( 'create/' => 'PhabricatorCalendarEventEditController', 'edit/(?P[1-9]\d*)/(?:(?P\d+)/)?' => 'PhabricatorCalendarEventEditController', 'drag/(?P[1-9]\d*)/' => 'PhabricatorCalendarEventDragController', 'cancel/(?P[1-9]\d*)/(?:(?P\d+)/)?' => 'PhabricatorCalendarEventCancelController', '(?Pjoin|decline|accept)/(?P[1-9]\d*)/' => 'PhabricatorCalendarEventJoinController', 'comment/(?P[1-9]\d*)/(?:(?P\d+)/)?' => 'PhabricatorCalendarEventCommentController', ), ), ); } public function getHelpDocumentationArticles(PhabricatorUser $viewer) { return array( array( 'name' => pht('Calendar User Guide'), 'href' => PhabricatorEnv::getDoclink('Calendar User Guide'), ), ); } public function getQuickCreateItems(PhabricatorUser $viewer) { $items = array(); $item = id(new PHUIListItemView()) ->setName(pht('Calendar Event')) ->setIcon('fa-calendar') ->setHref($this->getBaseURI().'event/create/'); $items[] = $item; return $items; } public function getMailCommandObjects() { return array( 'event' => array( 'name' => pht('Email Commands: Events'), 'header' => pht('Interacting with Calendar Events'), 'object' => new PhabricatorCalendarEvent(), 'summary' => pht( 'This page documents the commands you can use to interact with '. 'events in Calendar. These commands work when creating new tasks '. 'via email and when replying to existing tasks.'), ), ); } } diff --git a/src/applications/chatlog/application/PhabricatorChatLogApplication.php b/src/applications/chatlog/application/PhabricatorChatLogApplication.php index b2023ce07..912ddd9b6 100644 --- a/src/applications/chatlog/application/PhabricatorChatLogApplication.php +++ b/src/applications/chatlog/application/PhabricatorChatLogApplication.php @@ -1,43 +1,43 @@ array( '' => 'PhabricatorChatLogChannelListController', 'channel/(?P[^/]+)/' => 'PhabricatorChatLogChannelLogController', ), ); } } diff --git a/src/applications/conduit/application/PhabricatorConduitApplication.php b/src/applications/conduit/application/PhabricatorConduitApplication.php index 7a0ca0e4b..a036860aa 100644 --- a/src/applications/conduit/application/PhabricatorConduitApplication.php +++ b/src/applications/conduit/application/PhabricatorConduitApplication.php @@ -1,65 +1,65 @@ pht('Conduit API Overview'), 'href' => PhabricatorEnv::getDoclink('Conduit API Overview'), ), ); } public function getName() { return pht('Conduit'); } public function getShortDescription() { return pht('Developer API'); } public function getTitleGlyph() { return "\xE2\x87\xB5"; } public function getApplicationGroup() { return self::GROUP_DEVELOPER; } public function getApplicationOrder() { return 0.100; } public function getRoutes() { return array( '/conduit/' => array( '(?:query/(?P[^/]+)/)?' => 'PhabricatorConduitListController', 'method/(?P[^/]+)/' => 'PhabricatorConduitConsoleController', 'log/(?:query/(?P[^/]+)/)?' => 'PhabricatorConduitLogController', 'log/view/(?P[^/]+)/' => 'PhabricatorConduitLogController', 'token/' => 'PhabricatorConduitTokenController', 'token/edit/(?:(?P\d+)/)?' => 'PhabricatorConduitTokenEditController', 'token/terminate/(?:(?P\d+)/)?' => 'PhabricatorConduitTokenTerminateController', 'login/' => 'PhabricatorConduitTokenHandshakeController', ), '/api/(?P[^/]+)' => 'PhabricatorConduitAPIController', ); } } diff --git a/src/applications/config/application/PhabricatorConfigApplication.php b/src/applications/config/application/PhabricatorConfigApplication.php index 36bac1d25..8570484e3 100644 --- a/src/applications/config/application/PhabricatorConfigApplication.php +++ b/src/applications/config/application/PhabricatorConfigApplication.php @@ -1,69 +1,69 @@ getIsAdmin(); } public function getTitleGlyph() { return "\xE2\x98\xBA"; } public function getApplicationGroup() { return self::GROUP_ADMIN; } public function canUninstall() { return false; } public function getName() { return 'Config'; } public function getShortDescription() { return pht('Configure Phabricator'); } public function getRoutes() { return array( '/config/' => array( '' => 'PhabricatorConfigListController', 'all/' => 'PhabricatorConfigAllController', 'history/' => 'PhabricatorConfigHistoryController', 'edit/(?P[\w\.\-]+)/' => 'PhabricatorConfigEditController', 'group/(?P[^/]+)/' => 'PhabricatorConfigGroupController', 'welcome/' => 'PhabricatorConfigWelcomeController', 'database/'. '(?:(?P[^/]+)/'. '(?:(?P[^/]+)/'. '(?:(?:col/(?P[^/]+)|key/(?P[^/]+))/)?)?)?' => 'PhabricatorConfigDatabaseStatusController', 'dbissue/' => 'PhabricatorConfigDatabaseIssueController', '(?Pignore|unignore)/(?P[^/]+)/' => 'PhabricatorConfigIgnoreController', 'issue/' => array( '' => 'PhabricatorConfigIssueListController', '(?P[^/]+)/' => 'PhabricatorConfigIssueViewController', ), 'cache/' => array( '' => 'PhabricatorConfigCacheController', 'purge/' => 'PhabricatorConfigPurgeCacheController', ), 'module/' => array( '(?P[^/]+)/' => 'PhabricatorConfigModuleController', ), ), ); } } diff --git a/src/applications/config/controller/PhabricatorConfigListController.php b/src/applications/config/controller/PhabricatorConfigListController.php index 6a1823ecd..9e87995eb 100644 --- a/src/applications/config/controller/PhabricatorConfigListController.php +++ b/src/applications/config/controller/PhabricatorConfigListController.php @@ -1,64 +1,64 @@ getViewer(); $nav = $this->buildSideNavView(); $nav->selectFilter('/'); $groups = PhabricatorApplicationConfigOptions::loadAll(); $core_list = $this->buildConfigOptionsList($groups, 'core'); $apps_list = $this->buildConfigOptionsList($groups, 'apps'); $title = pht('Phabricator Configuration'); $core = id(new PHUIObjectBoxView()) ->setHeaderText($title) ->setObjectList($core_list); $apps = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Applications Configuration')) ->setObjectList($apps_list); $nav->appendChild( array( $core, $apps, )); $crumbs = $this ->buildApplicationCrumbs() ->addTextCrumb(pht('Config'), $this->getApplicationURI()); $nav->setCrumbs($crumbs); return $this->buildApplicationPage( $nav, array( 'title' => $title, )); } private function buildConfigOptionsList(array $groups, $type) { assert_instances_of($groups, 'PhabricatorApplicationConfigOptions'); $list = new PHUIObjectItemListView(); $groups = msort($groups, 'getName'); foreach ($groups as $group) { if ($group->getGroup() == $type) { $item = id(new PHUIObjectItemView()) ->setHeader($group->getName()) ->setHref('/config/group/'.$group->getKey().'/') ->addAttribute($group->getDescription()) - ->setFontIcon($group->getFontIcon()); + ->setIcon($group->getIcon()); $list->addItem($item); } } return $list; } } diff --git a/src/applications/config/module/PhabricatorConfigPHIDModule.php b/src/applications/config/module/PhabricatorConfigPHIDModule.php index 0a1eaf7b3..cd7d0e009 100644 --- a/src/applications/config/module/PhabricatorConfigPHIDModule.php +++ b/src/applications/config/module/PhabricatorConfigPHIDModule.php @@ -1,79 +1,79 @@ getViewer(); $types = PhabricatorPHIDType::getAllTypes(); $types = msort($types, 'getTypeConstant'); $rows = array(); foreach ($types as $key => $type) { $class_name = $type->getPHIDTypeApplicationClass(); if ($class_name !== null) { $app = PhabricatorApplication::getByClass($class_name); $app_name = $app->getName(); - $icon = $app->getFontIcon(); + $icon = $app->getIcon(); if ($icon) { $app_icon = id(new PHUIIconView())->setIcon($icon); } else { $app_icon = null; } } else { $app_name = null; $app_icon = null; } $icon = $type->getTypeIcon(); if ($icon) { $type_icon = id(new PHUIIconView())->setIcon($icon); } else { $type_icon = null; } $rows[] = array( $type->getTypeConstant(), get_class($type), $app_icon, $app_name, $type_icon, $type->getTypeName(), ); } $table = id(new AphrontTableView($rows)) ->setHeaders( array( pht('Constant'), pht('Class'), null, pht('Application'), null, pht('Name'), )) ->setColumnClasses( array( null, 'pri', 'icon', null, 'icon', 'wide', )); return id(new PHUIObjectBoxView()) ->setHeaderText(pht('PHID Types')) ->setTable($table); } } diff --git a/src/applications/config/option/PhabricatorAWSConfigOptions.php b/src/applications/config/option/PhabricatorAWSConfigOptions.php index af328ba0a..faed807ae 100644 --- a/src/applications/config/option/PhabricatorAWSConfigOptions.php +++ b/src/applications/config/option/PhabricatorAWSConfigOptions.php @@ -1,66 +1,66 @@ newOption('amazon-ses.access-key', 'string', null) ->setLocked(true) ->setDescription(pht('Access key for Amazon SES.')), $this->newOption('amazon-ses.secret-key', 'string', null) ->setHidden(true) ->setDescription(pht('Secret key for Amazon SES.')), $this->newOption('amazon-s3.access-key', 'string', null) ->setLocked(true) ->setDescription(pht('Access key for Amazon S3.')), $this->newOption('amazon-s3.secret-key', 'string', null) ->setHidden(true) ->setDescription(pht('Secret key for Amazon S3.')), $this->newOption('amazon-s3.region', 'string', null) ->setLocked(true) ->setDescription( pht( 'Amazon S3 region where your S3 bucket is located. When you '. 'specify a region, you should also specify a corresponding '. 'endpoint with `amazon-s3.endpoint`. You can find a list of '. 'available regions and endpoints in the AWS documentation.')) ->addExample('us-west-1', pht('USWest Region')), $this->newOption('amazon-s3.endpoint', 'string', null) ->setLocked(true) ->setDescription( pht( 'Explicit S3 endpoint to use. This should be the endpoint '. 'which corresponds to the region you have selected in '. '`amazon-s3.region`. Phabricator can not determine the correct '. 'endpoint automatically because some endpoint locations are '. 'irregular.')) ->addExample( 's3-us-west-1.amazonaws.com', pht('Use specific endpoint')), $this->newOption('amazon-ec2.access-key', 'string', null) ->setLocked(true) ->setDescription(pht('Access key for Amazon EC2.')), $this->newOption('amazon-ec2.secret-key', 'string', null) ->setHidden(true) ->setDescription(pht('Secret key for Amazon EC2.')), ); } } diff --git a/src/applications/config/option/PhabricatorAccessLogConfigOptions.php b/src/applications/config/option/PhabricatorAccessLogConfigOptions.php index 5bc55f64a..6183f9936 100644 --- a/src/applications/config/option/PhabricatorAccessLogConfigOptions.php +++ b/src/applications/config/option/PhabricatorAccessLogConfigOptions.php @@ -1,135 +1,135 @@ pht('The controller or workflow which handled the request.'), 'c' => pht('The HTTP response code or process exit code.'), 'D' => pht('The request date.'), 'e' => pht('Epoch timestamp.'), 'h' => pht("The webserver's host name."), 'p' => pht('The PID of the server process.'), 'r' => pht('The remote IP.'), 'T' => pht('The request duration, in microseconds.'), 'U' => pht('The request path, or request target.'), 'm' => pht('For conduit, the Conduit method which was invoked.'), 'u' => pht('The logged-in username, if one is logged in.'), 'P' => pht('The logged-in user PHID, if one is logged in.'), 'i' => pht('Request input, in bytes.'), 'o' => pht('Request output, in bytes.'), ); $http_map = $common_map + array( 'R' => pht('The HTTP referrer.'), 'M' => pht('The HTTP method.'), ); $ssh_map = $common_map + array( 's' => pht('The system user.'), 'S' => pht('The system sudo user.'), 'k' => pht('ID of the SSH key used to authenticate the request.'), ); $http_desc = pht( 'Format for the HTTP access log. Use `%s` to set the path. '. 'Available variables are:', 'log.access.path'); $http_desc .= "\n\n"; $http_desc .= $this->renderMapHelp($http_map); $ssh_desc = pht( 'Format for the SSH access log. Use %s to set the path. '. 'Available variables are:', 'log.ssh.path'); $ssh_desc .= "\n\n"; $ssh_desc .= $this->renderMapHelp($ssh_map); return array( $this->newOption('log.access.path', 'string', null) ->setLocked(true) ->setSummary(pht('Access log location.')) ->setDescription( pht( "To enable the Phabricator access log, specify a path. The ". "Phabricator access than normal HTTP access logs (for instance, ". "it can show logged-in users, controllers, and other application ". "data).\n\n". "If not set, no log will be written.")) ->addExample( null, pht('Disable access log.')) ->addExample( '/var/log/phabricator/access.log', pht('Write access log here.')), $this->newOption( 'log.access.format', // NOTE: This is 'wild' intead of 'string' so "\t" and such can be // specified. 'wild', "[%D]\t%p\t%h\t%r\t%u\t%C\t%m\t%U\t%R\t%c\t%T") ->setLocked(true) ->setSummary(pht('Access log format.')) ->setDescription($http_desc), $this->newOption('log.ssh.path', 'string', null) ->setLocked(true) ->setSummary(pht('SSH log location.')) ->setDescription( pht( "To enable the Phabricator SSH log, specify a path. The ". "access log can provide more detailed information about SSH ". "access than a normal SSH log (for instance, it can show ". "logged-in users, commands, and other application data).\n\n". "If not set, no log will be written.")) ->addExample( null, pht('Disable SSH log.')) ->addExample( '/var/log/phabricator/ssh.log', pht('Write SSH log here.')), $this->newOption( 'log.ssh.format', 'wild', "[%D]\t%p\t%h\t%r\t%s\t%S\t%u\t%C\t%U\t%c\t%T\t%i\t%o") ->setLocked(true) ->setSummary(pht('SSH log format.')) ->setDescription($ssh_desc), ); } private function renderMapHelp(array $map) { $desc = ''; foreach ($map as $key => $kdesc) { $desc .= " - `%".$key."` ".$kdesc."\n"; } $desc .= "\n"; $desc .= pht( "If a variable isn't available (for example, %%m appears in the file ". "format but the request is not a Conduit request), it will be rendered ". "as '-'"); $desc .= "\n\n"; $desc .= pht( "Note that the default format is subject to change in the future, so ". "if you rely on the log's format, specify it explicitly."); return $desc; } } diff --git a/src/applications/config/option/PhabricatorApplicationConfigOptions.php b/src/applications/config/option/PhabricatorApplicationConfigOptions.php index 491852eab..dab28af77 100644 --- a/src/applications/config/option/PhabricatorApplicationConfigOptions.php +++ b/src/applications/config/option/PhabricatorApplicationConfigOptions.php @@ -1,256 +1,256 @@ getDefault()) { return; } if ($value === null) { return; } if ($option->isCustomType()) { try { return $option->getCustomObject()->validateOption($option, $value); } catch (Exception $ex) { // If custom validators threw exceptions, convert them to configuation // validation exceptions so we repair the configuration and raise // an error. throw new PhabricatorConfigValidationException($ex->getMessage()); } } switch ($option->getType()) { case 'bool': if ($value !== true && $value !== false) { throw new PhabricatorConfigValidationException( pht( "Option '%s' is of type bool, but value is not true or false.", $option->getKey())); } break; case 'int': if (!is_int($value)) { throw new PhabricatorConfigValidationException( pht( "Option '%s' is of type int, but value is not an integer.", $option->getKey())); } break; case 'string': if (!is_string($value)) { throw new PhabricatorConfigValidationException( pht( "Option '%s' is of type string, but value is not a string.", $option->getKey())); } break; case 'class': $symbols = id(new PhutilSymbolLoader()) ->setType('class') ->setAncestorClass($option->getBaseClass()) ->setConcreteOnly(true) ->selectSymbolsWithoutLoading(); $names = ipull($symbols, 'name', 'name'); if (empty($names[$value])) { throw new PhabricatorConfigValidationException( pht( "Option '%s' value must name a class extending '%s'.", $option->getKey(), $option->getBaseClass())); } break; case 'set': $valid = true; if (!is_array($value)) { throw new PhabricatorConfigValidationException( pht( "Option '%s' must be a set, but value is not an array.", $option->getKey())); } foreach ($value as $v) { if ($v !== true) { throw new PhabricatorConfigValidationException( pht( "Option '%s' must be a set, but array contains values other ". "than 'true'.", $option->getKey())); } } break; case 'list': $valid = true; if (!is_array($value)) { throw new PhabricatorConfigValidationException( pht( "Option '%s' must be a list of regular expressions, but value ". "is not an array.", $option->getKey())); } if ($value && array_keys($value) != range(0, count($value) - 1)) { throw new PhabricatorConfigValidationException( pht( "Option '%s' must be a list of regular expressions, but the ". "value is a map with unnatural keys.", $option->getKey())); } foreach ($value as $v) { $ok = @preg_match($v, ''); if ($ok === false) { throw new PhabricatorConfigValidationException( pht( "Option '%s' must be a list of regular expressions, but the ". "value '%s' is not a valid regular expression.", $option->getKey(), $v)); } } break; case 'list': $valid = true; if (!is_array($value)) { throw new PhabricatorConfigValidationException( pht( "Option '%s' must be a list of strings, but value is not ". "an array.", $option->getKey())); } if ($value && array_keys($value) != range(0, count($value) - 1)) { throw new PhabricatorConfigValidationException( pht( "Option '%s' must be a list of strings, but the value is a ". "map with unnatural keys.", $option->getKey())); } foreach ($value as $v) { if (!is_string($v)) { throw new PhabricatorConfigValidationException( pht( "Option '%s' must be a list of strings, but it contains one ". "or more non-strings.", $option->getKey())); } } break; case 'wild': default: break; } $this->didValidateOption($option, $value); } protected function didValidateOption( PhabricatorConfigOption $option, $value) { // Hook for subclasses to do complex validation. return; } /** * Hook to render additional hints based on, e.g., the viewing user, request, * or other context. For example, this is used to show workspace IDs when * configuring `asana.workspace-id`. * * @param PhabricatorConfigOption Option being rendered. * @param AphrontRequest Active request. * @return wild Additional contextual description * information. */ public function renderContextualDescription( PhabricatorConfigOption $option, AphrontRequest $request) { return null; } public function getKey() { $class = get_class($this); $matches = null; if (preg_match('/^Phabricator(.*)ConfigOptions$/', $class, $matches)) { return strtolower($matches[1]); } return strtolower(get_class($this)); } final protected function newOption($key, $type, $default) { return id(new PhabricatorConfigOption()) ->setKey($key) ->setType($type) ->setDefault($default) ->setGroup($this); } final public static function loadAll($external_only = false) { $symbols = id(new PhutilSymbolLoader()) ->setAncestorClass(__CLASS__) ->setConcreteOnly(true) ->selectAndLoadSymbols(); $groups = array(); foreach ($symbols as $symbol) { if ($external_only && $symbol['library'] == 'phabricator') { continue; } $obj = newv($symbol['name'], array()); $key = $obj->getKey(); if (isset($groups[$key])) { $pclass = get_class($groups[$key]); $nclass = $symbol['name']; throw new Exception( pht( "Multiple %s subclasses have the same key ('%s'): %s, %s.", __CLASS__, $key, $pclass, $nclass)); } $groups[$key] = $obj; } return $groups; } final public static function loadAllOptions($external_only = false) { $groups = self::loadAll($external_only); $options = array(); foreach ($groups as $group) { foreach ($group->getOptions() as $option) { $key = $option->getKey(); if (isset($options[$key])) { throw new Exception( pht( "Mulitple %s subclasses contain an option named '%s'!", __CLASS__, $key)); } $options[$key] = $option; } } return $options; } /** * Deformat a HEREDOC for use in remarkup by converting line breaks to * spaces. */ final protected function deformat($string) { return preg_replace('/(?<=\S)\n(?=\S)/', ' ', $string); } } diff --git a/src/applications/config/option/PhabricatorAuthenticationConfigOptions.php b/src/applications/config/option/PhabricatorAuthenticationConfigOptions.php index 8092f52ff..bd1c8965e 100644 --- a/src/applications/config/option/PhabricatorAuthenticationConfigOptions.php +++ b/src/applications/config/option/PhabricatorAuthenticationConfigOptions.php @@ -1,101 +1,101 @@ newOption('auth.require-email-verification', 'bool', false) ->setBoolOptions( array( pht('Require email verification'), pht("Don't require email verification"), )) ->setSummary( pht('Require email verification before a user can log in.')) ->setDescription( pht( 'If true, email addresses must be verified (by clicking a link '. 'in an email) before a user can login. By default, verification '. 'is optional unless {{auth.email-domains}} is nonempty.')), $this->newOption('auth.require-approval', 'bool', true) ->setBoolOptions( array( pht('Require Administrators to Approve Accounts'), pht("Don't Require Manual Approval"), )) ->setSummary( pht('Require administrators to approve new accounts.')) ->setDescription( pht( "Newly registered Phabricator accounts can either be placed ". "into a manual approval queue for administrative review, or ". "automatically activated immediately. The approval queue is ". "enabled by default because it gives you greater control over ". "who can register an account and access Phabricator.\n\n". "If your install is completely public, or on a VPN, or users can ". "only register with a trusted provider like LDAP, or you've ". "otherwise configured Phabricator to prevent unauthorized ". "registration, you can disable the queue to reduce administrative ". "overhead.\n\n". "NOTE: Before you disable the queue, make sure ". "{{auth.email-domains}} is configured correctly ". "for your install!")), $this->newOption('auth.email-domains', 'list', array()) ->setSummary(pht('Only allow registration from particular domains.')) ->setDescription( pht( "You can restrict allowed email addresses to certain domains ". "(like `yourcompany.com`) by setting a list of allowed domains ". "here.\n\nUsers will only be allowed to register using email ". "addresses at one of the domains, and will only be able to add ". "new email addresses for these domains. If you configure this, ". "it implies {{auth.require-email-verification}}.\n\n". "You should omit the `@` from domains. Note that the domain must ". "match exactly. If you allow `yourcompany.com`, that permits ". "`joe@yourcompany.com` but rejects `joe@mail.yourcompany.com`.")) ->addExample( "yourcompany.com\nmail.yourcompany.com", pht('Valid Setting')), $this->newOption('account.editable', 'bool', true) ->setBoolOptions( array( pht('Allow editing'), pht('Prevent editing'), )) ->setSummary( pht( 'Determines whether or not basic account information is editable.')) ->setDescription( pht( 'Is basic account information (email, real name, profile '. 'picture) editable? If you set up Phabricator to automatically '. 'synchronize account information from some other authoritative '. 'system, you can disable this to ensure information remains '. 'consistent across both systems.')), $this->newOption('account.minimum-password-length', 'int', 8) ->setSummary(pht('Minimum password length.')) ->setDescription( pht( 'When users set or reset a password, it must have at least this '. 'many characters.')), ); } } diff --git a/src/applications/config/option/PhabricatorClusterConfigOptions.php b/src/applications/config/option/PhabricatorClusterConfigOptions.php index 8684d9823..0b7974904 100644 --- a/src/applications/config/option/PhabricatorClusterConfigOptions.php +++ b/src/applications/config/option/PhabricatorClusterConfigOptions.php @@ -1,79 +1,79 @@ newOption('cluster.addresses', 'list', array()) ->setLocked(true) ->setSummary(pht('Address ranges of cluster hosts.')) ->setDescription( pht( 'To allow Phabricator nodes to communicate with other nodes '. 'in the cluster, provide an address whitelist of hosts that '. 'are part of the cluster.'. "\n\n". 'Hosts on this whitelist are permitted to use special cluster '. 'mechanisms to authenticate requests. By default, these '. 'mechanisms are disabled.'. "\n\n". 'Define a list of CIDR blocks which whitelist all hosts in the '. 'cluster. See the examples below for details.', "\n\n". 'When cluster addresses are defined, Phabricator hosts will also '. 'reject requests to interfaces which are not whitelisted.')) ->addExample( array( '23.24.25.80/32', '23.24.25.81/32', ), pht('Whitelist Specific Addresses')) ->addExample( array( '1.2.3.0/24', ), pht('Whitelist 1.2.3.*')) ->addExample( array( '1.2.0.0/16', ), pht('Whitelist 1.2.*.*')) ->addExample( array( '0.0.0.0/0', ), pht('Allow Any Host (Insecure!)')), $this->newOption('cluster.instance', 'string', null) ->setLocked(true) ->setSummary(pht('Instance identifier for multi-tenant clusters.')) ->setDescription( pht( 'WARNING: This is a very advanced option, and only useful for '. 'hosting providers running multi-tenant clusters.'. "\n\n". 'If you provide an instance identifier here (normally by '. 'injecting it with a `%s`), Phabricator will pass it to '. 'subprocesses and commit hooks in the `%s` environmental variable.', 'PhabricatorConfigSiteSource', 'PHABRICATOR_INSTANCE')), ); } } diff --git a/src/applications/config/option/PhabricatorCoreConfigOptions.php b/src/applications/config/option/PhabricatorCoreConfigOptions.php index 7f696ae48..c486ce0db 100644 --- a/src/applications/config/option/PhabricatorCoreConfigOptions.php +++ b/src/applications/config/option/PhabricatorCoreConfigOptions.php @@ -1,324 +1,324 @@ newOption('phabricator.base-uri', 'string', null) ->setLocked(true) ->setSummary(pht('URI where Phabricator is installed.')) ->setDescription( pht( 'Set the URI where Phabricator is installed. Setting this '. 'improves security by preventing cookies from being set on other '. 'domains, and allows daemons to send emails with links that have '. 'the correct domain.')) ->addExample('http://phabricator.example.com/', pht('Valid Setting')), $this->newOption('phabricator.production-uri', 'string', null) ->setSummary( pht('Primary install URI, for multi-environment installs.')) ->setDescription( pht( 'If you have multiple Phabricator environments (like a '. 'development/staging environment for working on testing '. 'Phabricator, and a production environment for deploying it), '. 'set the production environment URI here so that emails and other '. 'durable URIs will always generate with links pointing at the '. 'production environment. If unset, defaults to `%s`. Most '. 'installs do not need to set this option.', 'phabricator.base-uri')) ->addExample('http://phabricator.example.com/', pht('Valid Setting')), $this->newOption('phabricator.allowed-uris', 'list', array()) ->setLocked(true) ->setSummary(pht('Alternative URIs that can access Phabricator.')) ->setDescription( pht( "These alternative URIs will be able to access 'normal' pages ". "on your Phabricator install. Other features such as OAuth ". "won't work. The major use case for this is moving installs ". "across domains.")) ->addExample( "http://phabricator2.example.com/\n". "http://phabricator3.example.com/", pht('Valid Setting')), $this->newOption('phabricator.timezone', 'string', null) ->setSummary( pht('The timezone Phabricator should use.')) ->setDescription( pht( "PHP requires that you set a timezone in your php.ini before ". "using date functions, or it will emit a warning. If this isn't ". "possible (for instance, because you are using HPHP) you can set ". "some valid constant for %s here and Phabricator will set it on ". "your behalf, silencing the warning.", 'date_default_timezone_set()')) ->addExample('America/New_York', pht('US East (EDT)')) ->addExample('America/Chicago', pht('US Central (CDT)')) ->addExample('America/Boise', pht('US Mountain (MDT)')) ->addExample('America/Los_Angeles', pht('US West (PDT)')), $this->newOption('phabricator.cookie-prefix', 'string', null) ->setLocked(true) ->setSummary( pht( 'Set a string Phabricator should use to prefix cookie names.')) ->setDescription( pht( 'Cookies set for x.com are also sent for y.x.com. Assuming '. 'Phabricator instances are running on both domains, this will '. 'create a collision preventing you from logging in.')) ->addExample('dev', pht('Prefix cookie with "%s"', 'dev')), $this->newOption('phabricator.show-prototypes', 'bool', false) ->setLocked(true) ->setBoolOptions( array( pht('Enable Prototypes'), pht('Disable Prototypes'), )) ->setSummary( pht( 'Install applications which are still under development.')) ->setDescription( pht( "IMPORTANT: The upstream does not provide support for prototype ". "applications.". "\n\n". "Phabricator includes prototype applications which are in an ". "**early stage of development**. By default, prototype ". "applications are not installed, because they are often not yet ". "developed enough to be generally usable. You can enable ". "this option to install them if you're developing Phabricator ". "or are interested in previewing upcoming features.". "\n\n". "To learn more about prototypes, see [[ %s | %s ]].". "\n\n". "After enabling prototypes, you can selectively uninstall them ". "(like normal applications).", $proto_doc_href, $proto_doc_name)), $this->newOption('phabricator.serious-business', 'bool', false) ->setBoolOptions( array( pht('Serious business'), pht('Shenanigans'), // That should be interesting to translate. :P )) ->setSummary( pht('Allows you to remove levity and jokes from the UI.')) ->setDescription( pht( 'By default, Phabricator includes some flavor text in the UI, '. 'like a prompt to "Weigh In" rather than "Add Comment" in '. 'Maniphest. If you\'d prefer more traditional UI strings like '. '"Add Comment", you can set this flag to disable most of the '. 'extra flavor.')), $this->newOption('remarkup.ignored-object-names', 'string', '/^(Q|V)\d$/') ->setSummary( pht('Text values that match this regex and are also object names '. 'will not be linked.')) ->setDescription( pht( 'By default, Phabricator links object names in Remarkup fields '. 'to the corresponding object. This regex can be used to modify '. 'this behavior; object names that match this regex will not be '. 'linked.')), $this->newOption('environment.append-paths', 'list', $paths) ->setSummary( pht( 'These paths get appended to your %s environment variable.', '$PATH')) ->setDescription( pht( "Phabricator occasionally shells out to other binaries on the ". "server. An example of this is the `%s` command, used to ". "syntax-highlight code written in languages other than PHP. By ". "default, it is assumed that these binaries are in the %s of the ". "user running Phabricator (normally 'apache', 'httpd', or ". "'nobody'). Here you can add extra directories to the %s ". "environment variable, for when these binaries are in ". "non-standard locations.\n\n". "Note that you can also put binaries in `%s` (for example, by ". "symlinking them).\n\n". "The current value of PATH after configuration is applied is:\n\n". " lang=text\n". " %s", 'pygmentize', '$PATH', '$PATH', 'phabricator/support/bin/', $path)) ->setLocked(true) ->addExample('/usr/local/bin', pht('Add One Path')) ->addExample("/usr/bin\n/usr/local/bin", pht('Add Multiple Paths')), $this->newOption('config.lock', 'set', array()) ->setLocked(true) ->setDescription(pht('Additional configuration options to lock.')), $this->newOption('config.hide', 'set', array()) ->setLocked(true) ->setDescription(pht('Additional configuration options to hide.')), $this->newOption('config.ignore-issues', 'set', array()) ->setLocked(true) ->setDescription(pht('Setup issues to ignore.')), $this->newOption('phabricator.env', 'string', null) ->setLocked(true) ->setDescription(pht('Internal.')), $this->newOption('test.value', 'wild', null) ->setLocked(true) ->setDescription(pht('Unit test value.')), $this->newOption('phabricator.uninstalled-applications', 'set', array()) ->setLocked(true) ->setLockedMessage(pht( 'Use the %s to manage installed applications.', phutil_tag( 'a', array( 'href' => $applications_app_href, ), pht('Applications application')))) ->setDescription( pht('Array containing list of uninstalled applications.')), $this->newOption('phabricator.application-settings', 'wild', array()) ->setLocked(true) ->setDescription( pht('Customized settings for Phabricator applications.')), $this->newOption('welcome.html', 'string', null) ->setLocked(true) ->setDescription( pht('Custom HTML to show on the main Phabricator dashboard.')), $this->newOption('phabricator.cache-namespace', 'string', 'phabricator') ->setLocked(true) ->setDescription(pht('Cache namespace.')), $this->newOption('phabricator.allow-email-users', 'bool', false) ->setBoolOptions( array( pht('Allow'), pht('Disallow'), )) ->setDescription( pht('Allow non-members to interact with tasks over email.')), $this->newOption('phabricator.silent', 'bool', false) ->setLocked(true) ->setBoolOptions( array( pht('Run Silently'), pht('Run Normally'), )) ->setSummary(pht('Stop Phabricator from sending any email, etc.')) ->setDescription( pht( 'This option allows you to stop Phabricator from sending '. 'any data to external services. Among other things, it will '. 'disable email, SMS, repository mirroring, and HTTP hooks.'. "\n\n". 'This option is intended to allow a Phabricator instance to '. 'be exported, copied, imported, and run in a test environment '. 'without impacting users. For example, if you are migrating '. 'to new hardware, you could perform a test migration first, '. 'make sure things work, and then do a production cutover '. 'later with higher confidence and less disruption. Without '. 'this flag, users would receive duplicate email during the '. 'time the test instance and old production instance were '. 'both in operation.')), ); } protected function didValidateOption( PhabricatorConfigOption $option, $value) { $key = $option->getKey(); if ($key == 'phabricator.base-uri' || $key == 'phabricator.production-uri') { $uri = new PhutilURI($value); $protocol = $uri->getProtocol(); if ($protocol !== 'http' && $protocol !== 'https') { throw new PhabricatorConfigValidationException( pht( "Config option '%s' is invalid. The URI must start with ". "%s' or '%s'.", 'http://', 'https://', $key)); } $domain = $uri->getDomain(); if (strpos($domain, '.') === false) { throw new PhabricatorConfigValidationException( pht( "Config option '%s' is invalid. The URI must contain a dot ". "('%s'), like '%s', not just a bare name like '%s'. Some web ". "browsers will not set cookies on domains with no TLD.", '.', 'http://example.com/', 'http://example/', $key)); } $path = $uri->getPath(); if ($path !== '' && $path !== '/') { throw new PhabricatorConfigValidationException( pht( "Config option '%s' is invalid. The URI must NOT have a path, ". "e.g. '%s' is OK, but '%s' is not. Phabricator must be installed ". "on an entire domain; it can not be installed on a path.", $key, 'http://phabricator.example.com/', 'http://example.com/phabricator/')); } } if ($key === 'phabricator.timezone') { $old = date_default_timezone_get(); $ok = @date_default_timezone_set($value); @date_default_timezone_set($old); if (!$ok) { throw new PhabricatorConfigValidationException( pht( "Config option '%s' is invalid. The timezone identifier must ". "be a valid timezone identifier recognized by PHP, like '%s'. "." You can find a list of valid identifiers here: %s", $key, 'America/Los_Angeles', 'http://php.net/manual/timezones.php')); } } } } diff --git a/src/applications/config/option/PhabricatorDeveloperConfigOptions.php b/src/applications/config/option/PhabricatorDeveloperConfigOptions.php index 37cf752f6..a84a7484a 100644 --- a/src/applications/config/option/PhabricatorDeveloperConfigOptions.php +++ b/src/applications/config/option/PhabricatorDeveloperConfigOptions.php @@ -1,183 +1,183 @@ newOption('darkconsole.enabled', 'bool', false) ->setBoolOptions( array( pht('Enable DarkConsole'), pht('Disable DarkConsole'), )) ->setSummary(pht("Enable Phabricator's debugging console.")) ->setDescription( pht( "DarkConsole is a development and profiling tool built into ". "Phabricator's web interface. You should leave it disabled unless ". "you are developing or debugging Phabricator.\n\n". "Once you activate DarkConsole for the install, **you need to ". "enable it for your account before it will actually appear on ". "pages.** You can do this in Settings > Developer Settings.\n\n". "DarkConsole exposes potentially sensitive data (like queries, ". "stack traces, and configuration) so you generally should not ". "turn it on in production.")), $this->newOption('darkconsole.always-on', 'bool', false) ->setBoolOptions( array( pht('Always Activate DarkConsole'), pht('Require DarkConsole Activation'), )) ->setSummary(pht('Activate DarkConsole on every page.')) ->setDescription( pht( "This option allows you to enable DarkConsole on every page, ". "even for logged-out users. This is only really useful if you ". "need to debug something on a logged-out page. You should not ". "enable this option in production.\n\n". "You must enable DarkConsole by setting '%s' ". "before this option will have any effect.", 'darkconsole.enabled')), $this->newOption('debug.time-limit', 'int', null) ->setSummary( pht( 'Limit page execution time to debug hangs.')) ->setDescription( pht( "This option can help debug pages which are taking a very ". "long time (more than 30 seconds) to render.\n\n". "If a page is slow to render (but taking less than 30 seconds), ". "the best tools to use to figure out why it is slow are usually ". "the DarkConsole service call profiler and XHProf.\n\n". "However, if a request takes a very long time to return, some ". "components (like Apache, nginx, or PHP itself) may abort the ". "request before it finishes. This can prevent you from using ". "profiling tools to understand page performance in detail.\n\n". "In these cases, you can use this option to force the page to ". "abort after a smaller number of seconds (for example, 10), and ". "dump a useful stack trace. This can provide useful information ". "about why a page is hanging.\n\n". "To use this option, set it to a small number (like 10), and ". "reload a hanging page. The page should exit after 10 seconds ". "and give you a stack trace.\n\n". "You should turn this option off (set it to 0) when you are ". "done with it. Leaving it on creates a small amount of overhead ". "for all requests, even if they do not hit the time limit.")), $this->newOption('debug.stop-on-redirect', 'bool', false) ->setBoolOptions( array( pht('Stop Before HTTP Redirect'), pht('Use Normal HTTP Redirects'), )) ->setSummary( pht( 'Confirm before redirecting so DarkConsole can be examined.')) ->setDescription( pht( 'Normally, Phabricator issues HTTP redirects after a successful '. 'POST. This can make it difficult to debug things which happen '. 'while processing the POST, because service and profiling '. 'information are lost. By setting this configuration option, '. 'Phabricator will show a page instead of automatically '. 'redirecting, allowing you to examine service and profiling '. 'information. It also makes the UX awful, so you should only '. 'enable it when debugging.')), $this->newOption('debug.profile-rate', 'int', 0) ->addExample(0, pht('No profiling')) ->addExample(1, pht('Profile every request (slow)')) ->addExample(1000, pht('Profile 0.1%% of all requests')) ->setSummary(pht('Automatically profile some percentage of pages.')) ->setDescription( pht( "Normally, Phabricator profiles pages only when explicitly ". "requested via DarkConsole. However, it may be useful to profile ". "some pages automatically.\n\n". "Set this option to a positive integer N to profile 1 / N pages ". "automatically. For example, setting it to 1 will profile every ". "page, while setting it to 1000 will profile 1 page per 1000 ". "requests (i.e., 0.1%% of requests).\n\n". "Since profiling is slow and generates a lot of data, you should ". "set this to 0 in production (to disable it) or to a large number ". "(to collect a few samples, if you're interested in having some ". "data to look at eventually). In development, it may be useful to ". "set it to 1 in order to debug performance problems.\n\n". "NOTE: You must install XHProf for profiling to work.")), $this->newOption('debug.sample-rate', 'int', 1000) ->setLocked(true) ->addExample(0, pht('No performance sampling.')) ->addExample(1, pht('Sample every request (slow).')) ->addExample(1000, pht('Sample 0.1%% of requests.')) ->setSummary(pht('Automatically sample some fraction of requests.')) ->setDescription( pht( "The Multimeter application collects performance samples. You ". "can use this data to help you understand what Phabricator is ". "spending time and resources doing, and to identify problematic ". "access patterns.". "\n\n". "This option controls how frequently sampling activates. Set it ". "to some positive integer N to sample every 1 / N pages.". "\n\n". "For most installs, the default value (1 sample per 1000 pages) ". "should collect enough data to be useful without requiring much ". "storage or meaningfully impacting performance. If you're ". "investigating performance issues, you can adjust the rate ". "in order to collect more data.")), $this->newOption('phabricator.developer-mode', 'bool', false) ->setBoolOptions( array( pht('Enable developer mode'), pht('Disable developer mode'), )) ->setSummary(pht('Enable verbose error reporting and disk reads.')) ->setDescription( pht( 'This option enables verbose error reporting (stack traces, '. 'error callouts) and forces disk reads of static assets on '. 'every reload.')), $this->newOption('celerity.minify', 'bool', true) ->setBoolOptions( array( pht('Minify static resources.'), pht("Don't minify static resources."), )) ->setSummary(pht('Minify static Celerity resources.')) ->setDescription( pht( 'Minify static resources by removing whitespace and comments. You '. 'should enable this in production, but disable it in '. 'development.')), $this->newOption('cache.enable-deflate', 'bool', true) ->setBoolOptions( array( pht('Enable deflate compression'), pht('Disable deflate compression'), )) ->setSummary( pht('Toggle %s-based compression for some caches.', 'gzdeflate()')) ->setDescription( pht( 'Set this to false to disable the use of %s-based '. 'compression in some caches. This may give you less performant '. '(but more debuggable) caching.', 'gzdeflate()')), ); } } diff --git a/src/applications/config/option/PhabricatorExtendingPhabricatorConfigOptions.php b/src/applications/config/option/PhabricatorExtendingPhabricatorConfigOptions.php index ef07bc910..28a0f619d 100644 --- a/src/applications/config/option/PhabricatorExtendingPhabricatorConfigOptions.php +++ b/src/applications/config/option/PhabricatorExtendingPhabricatorConfigOptions.php @@ -1,50 +1,50 @@ newOption('load-libraries', 'list', array()) ->setLocked(true) ->setSummary(pht('Paths to additional phutil libraries to load.')) ->addExample('/srv/our-libs/sekrit-phutil', pht('Valid Setting')), $this->newOption('events.listeners', 'list', array()) ->setLocked(true) ->setSummary( pht('Listeners receive callbacks when interesting things occur.')) ->setDescription( pht( 'You can respond to various application events by installing '. 'listeners, which will receive callbacks when interesting things '. 'occur. Specify a list of classes which extend '. 'PhabricatorEventListener here.')) ->addExample('MyEventListener', pht('Valid Setting')), $this->newOption( 'aphront.default-application-configuration-class', 'class', 'AphrontDefaultApplicationConfiguration') ->setLocked(true) ->setBaseClass('AphrontApplicationConfiguration') // TODO: This could probably use some better documentation. ->setDescription(pht('Application configuration class.')), ); } } diff --git a/src/applications/config/option/PhabricatorMailgunConfigOptions.php b/src/applications/config/option/PhabricatorMailgunConfigOptions.php index aebcba672..cc4e71fb5 100644 --- a/src/applications/config/option/PhabricatorMailgunConfigOptions.php +++ b/src/applications/config/option/PhabricatorMailgunConfigOptions.php @@ -1,38 +1,38 @@ newOption('mailgun.domain', 'string', null) ->setLocked(true) ->setDescription( pht( 'Mailgun domain name. See %s.', 'https://mailgun.com/cp/domains')) ->addExample('mycompany.com', pht('Use specific domain')), $this->newOption('mailgun.api-key', 'string', null) ->setHidden(true) ->setDescription(pht('Mailgun API key.')), ); } } diff --git a/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php b/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php index 929f02be2..16bdb7e52 100644 --- a/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php +++ b/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php @@ -1,349 +1,349 @@ deformat(pht(<<deformat(pht(<<deformat(pht(<<deformat(pht(<<deformat(pht(<<deformat(pht(<<deformat(pht(<<deformat(pht(<<deformat(pht(<<deformat(pht(<<deformat(pht(<<deformat(pht(<<deformat(pht(<<deformat(pht(<<deformat(pht(<<' - `real`: 'George Washington ' - `full`: 'gwashington (George Washington) ' The default is `full`. EODOC )); return array( $this->newOption( 'metamta.default-address', 'string', 'noreply@phabricator.example.com') ->setDescription(pht('Default "From" address.')), $this->newOption( 'metamta.domain', 'string', 'phabricator.example.com') ->setDescription(pht('Domain used to generate Message-IDs.')), $this->newOption( 'metamta.mail-adapter', 'class', 'PhabricatorMailImplementationPHPMailerLiteAdapter') ->setBaseClass('PhabricatorMailImplementationAdapter') ->setSummary(pht('Control how mail is sent.')) ->setDescription($adapter_description), $this->newOption( 'metamta.one-mail-per-recipient', 'bool', true) ->setLocked(true) ->setBoolOptions( array( pht('Send Mail To Each Recipient'), pht('Send Mail To All Recipients'), )) ->setSummary( pht( 'Controls whether Phabricator sends one email with multiple '. 'recipients in the "To:" line, or multiple emails, each with a '. 'single recipient in the "To:" line.')) ->setDescription($one_mail_per_recipient_desc), $this->newOption('metamta.can-send-as-user', 'bool', false) ->setBoolOptions( array( pht('Send as User Taking Action'), pht('Send as Phabricator'), )) ->setSummary( pht( 'Controls whether Phabricator sends email "From" users.')) ->setDescription($send_as_user_desc), $this->newOption( 'metamta.reply-handler-domain', 'string', null) ->setLocked(true) ->setDescription(pht('Domain used for reply email addresses.')) ->addExample('phabricator.example.com', ''), $this->newOption('metamta.herald.show-hints', 'bool', true) ->setBoolOptions( array( pht('Show Herald Hints'), pht('No Herald Hints'), )) ->setSummary(pht('Show hints about Herald rules in email.')) ->setDescription($herald_hints_description), $this->newOption('metamta.recipients.show-hints', 'bool', true) ->setBoolOptions( array( pht('Show Recipient Hints'), pht('No Recipient Hints'), )) ->setSummary(pht('Show "To:" and "Cc:" footer hints in email.')) ->setDescription($recipient_hints_description), $this->newOption('metamta.email-preferences', 'bool', true) ->setBoolOptions( array( pht('Show Email Preferences Link'), pht('No Email Preferences Link'), )) ->setSummary(pht('Show email preferences link in email.')) ->setDescription($email_preferences_description), $this->newOption('metamta.re-prefix', 'bool', false) ->setBoolOptions( array( pht('Force "Re:" Subject Prefix'), pht('No "Re:" Subject Prefix'), )) ->setSummary(pht('Control "Re:" subject prefix, for Mail.app.')) ->setDescription($re_prefix_description), $this->newOption('metamta.vary-subjects', 'bool', true) ->setBoolOptions( array( pht('Allow Varied Subjects'), pht('Always Use the Same Thread Subject'), )) ->setSummary(pht('Control subject variance, for some mail clients.')) ->setDescription($vary_subjects_description), $this->newOption('metamta.insecure-auth-with-reply-to', 'bool', false) ->setBoolOptions( array( pht('Allow Insecure Reply-To Auth'), pht('Disallow Reply-To Auth'), )) ->setSummary(pht('Trust "Reply-To" headers for authentication.')) ->setDescription($reply_to_description), $this->newOption('metamta.placeholder-to-recipient', 'string', null) ->setSummary(pht('Placeholder for mail with only CCs.')) ->setDescription($placeholder_description), $this->newOption('metamta.public-replies', 'bool', false) ->setBoolOptions( array( pht('Use Public Replies (Less Secure)'), pht('Use Private Replies (More Secure)'), )) ->setSummary( pht( 'Phabricator can use less-secure but mailing list friendly public '. 'reply addresses.')) ->setDescription($public_replies_description), $this->newOption('metamta.single-reply-handler-prefix', 'string', null) ->setSummary( pht('Allow Phabricator to use a single mailbox for all replies.')) ->setDescription($single_description), $this->newOption('metamta.user-address-format', 'enum', 'full') ->setEnumOptions( array( 'short' => 'short', 'real' => 'real', 'full' => 'full', )) ->setSummary(pht('Control how Phabricator renders user names in mail.')) ->setDescription($address_description) ->addExample('gwashington ', 'short') ->addExample('George Washington ', 'real') ->addExample( 'gwashington (George Washington) ', 'full'), $this->newOption('metamta.email-body-limit', 'int', 524288) ->setDescription( pht( 'You can set a limit for the maximum byte size of outbound mail. '. 'Mail which is larger than this limit will be truncated before '. 'being sent. This can be useful if your MTA rejects mail which '. 'exceeds some limit (this is reasonably common). Specify a value '. 'in bytes.')) ->setSummary(pht('Global cap for size of generated emails (bytes).')) ->addExample(524288, pht('Truncate at 512KB')) ->addExample(1048576, pht('Truncate at 1MB')), ); } } diff --git a/src/applications/config/option/PhabricatorMySQLConfigOptions.php b/src/applications/config/option/PhabricatorMySQLConfigOptions.php index e3cd480e2..28fa78d2a 100644 --- a/src/applications/config/option/PhabricatorMySQLConfigOptions.php +++ b/src/applications/config/option/PhabricatorMySQLConfigOptions.php @@ -1,87 +1,87 @@ newOption('mysql.host', 'string', 'localhost') ->setLocked(true) ->setDescription( pht('MySQL database hostname.')) ->addExample('localhost', pht('MySQL on this machine')) ->addExample('db.example.com:3300', pht('Nonstandard port')), $this->newOption('mysql.user', 'string', 'root') ->setLocked(true) ->setDescription( pht('MySQL username to use when connecting to the database.')), $this->newOption('mysql.pass', 'string', null) ->setHidden(true) ->setDescription( pht('MySQL password to use when connecting to the database.')), $this->newOption( 'mysql.configuration-provider', 'class', 'DefaultDatabaseConfigurationProvider') ->setLocked(true) ->setBaseClass('DatabaseConfigurationProvider') ->setSummary( pht('Configure database configuration class.')) ->setDescription( pht( 'Phabricator chooses which database to connect to through a '. 'swappable configuration provider. You almost certainly do not '. 'need to change this.')), $this->newOption( 'mysql.implementation', 'class', (extension_loaded('mysqli') ? 'AphrontMySQLiDatabaseConnection' : 'AphrontMySQLDatabaseConnection')) ->setLocked(true) ->setBaseClass('AphrontMySQLDatabaseConnectionBase') ->setSummary( pht('Configure database connection class.')) ->setDescription( pht( 'Phabricator connects to MySQL through a swappable abstraction '. 'layer. You can choose an alternate implementation by setting '. 'this option. To provide your own implementation, extend '. '`%s`. It is very unlikely that you need to change this.', 'AphrontMySQLDatabaseConnectionBase')), $this->newOption('storage.default-namespace', 'string', 'phabricator') ->setLocked(true) ->setSummary( pht('The namespace that Phabricator databases should use.')) ->setDescription( pht( "Phabricator puts databases in a namespace, which defaults to ". "'phabricator' -- for instance, the Differential database is ". "named 'phabricator_differential' by default. You can change ". "this namespace if you want. Normally, you should not do this ". "unless you are developing Phabricator and using namespaces to ". "separate multiple sandbox datasets.")), $this->newOption('mysql.port', 'string', null) ->setLocked(true) ->setDescription( pht('MySQL port to use when connecting to the database.')), ); } } diff --git a/src/applications/config/option/PhabricatorNotificationConfigOptions.php b/src/applications/config/option/PhabricatorNotificationConfigOptions.php index f43f2af32..7b6acf4ec 100644 --- a/src/applications/config/option/PhabricatorNotificationConfigOptions.php +++ b/src/applications/config/option/PhabricatorNotificationConfigOptions.php @@ -1,65 +1,65 @@ newOption('notification.enabled', 'bool', false) ->setBoolOptions( array( pht('Enable Real-Time Notifications'), pht('Disable Real-Time Notifications'), )) ->setSummary(pht('Enable real-time notifications.')) ->setDescription( pht( "Enable real-time notifications. You must also run a Node.js ". "based notification server for this to work. Consult the ". "documentation in 'Notifications User Guide: Setup and ". "Configuration' for instructions.")), $this->newOption( 'notification.client-uri', 'string', 'http://localhost:22280/') ->setDescription(pht('Location of the client server.')), $this->newOption( 'notification.server-uri', 'string', 'http://localhost:22281/') ->setDescription(pht('Location of the notification receiver server.')), $this->newOption('notification.log', 'string', '/var/log/aphlict.log') ->setDescription(pht('Location of the server log file.')), $this->newOption('notification.ssl-key', 'string', null) ->setLocked(true) ->setDescription( pht('Path to SSL key to use for secure WebSockets.')), $this->newOption('notification.ssl-cert', 'string', null) ->setLocked(true) ->setDescription( pht('Path to SSL certificate to use for secure WebSockets.')), $this->newOption( 'notification.pidfile', 'string', '/var/tmp/aphlict/pid/aphlict.pid') ->setDescription(pht('Location of the server PID file.')), ); } } diff --git a/src/applications/config/option/PhabricatorPHDConfigOptions.php b/src/applications/config/option/PhabricatorPHDConfigOptions.php index 6fecd7117..bfe7c148c 100644 --- a/src/applications/config/option/PhabricatorPHDConfigOptions.php +++ b/src/applications/config/option/PhabricatorPHDConfigOptions.php @@ -1,95 +1,95 @@ newOption('phd.pid-directory', 'string', '/var/tmp/phd/pid') ->setLocked(true) ->setDescription( pht('Directory that phd should use to track running daemons.')), $this->newOption('phd.log-directory', 'string', '/var/tmp/phd/log') ->setLocked(true) ->setDescription( pht('Directory that the daemons should use to store log files.')), $this->newOption('phd.taskmasters', 'int', 4) ->setLocked(true) ->setSummary(pht('Maximum taskmaster daemon pool size.')) ->setDescription( pht( 'Maximum number of taskmaster daemons to run at once. Raising '. 'this can increase the maximum throughput of the task queue. The '. 'pool will automatically scale down when unutilized.')), $this->newOption('phd.verbose', 'bool', false) ->setLocked(true) ->setBoolOptions( array( pht('Verbose mode'), pht('Normal mode'), )) ->setSummary(pht("Launch daemons in 'verbose' mode by default.")) ->setDescription( pht( "Launch daemons in 'verbose' mode by default. This creates a lot ". "of output, but can help debug issues. Daemons launched in debug ". "mode with '%s' are always launched in verbose mode. ". "See also '%s'.", 'phd debug', 'phd.trace')), $this->newOption('phd.user', 'string', null) ->setLocked(true) ->setSummary(pht('System user to run daemons as.')) ->setDescription( pht( 'Specify a system user to run the daemons as. Primarily, this '. 'user will own the working copies of any repositories that '. 'Phabricator imports or manages. This option is new and '. 'experimental.')), $this->newOption('phd.trace', 'bool', false) ->setLocked(true) ->setBoolOptions( array( pht('Trace mode'), pht('Normal mode'), )) ->setSummary(pht("Launch daemons in 'trace' mode by default.")) ->setDescription( pht( "Launch daemons in 'trace' mode by default. This creates an ". "ENORMOUS amount of output, but can help debug issues. Daemons ". "launched in debug mode with '%s' are always launched in ". "trace mode. See also '%s'.", 'phd debug', 'phd.verbose')), $this->newOption('phd.garbage-collection', 'wild', array()) ->setLocked(true) ->setLockedMessage( pht( 'This option can not be edited from the web UI. Use %s to adjust '. 'garbage collector policies.', phutil_tag('tt', array(), 'bin/garbage set-policy'))) ->setSummary(pht('Retention policies for garbage collection.')) ->setDescription( pht( 'Customizes retention policies for garbage collectors.')), ); } } diff --git a/src/applications/config/option/PhabricatorPHPMailerConfigOptions.php b/src/applications/config/option/PhabricatorPHPMailerConfigOptions.php index b2d5c91e9..6b999b0d5 100644 --- a/src/applications/config/option/PhabricatorPHPMailerConfigOptions.php +++ b/src/applications/config/option/PhabricatorPHPMailerConfigOptions.php @@ -1,78 +1,78 @@ newOption('phpmailer.mailer', 'string', 'smtp') ->setLocked(true) ->setSummary(pht('Configure mailer used by PHPMailer.')) ->setDescription( pht( "If you're using PHPMailer to send email, provide the mailer and ". "options here. PHPMailer is much more enormous than ". "PHPMailerLite, and provides more mailers and greater enormity. ". "You need it when you want to use SMTP instead of sendmail as the ". "mailer.")), $this->newOption('phpmailer.smtp-host', 'string', null) ->setLocked(true) ->setDescription(pht('Host for SMTP.')), $this->newOption('phpmailer.smtp-port', 'int', 25) ->setLocked(true) ->setDescription(pht('Port for SMTP.')), // TODO: Implement "enum"? Valid values are empty, 'tls', or 'ssl'. $this->newOption('phpmailer.smtp-protocol', 'string', null) ->setLocked(true) ->setSummary(pht('Configure TLS or SSL for SMTP.')) ->setDescription( pht( "Using PHPMailer with SMTP, you can set this to one of '%s' or ". "'%s' to use TLS or SSL, respectively. Leave it blank for ". "vanilla SMTP. If you're sending via Gmail, set it to '%s'.", 'tls', 'ssl', 'ssl')), $this->newOption('phpmailer.smtp-user', 'string', null) ->setLocked(true) ->setDescription(pht('Username for SMTP.')), $this->newOption('phpmailer.smtp-password', 'string', null) ->setHidden(true) ->setDescription(pht('Password for SMTP.')), $this->newOption('phpmailer.smtp-encoding', 'string', '8bit') ->setSummary(pht('Configure how mail is encoded.')) ->setDescription( pht( "Mail is normally encoded in `8bit`, which works correctly with ". "most MTAs. However, some MTAs do not work well with this ". "encoding. If you're having trouble with mail being mangled or ". "arriving with too many or too few newlines, you may try ". "adjusting this setting.\n\n". "Supported values are `8bit` (default), `quoted-printable`, ". "`7bit`, `binary` and `base64`.\n\n". "The settings in the table below may work well.\n\n". "| MTA | Setting | Notes\n". "|-----|---------|------\n". "| SendGrid via SMTP | `quoted-printable` | Double newlines under ". "`8bit`.\n". "| All Other MTAs | `8bit` | Default setting.")), ); } } diff --git a/src/applications/config/option/PhabricatorPhurlConfigOptions.php b/src/applications/config/option/PhabricatorPhurlConfigOptions.php index 726540d3d..69e3dd676 100644 --- a/src/applications/config/option/PhabricatorPhurlConfigOptions.php +++ b/src/applications/config/option/PhabricatorPhurlConfigOptions.php @@ -1,35 +1,35 @@ newOption('phurl.short-uri', 'string', null) ->setLocked(true) ->setSummary(pht('URI that Phurl will use to shorten URLs.')) ->setDescription( pht( 'Set the URI that Phurl will use to share shortened URLs.')) ->addExample( 'https://some-very-short-domain.museum/', pht('Valid Setting')), ); } } diff --git a/src/applications/config/option/PhabricatorRecaptchaConfigOptions.php b/src/applications/config/option/PhabricatorRecaptchaConfigOptions.php index 576ac1891..356bcfd4d 100644 --- a/src/applications/config/option/PhabricatorRecaptchaConfigOptions.php +++ b/src/applications/config/option/PhabricatorRecaptchaConfigOptions.php @@ -1,47 +1,47 @@ newOption('recaptcha.enabled', 'bool', false) ->setBoolOptions( array( pht('Enable Recaptcha'), pht('Disable Recaptcha'), )) ->setSummary(pht('Enable captchas with Recaptcha.')) ->setDescription( pht( 'Enable recaptcha to require users solve captchas after a few '. 'failed login attempts. This hinders brute-force attacks against '. 'user passwords. For more information, see http://recaptcha.net/')), $this->newOption('recaptcha.public-key', 'string', null) ->setDescription( pht('Recaptcha public key, obtained by signing up for Recaptcha.')), $this->newOption('recaptcha.private-key', 'string', null) ->setHidden(true) ->setDescription( pht('Recaptcha private key, obtained by signing up for Recaptcha.')), ); } } diff --git a/src/applications/config/option/PhabricatorSMSConfigOptions.php b/src/applications/config/option/PhabricatorSMSConfigOptions.php index 33b75c263..08f2e50ee 100644 --- a/src/applications/config/option/PhabricatorSMSConfigOptions.php +++ b/src/applications/config/option/PhabricatorSMSConfigOptions.php @@ -1,60 +1,60 @@ newOption( 'sms.default-sender', 'string', null) ->setDescription(pht('Default "from" number.')) ->addExample('8675309', 'Jenny still has this number') ->addExample('18005555555', 'Maybe not a real number'), $this->newOption( 'sms.default-adapter', 'class', null) ->setBaseClass('PhabricatorSMSImplementationAdapter') ->setSummary(pht('Control how SMS is sent.')) ->setDescription($adapter_description), $this->newOption( 'twilio.account-sid', 'string', null) ->setDescription(pht('Account ID on Twilio service.')) ->setLocked(true) ->addExample('gf5kzccfn2sfknpnadvz7kokv6nz5v', pht('30 characters')), $this->newOption( 'twilio.auth-token', 'string', null) ->setDescription(pht('Authorization token from Twilio service.')) ->setHidden(true) ->addExample('f3jsi4i67wiwt6w54hf2zwvy3fjf5h', pht('30 characters')), ); } } diff --git a/src/applications/config/option/PhabricatorSecurityConfigOptions.php b/src/applications/config/option/PhabricatorSecurityConfigOptions.php index 8baea2e66..8c9907d73 100644 --- a/src/applications/config/option/PhabricatorSecurityConfigOptions.php +++ b/src/applications/config/option/PhabricatorSecurityConfigOptions.php @@ -1,325 +1,325 @@ newOption('security.alternate-file-domain', 'string', null) ->setLocked(true) ->setSummary(pht('Alternate domain to serve files from.')) ->setDescription( pht( 'By default, Phabricator serves files from the same domain '. 'the application is served from. This is convenient, but '. 'presents a security risk.'. "\n\n". 'You should configure a CDN or alternate file domain to mitigate '. 'this risk. Configuring a CDN will also improve performance. See '. '[[ %s | %s ]] for instructions.', $doc_href, $doc_name)) ->addExample('https://files.phabcdn.net/', pht('Valid Setting')), $this->newOption( 'security.hmac-key', 'string', '[D\t~Y7eNmnQGJ;rnH6aF;m2!vJ8@v8C=Cs:aQS\.Qw') ->setHidden(true) ->setSummary( pht('Key for HMAC digests.')) ->setDescription( pht( 'Default key for HMAC digests where the key is not important '. '(i.e., the hash itself is secret). You can change this if you '. 'want (to any other string), but doing so will break existing '. 'sessions and CSRF tokens.')), $this->newOption('security.require-https', 'bool', false) ->setLocked(true) ->setSummary( pht('Force users to connect via HTTPS instead of HTTP.')) ->setDescription( pht( "If the web server responds to both HTTP and HTTPS requests but ". "you want users to connect with only HTTPS, you can set this ". "to true to make Phabricator redirect HTTP requests to HTTPS.\n\n". "Normally, you should just configure your server not to accept ". "HTTP traffic, but this setting may be useful if you originally ". "used HTTP and have now switched to HTTPS but don't want to ". "break old links, or if your webserver sits behind a load ". "balancer which terminates HTTPS connections and you can not ". "reasonably configure more granular behavior there.\n\n". "IMPORTANT: Phabricator determines if a request is HTTPS or not ". "by examining the PHP `%s` variable. If you run ". "Apache/mod_php this will probably be set correctly for you ". "automatically, but if you run Phabricator as CGI/FCGI (e.g., ". "through nginx or lighttpd), you need to configure your web ". "server so that it passes the value correctly based on the ". "connection type.", "\$_SERVER['HTTPS']")) ->setBoolOptions( array( pht('Force HTTPS'), pht('Allow HTTP'), )), $this->newOption('security.require-multi-factor-auth', 'bool', false) ->setLocked(true) ->setSummary( pht('Require all users to configure multi-factor authentication.')) ->setDescription( pht( 'By default, Phabricator allows users to add multi-factor '. 'authentication to their accounts, but does not require it. '. 'By enabling this option, you can force all users to add '. 'at least one authentication factor before they can use their '. 'accounts.')) ->setBoolOptions( array( pht('Multi-Factor Required'), pht('Multi-Factor Optional'), )), $this->newOption( 'phabricator.csrf-key', 'string', '0b7ec0592e0a2829d8b71df2fa269b2c6172eca3') ->setHidden(true) ->setSummary( pht('Hashed with other inputs to generate CSRF tokens.')) ->setDescription( pht( 'This is hashed with other inputs to generate CSRF tokens. If '. 'you want, you can change it to some other string which is '. 'unique to your install. This will make your install more secure '. 'in a vague, mostly theoretical way. But it will take you like 3 '. 'seconds of mashing on your keyboard to set it up so you might '. 'as well.')), $this->newOption( 'phabricator.mail-key', 'string', '5ce3e7e8787f6e40dfae861da315a5cdf1018f12') ->setHidden(true) ->setSummary( pht('Hashed with other inputs to generate mail tokens.')) ->setDescription( pht( "This is hashed with other inputs to generate mail tokens. If ". "you want, you can change it to some other string which is ". "unique to your install. In particular, you will want to do ". "this if you accidentally send a bunch of mail somewhere you ". "shouldn't have, to invalidate all old reply-to addresses.")), $this->newOption( 'uri.allowed-protocols', 'set', array( 'http' => true, 'https' => true, 'mailto' => true, )) ->setSummary( pht('Determines which URI protocols are auto-linked.')) ->setDescription( pht( "When users write comments which have URIs, they'll be ". "automatically linked if the protocol appears in this set. This ". "whitelist is primarily to prevent security issues like ". "%s URIs.", 'javascript://')) ->addExample("http\nhttps", pht('Valid Setting')) ->setLocked(true), $this->newOption( 'uri.allowed-editor-protocols', 'set', array( 'http' => true, 'https' => true, // This handler is installed by Textmate. 'txmt' => true, // This handler is for MacVim. 'mvim' => true, // Unofficial handler for Vim. 'vim' => true, // Unofficial handler for Sublime. 'subl' => true, // Unofficial handler for Emacs. 'emacs' => true, // This isn't a standard handler installed by an application, but // is a reasonable name for a user-installed handler. 'editor' => true, )) ->setSummary(pht('Whitelists editor protocols for "Open in Editor".')) ->setDescription( pht( 'Users can configure a URI pattern to open files in a text '. 'editor. The URI must use a protocol on this whitelist.')) ->setLocked(true), $this->newOption( 'celerity.resource-hash', 'string', 'd9455ea150622ee044f7931dabfa52aa') ->setSummary( pht('An input to the hash function when building resource hashes.')) ->setDescription( pht( 'This value is an input to the hash function when building '. 'resource hashes. It has no security value, but if you '. 'accidentally poison user caches (by pushing a bad patch or '. 'having something go wrong with a CDN, e.g.) you can change this '. 'to something else and rebuild the Celerity map to break user '. 'caches. Unless you are doing Celerity development, it is '. 'exceptionally unlikely that you need to modify this.')), $this->newOption('remarkup.enable-embedded-youtube', 'bool', false) ->setBoolOptions( array( pht('Embed YouTube videos'), pht("Don't embed YouTube videos"), )) ->setSummary( pht('Determines whether or not YouTube videos get embedded.')) ->setDescription( pht( "If you enable this, linked YouTube videos will be embedded ". "inline. This has mild security implications (you'll leak ". "referrers to YouTube) and is pretty silly (but sort of ". "awesome).")), $this->newOption( 'security.outbound-blacklist', 'list', $default_address_blacklist) ->setLocked(true) ->setSummary( pht( 'Blacklist subnets to prevent user-initiated outbound '. 'requests.')) ->setDescription( pht( 'Phabricator users can make requests to other services from '. 'the Phabricator host in some circumstances (for example, by '. 'creating a repository with a remote URL or having Phabricator '. 'fetch an image from a remote server).'. "\n\n". 'This may represent a security vulnerability if services on '. 'the same subnet will accept commands or reveal private '. 'information over unauthenticated HTTP GET, based on the source '. 'IP address. In particular, all hosts in EC2 have access to '. 'such a service.'. "\n\n". 'This option defines a list of netblocks which Phabricator '. 'will decline to connect to. Generally, you should list all '. 'private IP space here.')) ->addExample(array('0.0.0.0/0'), pht('No Outbound Requests')), $this->newOption('security.strict-transport-security', 'bool', false) ->setLocked(true) ->setBoolOptions( array( pht('Use HSTS'), pht('Do Not Use HSTS'), )) ->setSummary(pht('Enable HTTP Strict Transport Security (HSTS).')) ->setDescription( pht( 'HTTP Strict Transport Security (HSTS) sends a header which '. 'instructs browsers that the site should only be accessed '. 'over HTTPS, never HTTP. This defuses an attack where an '. 'adversary gains access to your network, then proxies requests '. 'through an unsecured link.'. "\n\n". 'Do not enable this option if you serve (or plan to ever serve) '. 'unsecured content over plain HTTP. It is very difficult to '. 'undo this change once users\' browsers have accepted the '. 'setting.')), ); } protected function didValidateOption( PhabricatorConfigOption $option, $value) { $key = $option->getKey(); if ($key == 'security.alternate-file-domain') { $uri = new PhutilURI($value); $protocol = $uri->getProtocol(); if ($protocol !== 'http' && $protocol !== 'https') { throw new PhabricatorConfigValidationException( pht( "Config option '%s' is invalid. The URI must start with ". "'%s' or '%s'.", $key, 'http://', 'https://')); } $domain = $uri->getDomain(); if (strpos($domain, '.') === false) { throw new PhabricatorConfigValidationException( pht( "Config option '%s' is invalid. The URI must contain a dot ('.'), ". "like '%s', not just a bare name like '%s'. ". "Some web browsers will not set cookies on domains with no TLD.", $key, 'http://example.com/', 'http://example/')); } $path = $uri->getPath(); if ($path !== '' && $path !== '/') { throw new PhabricatorConfigValidationException( pht( "Config option '%s' is invalid. The URI must NOT have a path, ". "e.g. '%s' is OK, but '%s' is not. Phabricator must be installed ". "on an entire domain; it can not be installed on a path.", $key, 'http://phabricator.example.com/', 'http://example.com/phabricator/')); } } } } diff --git a/src/applications/config/option/PhabricatorSendGridConfigOptions.php b/src/applications/config/option/PhabricatorSendGridConfigOptions.php index c740792a7..0baa7390a 100644 --- a/src/applications/config/option/PhabricatorSendGridConfigOptions.php +++ b/src/applications/config/option/PhabricatorSendGridConfigOptions.php @@ -1,33 +1,33 @@ newOption('sendgrid.api-user', 'string', null) ->setLocked(true) ->setDescription(pht('SendGrid API username.')), $this->newOption('sendgrid.api-key', 'string', null) ->setHidden(true) ->setDescription(pht('SendGrid API key.')), ); } } diff --git a/src/applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php b/src/applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php index 25b8835ca..41133d5ed 100644 --- a/src/applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php +++ b/src/applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php @@ -1,153 +1,153 @@ newOption( 'syntax-highlighter.engine', 'class', 'PhutilDefaultSyntaxHighlighterEngine') ->setBaseClass('PhutilSyntaxHighlighterEngine') ->setSummary(pht('Default non-pygments syntax highlighter engine.')) ->setDescription( pht( 'Phabricator can highlight PHP by default and use Pygments for '. 'other languages if enabled. You can provide a custom '. 'highlighter engine by extending class %s.', 'PhutilSyntaxHighlighterEngine')), $this->newOption('pygments.enabled', 'bool', false) ->setSummary( pht('Should Phabricator use Pygments to highlight code?')) ->setBoolOptions( array( pht('Use Pygments'), pht('Do Not Use Pygments'), )) ->setDescription( pht( 'Phabricator supports syntax highlighting a few languages by '. 'default, but you can install Pygments (a third-party syntax '. 'highlighting tool) to provide support for many more languages.'. "\n\n". 'To install Pygments, visit '. '[[ http://pygments.org | pygments.org ]] and follow the '. 'download and install instructions.'. "\n\n". 'Once Pygments is installed, enable this option '. '(`pygments.enabled`) to make Phabricator use Pygments when '. 'highlighting source code.'. "\n\n". 'After you install and enable Pygments, newly created source '. 'code (like diffs and pastes) should highlight correctly. '. 'You may need to clear Phabricator\'s caches to get previously '. 'existing source code to highlight. For instructions on '. 'managing caches, see [[ %s | Managing Caches ]].', $caches_href)), $this->newOption( 'pygments.dropdown-choices', 'wild', array( 'apacheconf' => 'Apache Configuration', 'bash' => 'Bash Scripting', 'brainfuck' => 'Brainf*ck', 'c' => 'C', 'coffee-script' => 'CoffeeScript', 'cpp' => 'C++', 'csharp' => 'C#', 'css' => 'CSS', 'd' => 'D', 'diff' => 'Diff', 'django' => 'Django Templating', 'docker' => 'Docker', 'erb' => 'Embedded Ruby/ERB', 'erlang' => 'Erlang', 'go' => 'Golang', 'groovy' => 'Groovy', 'haskell' => 'Haskell', 'html' => 'HTML', 'http' => 'HTTP', 'invisible' => 'Invisible', 'java' => 'Java', 'js' => 'Javascript', 'json' => 'JSON', 'make' => 'Makefile', 'mysql' => 'MySQL', 'nginx' => 'Nginx Configuration', 'objc' => 'Objective-C', 'perl' => 'Perl', 'php' => 'PHP', 'postgresql' => 'PostgreSQL', 'pot' => 'Gettext Catalog', 'puppet' => 'Puppet', 'python' => 'Python', 'rainbow' => 'Rainbow', 'remarkup' => 'Remarkup', 'rst' => 'reStructuredText', 'robotframework' => 'RobotFramework', 'ruby' => 'Ruby', 'sql' => 'SQL', 'tex' => 'LaTeX', 'text' => 'Plain Text', 'twig' => 'Twig', 'xml' => 'XML', 'yaml' => 'YAML', )) ->setSummary( pht('Set the language list which appears in dropdowns.')) ->setDescription( pht( 'In places that we display a dropdown to syntax-highlight code, '. 'this is where that list is defined.')), $this->newOption( 'syntax.filemap', 'wild', array( '@\.arcconfig$@' => 'js', '@\.arclint$@' => 'js', '@\.divinerconfig$@' => 'js', )) ->setSummary( pht('Override what language files (based on filename) highlight as.')) ->setDescription( pht( 'This is an override list of regular expressions which allows '. 'you to choose what language files are highlighted as. If your '. 'projects have certain rules about filenames or use unusual or '. 'ambiguous language extensions, you can create a mapping here. '. 'This is an ordered dictionary of regular expressions which will '. 'be tested against the filename. They should map to either an '. 'explicit language as a string value, or a numeric index into '. 'the captured groups as an integer.')) ->addExample('{"@\\.xyz$@": "php"}', pht('Highlight %s as PHP.', '*.xyz')) ->addExample( '{"@/httpd\\.conf@": "apacheconf"}', pht('Highlight httpd.conf as "apacheconf".')) ->addExample( '{"@\\.([^.]+)\\.bak$@": 1}', pht( "Treat all '*.x.bak' file as '.x'. NOTE: We map to capturing group ". "1 by specifying the mapping as '1'")), ); } } diff --git a/src/applications/config/option/PhabricatorTranslationsConfigOptions.php b/src/applications/config/option/PhabricatorTranslationsConfigOptions.php index aa7b9a2b3..c82e7901b 100644 --- a/src/applications/config/option/PhabricatorTranslationsConfigOptions.php +++ b/src/applications/config/option/PhabricatorTranslationsConfigOptions.php @@ -1,38 +1,38 @@ newOption('translation.override', 'wild', array()) ->setSummary(pht('Override translations.')) ->setDescription( pht( "You can use '%s' if you don't want to create a full translation ". "to give users an option for switching to it and you just want to ". "override some strings in the default translation.", 'translation.override')) ->addExample( '{"some string": "my alternative"}', pht('Valid Setting')), ); } } diff --git a/src/applications/config/option/PhabricatorUIConfigOptions.php b/src/applications/config/option/PhabricatorUIConfigOptions.php index efd4f5a5b..e73b4ee78 100644 --- a/src/applications/config/option/PhabricatorUIConfigOptions.php +++ b/src/applications/config/option/PhabricatorUIConfigOptions.php @@ -1,100 +1,100 @@ 'blindigo', 'red' => 'red', 'blue' => 'blue', 'green' => 'green', 'indigo' => 'indigo', 'dark' => 'dark', ); $example = <<newOption('ui.header-color', 'enum', 'blindigo') ->setDescription( pht('Sets the default color scheme of Phabricator.')) ->setEnumOptions($options), $this->newOption('ui.footer-items', 'list', array()) ->setSummary( pht( 'Allows you to add footer links on most pages.')) ->setDescription( pht( "Allows you to add a footer with links in it to most ". "pages. You might want to use these links to point at legal ". "information or an about page.\n\n". "Specify a list of dictionaries. Each dictionary describes ". "a footer item. These keys are supported:\n\n". " - `name` The name of the item.\n". " - `href` Optionally, the link target of the item. You can ". " omit this if you just want a piece of text, like a copyright ". " notice.")) ->addExample($example, pht('Basic Example')), $this->newOption( 'ui.custom-header', 'custom:PhabricatorCustomHeaderConfigType', null) ->setSummary( pht('Customize the Phabricator logo.')) ->setDescription( pht('You can customize the Phabricator logo by specifying the '. 'phid for a viewable image you have uploaded to Phabricator '. 'via the [[ /file/ | Files application]]. This image should '. 'be:'."\n". ' - 192px X 80px; while not enforced, images with these '. 'dimensions will look best across devices.'."\n". ' - have view policy public if [[ '. '/config/edit/policy.allow-public | `policy.allow-public`]] '. 'is true and otherwise view policy user; mismatches in these '. 'policy settings will result in a broken logo for some users.'. "\n\n". 'You should restart Phabricator after updating this value '. 'to see this change take effect.'. "\n\n". 'As this feature is experimental, please read [[ %s | T4214 ]] '. 'for up to date information.', $experimental_link)) ->addExample($custom_header_example, pht('Valid Config')), ); } } diff --git a/src/applications/conpherence/application/PhabricatorConpherenceApplication.php b/src/applications/conpherence/application/PhabricatorConpherenceApplication.php index 7708e6a53..ea06aaa45 100644 --- a/src/applications/conpherence/application/PhabricatorConpherenceApplication.php +++ b/src/applications/conpherence/application/PhabricatorConpherenceApplication.php @@ -1,81 +1,81 @@ [1-9]\d*)' => 'ConpherenceViewController', '/conpherence/' => array( '' => 'ConpherenceListController', 'thread/(?P[1-9]\d*)/' => 'ConpherenceListController', '(?P[1-9]\d*)/' => 'ConpherenceViewController', '(?P[1-9]\d*)/(?P[1-9]\d*)/' => 'ConpherenceViewController', 'columnview/' => 'ConpherenceColumnViewController', 'new/' => 'ConpherenceNewRoomController', 'search/(?:query/(?P[^/]+)/)?' => 'ConpherenceRoomListController', 'panel/' => 'ConpherenceNotificationPanelController', 'widget/(?P[1-9]\d*)/' => 'ConpherenceWidgetController', 'update/(?P[1-9]\d*)/' => 'ConpherenceUpdateController', ), ); } public function getQuickCreateItems(PhabricatorUser $viewer) { $items = array(); $item = id(new PHUIListItemView()) ->setName(pht('Conpherence Room')) ->setIcon('fa-comments') ->setWorkflow(true) ->setHref($this->getBaseURI().'new/'); $items[] = $item; return $items; } public function getQuicksandURIPatternBlacklist() { return array( '/conpherence/.*', '/Z\d+', ); } public function getMailCommandObjects() { // TODO: Conpherence threads don't currently support any commands directly, // so the documentation page we end up generating is empty and funny // looking. Add support here once we support "!add", "!leave", "!topic", // or whatever else. return array(); } } diff --git a/src/applications/conpherence/config/ConpherenceConfigOptions.php b/src/applications/conpherence/config/ConpherenceConfigOptions.php index f309b8d56..24e1cb126 100644 --- a/src/applications/conpherence/config/ConpherenceConfigOptions.php +++ b/src/applications/conpherence/config/ConpherenceConfigOptions.php @@ -1,32 +1,32 @@ newOption( 'metamta.conpherence.subject-prefix', 'string', '[Conpherence]') ->setDescription(pht('Subject prefix for Conpherence mail.')), ); } } diff --git a/src/applications/countdown/application/PhabricatorCountdownApplication.php b/src/applications/countdown/application/PhabricatorCountdownApplication.php index 5daaa5021..d6c62d7e1 100644 --- a/src/applications/countdown/application/PhabricatorCountdownApplication.php +++ b/src/applications/countdown/application/PhabricatorCountdownApplication.php @@ -1,74 +1,74 @@ [1-9]\d*)' => 'PhabricatorCountdownViewController', '/countdown/' => array( '(?:query/(?P[^/]+)/)?' => 'PhabricatorCountdownListController', '(?P[1-9]\d*)/' => 'PhabricatorCountdownViewController', 'comment/(?P[1-9]\d*)/' => 'PhabricatorCountdownCommentController', 'edit/(?:(?P[1-9]\d*)/)?' => 'PhabricatorCountdownEditController', 'create/' => 'PhabricatorCountdownEditController', 'delete/(?P[1-9]\d*)/' => 'PhabricatorCountdownDeleteController', ), ); } protected function getCustomCapabilities() { return array( PhabricatorCountdownDefaultViewCapability::CAPABILITY => array( 'caption' => pht('Default view policy for new countdowns.'), 'template' => PhabricatorCountdownCountdownPHIDType::TYPECONST, 'capability' => PhabricatorPolicyCapability::CAN_VIEW, ), PhabricatorCountdownDefaultEditCapability::CAPABILITY => array( 'caption' => pht('Default edit policy for new countdowns.'), 'template' => PhabricatorCountdownCountdownPHIDType::TYPECONST, 'capability' => PhabricatorPolicyCapability::CAN_EDIT, ), ); } } diff --git a/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php b/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php index ae90e5f3e..79b329385 100644 --- a/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php +++ b/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php @@ -1,168 +1,168 @@ newQuery(); if ($map['authorPHIDs']) { $query->withAuthorPHIDs($map['authorPHIDs']); } if ($map['upcoming'] && $map['upcoming'][0] == 'upcoming') { $query->withUpcoming(); } return $query; } protected function buildCustomSearchFields() { return array( id(new PhabricatorUsersSearchField()) ->setLabel(pht('Authors')) ->setKey('authorPHIDs') ->setAliases(array('author', 'authors')), id(new PhabricatorSearchCheckboxesField()) ->setKey('upcoming') ->setOptions(array( 'upcoming' => pht('Show only upcoming countdowns.'), )), ); } protected function getURI($path) { return '/countdown/'.$path; } protected function getBuiltinQueryNames() { $names = array( 'upcoming' => pht('Upcoming'), 'all' => pht('All'), ); if ($this->requireViewer()->getPHID()) { $names['authored'] = pht('Authored'); } return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); switch ($query_key) { case 'all': return $query; case 'authored': return $query->setParameter( 'authorPHIDs', array($this->requireViewer()->getPHID())); case 'upcoming': return $query->setParameter('upcoming', array('upcoming')); } return parent::buildSavedQueryFromBuiltin($query_key); } protected function getRequiredHandlePHIDsForResultList( array $countdowns, PhabricatorSavedQuery $query) { return mpull($countdowns, 'getAuthorPHID'); } protected function renderResultList( array $countdowns, PhabricatorSavedQuery $query, array $handles) { assert_instances_of($countdowns, 'PhabricatorCountdown'); $viewer = $this->requireViewer(); $list = new PHUIObjectItemListView(); $list->setUser($viewer); foreach ($countdowns as $countdown) { $id = $countdown->getID(); $ended = false; $epoch = $countdown->getEpoch(); if ($epoch <= PhabricatorTime::getNow()) { $ended = true; } $item = id(new PHUIObjectItemView()) ->setUser($viewer) ->setObject($countdown) ->setObjectName("C{$id}") ->setHeader($countdown->getTitle()) ->setHref($this->getApplicationURI("{$id}/")) ->addByline( pht( 'Created by %s', $handles[$countdown->getAuthorPHID()]->renderLink())); if ($ended) { $item->addAttribute( pht('Launched on %s', phabricator_datetime($epoch, $viewer))); $item->setDisabled(true); } else { $time_left = ($epoch - PhabricatorTime::getNow()); $num = round($time_left / (60 * 60 * 24)); $noun = pht('Days'); if ($num < 1) { $num = round($time_left / (60 * 60), 1); $noun = pht('Hours'); } $item->setCountdown($num, $noun); $item->addAttribute( phabricator_datetime($epoch, $viewer)); } $list->addItem($item); } $result = new PhabricatorApplicationSearchResultView(); $result->setObjectList($list); $result->setNoDataString(pht('No countdowns found.')); return $result; } protected function getNewUserBody() { $create_button = id(new PHUIButtonView()) ->setTag('a') ->setText(pht('Create a Countdown')) ->setHref('/countdown/create/') ->setColor(PHUIButtonView::GREEN); - $icon = $this->getApplication()->getFontIcon(); + $icon = $this->getApplication()->getIcon(); $app_name = $this->getApplication()->getName(); $view = id(new PHUIBigInfoView()) ->setIcon($icon) ->setTitle(pht('Welcome to %s', $app_name)) ->setDescription( pht('Keep track of upcoming launch dates with '. 'embeddable counters.')) ->addAction($create_button); return $view; } } diff --git a/src/applications/daemon/application/PhabricatorDaemonsApplication.php b/src/applications/daemon/application/PhabricatorDaemonsApplication.php index c605be63b..a0fb77beb 100644 --- a/src/applications/daemon/application/PhabricatorDaemonsApplication.php +++ b/src/applications/daemon/application/PhabricatorDaemonsApplication.php @@ -1,62 +1,62 @@ array( '' => 'PhabricatorDaemonConsoleController', 'task/(?P[1-9]\d*)/' => 'PhabricatorWorkerTaskDetailController', 'log/' => array( '' => 'PhabricatorDaemonLogListController', '(?P[1-9]\d*)/' => 'PhabricatorDaemonLogViewController', ), 'event/(?P[1-9]\d*)/' => 'PhabricatorDaemonLogEventViewController', 'bulk/' => array( '(?:query/(?P[^/]+)/)?' => 'PhabricatorDaemonBulkJobListController', 'monitor/(?P\d+)/' => 'PhabricatorDaemonBulkJobMonitorController', 'view/(?P\d+)/' => 'PhabricatorDaemonBulkJobViewController', ), ), ); } } diff --git a/src/applications/dashboard/application/PhabricatorDashboardApplication.php b/src/applications/dashboard/application/PhabricatorDashboardApplication.php index 0c7304a92..965325663 100644 --- a/src/applications/dashboard/application/PhabricatorDashboardApplication.php +++ b/src/applications/dashboard/application/PhabricatorDashboardApplication.php @@ -1,59 +1,59 @@ \d+)' => 'PhabricatorDashboardPanelViewController', '/dashboard/' => array( '(?:query/(?P[^/]+)/)?' => 'PhabricatorDashboardListController', 'view/(?P\d+)/' => 'PhabricatorDashboardViewController', 'archive/(?P\d+)/' => 'PhabricatorDashboardArchiveController', 'manage/(?P\d+)/' => 'PhabricatorDashboardManageController', 'history/(?P\d+)/' => 'PhabricatorDashboardHistoryController', 'create/' => 'PhabricatorDashboardEditController', 'copy/(?:(?P\d+)/)?' => 'PhabricatorDashboardCopyController', 'edit/(?:(?P\d+)/)?' => 'PhabricatorDashboardEditController', 'install/(?P\d+)/' => 'PhabricatorDashboardInstallController', 'uninstall/(?P\d+)/' => 'PhabricatorDashboardUninstallController', 'addpanel/(?P\d+)/' => 'PhabricatorDashboardAddPanelController', 'movepanel/(?P\d+)/' => 'PhabricatorDashboardMovePanelController', 'removepanel/(?P\d+)/' => 'PhabricatorDashboardRemovePanelController', 'panel/' => array( '(?:query/(?P[^/]+)/)?' => 'PhabricatorDashboardPanelListController', 'create/' => 'PhabricatorDashboardPanelEditController', 'edit/(?:(?P\d+)/)?' => 'PhabricatorDashboardPanelEditController', 'render/(?P\d+)/' => 'PhabricatorDashboardPanelRenderController', 'archive/(?P\d+)/' => 'PhabricatorDashboardPanelArchiveController', ), ), ); } public function getRemarkupRules() { return array( new PhabricatorDashboardRemarkupRule(), ); } } diff --git a/src/applications/dashboard/controller/PhabricatorDashboardPanelListController.php b/src/applications/dashboard/controller/PhabricatorDashboardPanelListController.php index 4136cad35..eaffdaa0d 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardPanelListController.php +++ b/src/applications/dashboard/controller/PhabricatorDashboardPanelListController.php @@ -1,70 +1,70 @@ getURIData('queryKey'); $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($query_key) ->setSearchEngine(new PhabricatorDashboardPanelSearchEngine()) ->setNavigation($this->buildSideNavView()); return $this->delegateToController($controller); } public function buildSideNavView() { $user = $this->getRequest()->getUser(); $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); id(new PhabricatorDashboardPanelSearchEngine()) ->setViewer($user) ->addNavigationItems($nav->getMenu()); $nav->selectFilter(null); return $nav; } protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Panels'), $this->getApplicationURI().'panel/'); $crumbs->addAction( id(new PHUIListItemView()) ->setIcon('fa-plus-square') ->setName(pht('Create Panel')) ->setHref($this->getApplicationURI().'panel/create/')); return $crumbs; } protected function getNewUserBody() { $create_button = id(new PHUIButtonView()) ->setTag('a') ->setText(pht('Create a Panel')) ->setHref('/dashboard/panel/create/') ->setColor(PHUIButtonView::GREEN); - $icon = $this->getApplication()->getFontIcon(); + $icon = $this->getApplication()->getIcon(); $app_name = $this->getApplication()->getName(); $view = id(new PHUIBigInfoView()) ->setIcon($icon) ->setTitle(pht('Welcome to %s', $app_name)) ->setDescription( pht('Build individual panels to display on your homepage dashboard.')) ->addAction($create_button); return $view; } } diff --git a/src/applications/dashboard/query/PhabricatorDashboardSearchEngine.php b/src/applications/dashboard/query/PhabricatorDashboardSearchEngine.php index 83557b141..26c71c1ba 100644 --- a/src/applications/dashboard/query/PhabricatorDashboardSearchEngine.php +++ b/src/applications/dashboard/query/PhabricatorDashboardSearchEngine.php @@ -1,197 +1,197 @@ needProjects(true); } protected function buildCustomSearchFields() { return array( id(new PhabricatorSearchCheckboxesField()) ->setKey('statuses') ->setLabel(pht('Status')) ->setOptions(PhabricatorDashboard::getStatusNameMap()), ); } protected function getURI($path) { return '/dashboard/'.$path; } protected function getBuiltinQueryNames() { return array( 'open' => pht('Active Dashboards'), 'all' => pht('All Dashboards'), ); } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); switch ($query_key) { case 'all': return $query; case 'open': return $query->setParameter( 'statuses', array( PhabricatorDashboard::STATUS_ACTIVE, )); } return parent::buildSavedQueryFromBuiltin($query_key); } protected function buildQueryFromParameters(array $map) { $query = $this->newQuery(); if ($map['statuses']) { $query->withStatuses($map['statuses']); } return $query; } protected function renderResultList( array $dashboards, PhabricatorSavedQuery $query, array $handles) { $dashboards = mpull($dashboards, null, 'getPHID'); $viewer = $this->requireViewer(); if ($dashboards) { $installs = id(new PhabricatorDashboardInstall()) ->loadAllWhere( 'objectPHID IN (%Ls) AND dashboardPHID IN (%Ls)', array( PhabricatorHomeApplication::DASHBOARD_DEFAULT, $viewer->getPHID(), ), array_keys($dashboards)); $installs = mpull($installs, null, 'getDashboardPHID'); } else { $installs = array(); } $proj_phids = array(); foreach ($dashboards as $dashboard) { foreach ($dashboard->getProjectPHIDs() as $project_phid) { $proj_phids[] = $project_phid; } } $proj_handles = id(new PhabricatorHandleQuery()) ->setViewer($viewer) ->withPHIDs($proj_phids) ->execute(); $list = new PHUIObjectItemListView(); $list->setUser($viewer); $list->initBehavior('phabricator-tooltips', array()); $list->requireResource('aphront-tooltip-css'); foreach ($dashboards as $dashboard_phid => $dashboard) { $id = $dashboard->getID(); $item = id(new PHUIObjectItemView()) ->setObjectName(pht('Dashboard %d', $id)) ->setHeader($dashboard->getName()) ->setHref($this->getApplicationURI("view/{$id}/")) ->setObject($dashboard); if (isset($installs[$dashboard_phid])) { $install = $installs[$dashboard_phid]; if ($install->getObjectPHID() == $viewer->getPHID()) { $attrs = array( 'tip' => pht( 'This dashboard is installed to your personal homepage.'), ); $item->addIcon('fa-user', pht('Installed'), $attrs); } else { $attrs = array( 'tip' => pht( 'This dashboard is the default homepage for all users.'), ); $item->addIcon('fa-globe', pht('Installed'), $attrs); } } $project_handles = array_select_keys( $proj_handles, $dashboard->getProjectPHIDs()); $item->addAttribute( id(new PHUIHandleTagListView()) ->setLimit(4) ->setNoDataString(pht('No Projects')) ->setSlim(true) ->setHandles($project_handles)); if ($dashboard->isArchived()) { $item->setDisabled(true); } $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $dashboard, PhabricatorPolicyCapability::CAN_EDIT); $href_view = $this->getApplicationURI("manage/{$id}/"); $item->addAction( id(new PHUIListItemView()) ->setName(pht('Manage')) ->setIcon('fa-th') ->setHref($href_view)); $href_edit = $this->getApplicationURI("edit/{$id}/"); $item->addAction( id(new PHUIListItemView()) ->setName(pht('Edit')) ->setIcon('fa-pencil') ->setHref($href_edit) ->setDisabled(!$can_edit)); $list->addItem($item); } $result = new PhabricatorApplicationSearchResultView(); $result->setObjectList($list); $result->setNoDataString(pht('No dashboards found.')); return $result; } protected function getNewUserBody() { $create_button = id(new PHUIButtonView()) ->setTag('a') ->setText(pht('Create a Dashboard')) ->setHref('/dashboard/create/') ->setColor(PHUIButtonView::GREEN); - $icon = $this->getApplication()->getFontIcon(); + $icon = $this->getApplication()->getIcon(); $app_name = $this->getApplication()->getName(); $view = id(new PHUIBigInfoView()) ->setIcon($icon) ->setTitle(pht('Welcome to %s', $app_name)) ->setDescription( pht('Customize your homepage with different panels and '. 'search queries.')) ->addAction($create_button); return $view; } } diff --git a/src/applications/differential/application/PhabricatorDifferentialApplication.php b/src/applications/differential/application/PhabricatorDifferentialApplication.php index a3074d3e3..787bf5278 100644 --- a/src/applications/differential/application/PhabricatorDifferentialApplication.php +++ b/src/applications/differential/application/PhabricatorDifferentialApplication.php @@ -1,217 +1,217 @@ pht('Differential User Guide'), 'href' => PhabricatorEnv::getDoclink('Differential User Guide'), ), ); } public function getFactObjectsForAnalysis() { return array( new DifferentialRevision(), ); } public function getTitleGlyph() { return "\xE2\x9A\x99"; } public function getEventListeners() { return array( new DifferentialActionMenuEventListener(), new DifferentialLandingActionMenuEventListener(), ); } public function getOverview() { return pht( 'Differential is a **code review application** which allows '. 'engineers to review, discuss and approve changes to software.'); } public function getRoutes() { return array( '/D(?P[1-9]\d*)' => 'DifferentialRevisionViewController', '/differential/' => array( '(?:query/(?P[^/]+)/)?' => 'DifferentialRevisionListController', 'diff/' => array( '(?P[1-9]\d*)/' => 'DifferentialDiffViewController', 'create/' => 'DifferentialDiffCreateController', ), 'changeset/' => 'DifferentialChangesetViewController', 'revision/' => array( 'edit/(?:(?P[1-9]\d*)/)?' => 'DifferentialRevisionEditController', 'land/(?:(?P[1-9]\d*))/(?P[^/]+)/' => 'DifferentialRevisionLandController', 'closedetails/(?P[^/]+)/' => 'DifferentialRevisionCloseDetailsController', 'update/(?P[1-9]\d*)/' => 'DifferentialDiffCreateController', 'operation/(?P[1-9]\d*)/' => 'DifferentialRevisionOperationController', ), 'comment/' => array( 'preview/(?P[1-9]\d*)/' => 'DifferentialCommentPreviewController', 'save/(?P[1-9]\d*)/' => 'DifferentialCommentSaveController', 'inline/' => array( 'preview/(?P[1-9]\d*)/' => 'DifferentialInlineCommentPreviewController', 'edit/(?P[1-9]\d*)/' => 'DifferentialInlineCommentEditController', ), ), 'preview/' => 'PhabricatorMarkupPreviewController', ), ); } public function getApplicationOrder() { return 0.100; } public function getRemarkupRules() { return array( new DifferentialRemarkupRule(), ); } public function loadStatus(PhabricatorUser $user) { $limit = self::MAX_STATUS_ITEMS; $revisions = id(new DifferentialRevisionQuery()) ->setViewer($user) ->withResponsibleUsers(array($user->getPHID())) ->withStatus(DifferentialRevisionQuery::STATUS_OPEN) ->needRelationships(true) ->setLimit($limit) ->execute(); $status = array(); if (count($revisions) >= $limit) { $all_count = count($revisions); $all_count_str = pht( '%s+ Active Review(s)', new PhutilNumber($limit - 1)); $type = PhabricatorApplicationStatusView::TYPE_WARNING; $status[] = id(new PhabricatorApplicationStatusView()) ->setType($type) ->setText($all_count_str) ->setCount($all_count); } else { list($blocking, $active, $waiting) = DifferentialRevisionQuery::splitResponsible( $revisions, array($user->getPHID())); $blocking = count($blocking); $blocking_str = pht( '%s Review(s) Blocking Others', new PhutilNumber($blocking)); $type = PhabricatorApplicationStatusView::TYPE_NEEDS_ATTENTION; $status[] = id(new PhabricatorApplicationStatusView()) ->setType($type) ->setText($blocking_str) ->setCount($blocking); $active = count($active); $active_str = pht( '%s Review(s) Need Attention', new PhutilNumber($active)); $type = PhabricatorApplicationStatusView::TYPE_WARNING; $status[] = id(new PhabricatorApplicationStatusView()) ->setType($type) ->setText($active_str) ->setCount($active); $waiting = count($waiting); $waiting_str = pht( '%s Review(s) Waiting on Others', new PhutilNumber($waiting)); $type = PhabricatorApplicationStatusView::TYPE_INFO; $status[] = id(new PhabricatorApplicationStatusView()) ->setType($type) ->setText($waiting_str) ->setCount($waiting); } return $status; } public function supportsEmailIntegration() { return true; } public function getAppEmailBlurb() { return pht( 'Send email to these addresses to create revisions. The body of the '. 'message and / or one or more attachments should be the output of a '. '"diff" command. %s', phutil_tag( 'a', array( 'href' => $this->getInboundEmailSupportLink(), ), pht('Learn More'))); } protected function getCustomCapabilities() { return array( DifferentialDefaultViewCapability::CAPABILITY => array( 'caption' => pht('Default view policy for newly created revisions.'), 'template' => DifferentialRevisionPHIDType::TYPECONST, 'capability' => PhabricatorPolicyCapability::CAN_VIEW, ), ); } public function getMailCommandObjects() { return array( 'revision' => array( 'name' => pht('Email Commands: Revisions'), 'header' => pht('Interacting with Differential Revisions'), 'object' => new DifferentialRevision(), 'summary' => pht( 'This page documents the commands you can use to interact with '. 'revisions in Differential.'), ), ); } public function getApplicationSearchDocumentTypes() { return array( DifferentialRevisionPHIDType::TYPECONST, ); } } diff --git a/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php b/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php index dd864d523..bd1aa9935 100644 --- a/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php +++ b/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php @@ -1,308 +1,308 @@ getFieldKey()] = array( 'disabled' => $field->shouldDisableByDefault(), ); } return array( $this->newOption( 'differential.fields', $custom_field_type, $default_fields) ->setCustomData( id(new DifferentialRevision())->getCustomFieldBaseClass()) ->setDescription( pht( "Select and reorder revision fields.\n\n". "NOTE: This feature is under active development and subject ". "to change.")), $this->newOption( 'differential.whitespace-matters', 'list', array( '/\.py$/', '/\.l?hs$/', )) ->setDescription( pht( "List of file regexps where whitespace is meaningful and should ". "not use 'ignore-all' by default")), $this->newOption('differential.require-test-plan-field', 'bool', true) ->setBoolOptions( array( pht("Require 'Test Plan' field"), pht("Make 'Test Plan' field optional"), )) ->setSummary(pht('Require "Test Plan" field?')) ->setDescription( pht( "Differential has a required 'Test Plan' field by default. You ". "can make it optional by setting this to false. You can also ". "completely remove it above, if you prefer.")), $this->newOption('differential.enable-email-accept', 'bool', false) ->setBoolOptions( array( pht('Enable Email "!accept" Action'), pht('Disable Email "!accept" Action'), )) ->setSummary(pht('Enable or disable "!accept" action via email.')) ->setDescription( pht( 'If inbound email is configured, users can interact with '. 'revisions by using "!actions" in email replies (for example, '. '"!resign" or "!rethink"). However, by default, users may not '. '"!accept" revisions via email: email authentication can be '. 'configured to be very weak, and email "!accept" is kind of '. 'sketchy and implies the revision may not actually be receiving '. 'thorough review. You can enable "!accept" by setting this '. 'option to true.')), $this->newOption('differential.generated-paths', 'list', array()) ->setSummary(pht('File regexps to treat as automatically generated.')) ->setDescription( pht( 'List of file regexps that should be treated as if they are '. 'generated by an automatic process, and thus be hidden by '. 'default in Differential.'. "\n\n". 'NOTE: This property is cached, so you will need to purge the '. 'cache after making changes if you want the new configuration '. 'to affect existing revisions. For instructions, see '. '**[[ %s | Managing Caches ]]** in the documentation.', $caches_href)) ->addExample("/config\.h$/\n#(^|/)autobuilt/#", pht('Valid Setting')), $this->newOption('differential.sticky-accept', 'bool', true) ->setBoolOptions( array( pht('Accepts persist across updates'), pht('Accepts are reset by updates'), )) ->setSummary( pht('Should "Accepted" revisions remain "Accepted" after updates?')) ->setDescription( pht( 'Normally, when revisions that have been "Accepted" are updated, '. 'they remain "Accepted". This allows reviewers to suggest minor '. 'alterations when accepting, and encourages authors to update '. 'if they make minor changes in response to this feedback.'. "\n\n". 'If you want updates to always require re-review, you can disable '. 'the "stickiness" of the "Accepted" status with this option. '. 'This may make the process for minor changes much more burdensome '. 'to both authors and reviewers.')), $this->newOption('differential.allow-self-accept', 'bool', false) ->setBoolOptions( array( pht('Allow self-accept'), pht('Disallow self-accept'), )) ->setSummary(pht('Allows users to accept their own revisions.')) ->setDescription( pht( "If you set this to true, users can accept their own revisions. ". "This action is disabled by default because it's most likely not ". "a behavior you want, but it proves useful if you are working ". "alone on a project and want to make use of all of ". "differential's features.")), $this->newOption('differential.always-allow-close', 'bool', false) ->setBoolOptions( array( pht('Allow any user'), pht('Restrict to submitter'), )) ->setSummary(pht('Allows any user to close accepted revisions.')) ->setDescription( pht( 'If you set this to true, any user can close any revision so '. 'long as it has been accepted. This can be useful depending on '. 'your development model. For example, github-style pull requests '. 'where the reviewer is often the actual committer can benefit '. 'from turning this option to true. If false, only the submitter '. 'can close a revision.')), $this->newOption('differential.always-allow-abandon', 'bool', false) ->setBoolOptions( array( pht('Allow any user'), pht('Restrict to submitter'), )) ->setSummary(pht('Allows any user to abandon revisions.')) ->setDescription( pht( 'If you set this to true, any user can abandon any revision. If '. 'false, only the submitter can abandon a revision.')), $this->newOption('differential.allow-reopen', 'bool', false) ->setBoolOptions( array( pht('Enable reopen'), pht('Disable reopen'), )) ->setSummary(pht('Allows any user to reopen a closed revision.')) ->setDescription( pht( 'If you set this to true, any user can reopen a revision so '. 'long as it has been closed. This can be useful if a revision '. 'is accidentally closed or if a developer changes his or her '. 'mind after closing a revision. If it is false, reopening '. 'is not allowed.')), $this->newOption('differential.close-on-accept', 'bool', false) ->setBoolOptions( array( pht('Treat Accepted Revisions as "Closed"'), pht('Treat Accepted Revisions as "Open"'), )) ->setSummary(pht('Allows "Accepted" to act as a closed status.')) ->setDescription( pht( 'Normally, Differential revisions remain on the dashboard when '. 'they are "Accepted", and the author then commits the changes '. 'to "Close" the revision and move it off the dashboard.'. "\n\n". 'If you have an unusual workflow where Differential is used for '. 'post-commit review (normally called "Audit", elsewhere in '. 'Phabricator), you can set this flag to treat the "Accepted" '. 'state as a "Closed" state and end the review workflow early.'. "\n\n". 'This sort of workflow is very unusual. Very few installs should '. 'need to change this option.')), $this->newOption('differential.days-fresh', 'int', 1) ->setSummary( pht( "For how many business days should a revision be considered ". "'fresh'?")) ->setDescription( pht( 'Revisions newer than this number of days are marked as fresh in '. 'Action Required and Revisions Waiting on You views. Only work '. 'days (not weekends and holidays) are included. Set to 0 to '. 'disable this feature.')), $this->newOption('differential.days-stale', 'int', 3) ->setSummary( pht("After this many days, a revision will be considered 'stale'.")) ->setDescription( pht( "Similar to `%s` but marks stale revisions. ". "If the revision is even older than it is when marked as 'old'.", 'differential.days-fresh')), $this->newOption( 'metamta.differential.subject-prefix', 'string', '[Differential]') ->setDescription(pht('Subject prefix for Differential mail.')), $this->newOption( 'metamta.differential.attach-patches', 'bool', false) ->setBoolOptions( array( pht('Attach Patches'), pht('Do Not Attach Patches'), )) ->setSummary(pht('Attach patches to email, as text attachments.')) ->setDescription( pht( 'If you set this to true, Phabricator will attach patches to '. 'Differential mail (as text attachments). This will not work if '. 'you are using SendGrid as your mail adapter.')), $this->newOption( 'metamta.differential.inline-patches', 'int', 0) ->setSummary(pht('Inline patches in email, as body text.')) ->setDescription( pht( "To include patches inline in email bodies, set this to a ". "positive integer. Patches will be inlined if they are at most ". "that many lines. For instance, a value of 100 means 'inline ". "patches if they are no longer than 100 lines'. By default, ". "patches are not inlined.")), // TODO: Implement 'enum'? Options are 'unified' or 'git'. $this->newOption( 'metamta.differential.patch-format', 'string', 'unified') ->setDescription( pht("Format for inlined or attached patches: 'git' or 'unified'.")), $this->newOption( 'metamta.differential.unified-comment-context', 'bool', false) ->setBoolOptions( array( pht('Show context'), pht('Do not show context'), )) ->setSummary(pht('Show diff context around inline comments in email.')) ->setDescription( pht( 'Normally, inline comments in emails are shown with a file and '. 'line but without any diff context. Enabling this option adds '. 'diff context and the comment thread.')), ); } } diff --git a/src/applications/differential/query/DifferentialRevisionSearchEngine.php b/src/applications/differential/query/DifferentialRevisionSearchEngine.php index 60e16b58b..002a5df18 100644 --- a/src/applications/differential/query/DifferentialRevisionSearchEngine.php +++ b/src/applications/differential/query/DifferentialRevisionSearchEngine.php @@ -1,364 +1,364 @@ needFlags(true) ->needDrafts(true) ->needRelationships(true); } public function getPageSize(PhabricatorSavedQuery $saved) { if ($saved->getQueryKey() == 'active') { return 0xFFFF; } return parent::getPageSize($saved); } public function buildSavedQueryFromRequest(AphrontRequest $request) { $saved = new PhabricatorSavedQuery(); $saved->setParameter( 'responsiblePHIDs', $this->readUsersFromRequest($request, 'responsibles')); $saved->setParameter( 'authorPHIDs', $this->readUsersFromRequest($request, 'authors')); $saved->setParameter( 'reviewerPHIDs', $this->readUsersFromRequest( $request, 'reviewers', array( PhabricatorProjectProjectPHIDType::TYPECONST, ))); $saved->setParameter( 'subscriberPHIDs', $this->readSubscribersFromRequest($request, 'subscribers')); $saved->setParameter( 'repositoryPHIDs', $request->getArr('repositories')); $saved->setParameter( 'projects', $this->readProjectsFromRequest($request, 'projects')); $saved->setParameter( 'draft', $request->getBool('draft')); $saved->setParameter( 'order', $request->getStr('order')); $saved->setParameter( 'status', $request->getStr('status')); return $saved; } public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { $query = id(new DifferentialRevisionQuery()) ->needFlags(true) ->needDrafts(true) ->needRelationships(true); $user_datasource = id(new PhabricatorPeopleUserFunctionDatasource()) ->setViewer($this->requireViewer()); $responsible_phids = $saved->getParameter('responsiblePHIDs', array()); $responsible_phids = $user_datasource->evaluateTokens($responsible_phids); if ($responsible_phids) { $query->withResponsibleUsers($responsible_phids); } $this->setQueryProjects($query, $saved); $author_phids = $saved->getParameter('authorPHIDs', array()); $author_phids = $user_datasource->evaluateTokens($author_phids); if ($author_phids) { $query->withAuthors($author_phids); } $reviewer_phids = $saved->getParameter('reviewerPHIDs', array()); if ($reviewer_phids) { $query->withReviewers($reviewer_phids); } $sub_datasource = id(new PhabricatorMetaMTAMailableFunctionDatasource()) ->setViewer($this->requireViewer()); $subscriber_phids = $saved->getParameter('subscriberPHIDs', array()); $subscriber_phids = $sub_datasource->evaluateTokens($subscriber_phids); if ($subscriber_phids) { $query->withCCs($subscriber_phids); } $repository_phids = $saved->getParameter('repositoryPHIDs', array()); if ($repository_phids) { $query->withRepositoryPHIDs($repository_phids); } $draft = $saved->getParameter('draft', false); if ($draft && $this->requireViewer()->isLoggedIn()) { $query->withDraftRepliesByAuthors( array($this->requireViewer()->getPHID())); } $status = $saved->getParameter('status'); if (idx($this->getStatusOptions(), $status)) { $query->withStatus($status); } $order = $saved->getParameter('order'); if (idx($this->getOrderOptions(), $order)) { $query->setOrder($order); } else { $query->setOrder(DifferentialRevisionQuery::ORDER_CREATED); } return $query; } public function buildSearchForm( AphrontFormView $form, PhabricatorSavedQuery $saved) { $responsible_phids = $saved->getParameter('responsiblePHIDs', array()); $author_phids = $saved->getParameter('authorPHIDs', array()); $reviewer_phids = $saved->getParameter('reviewerPHIDs', array()); $subscriber_phids = $saved->getParameter('subscriberPHIDs', array()); $repository_phids = $saved->getParameter('repositoryPHIDs', array()); $only_draft = $saved->getParameter('draft', false); $projects = $saved->getParameter('projects', array()); $form ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Responsible Users')) ->setName('responsibles') ->setDatasource(new PhabricatorPeopleUserFunctionDatasource()) ->setValue($responsible_phids)) ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Authors')) ->setName('authors') ->setDatasource(new PhabricatorPeopleUserFunctionDatasource()) ->setValue($author_phids)) ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Reviewers')) ->setName('reviewers') ->setDatasource(new PhabricatorProjectOrUserDatasource()) ->setValue($reviewer_phids)) ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Subscribers')) ->setName('subscribers') ->setDatasource(new PhabricatorMetaMTAMailableFunctionDatasource()) ->setValue($subscriber_phids)) ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Repositories')) ->setName('repositories') ->setDatasource(new DiffusionRepositoryDatasource()) ->setValue($repository_phids)) ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Projects')) ->setName('projects') ->setDatasource(new PhabricatorProjectLogicalDatasource()) ->setValue($projects)) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Status')) ->setName('status') ->setOptions($this->getStatusOptions()) ->setValue($saved->getParameter('status'))); if ($this->requireViewer()->isLoggedIn()) { $form ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'draft', 1, pht('Show only revisions with a draft comment.'), $only_draft)); } $form ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Order')) ->setName('order') ->setOptions($this->getOrderOptions()) ->setValue($saved->getParameter('order'))); } protected function getURI($path) { return '/differential/'.$path; } protected function getBuiltinQueryNames() { $names = array(); if ($this->requireViewer()->isLoggedIn()) { $names['active'] = pht('Active Revisions'); $names['authored'] = pht('Authored'); } $names['all'] = pht('All Revisions'); return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); $viewer = $this->requireViewer(); switch ($query_key) { case 'active': return $query ->setParameter('responsiblePHIDs', array($viewer->getPHID())) ->setParameter('status', DifferentialRevisionQuery::STATUS_OPEN); case 'authored': return $query ->setParameter('authorPHIDs', array($viewer->getPHID())); case 'all': return $query; } return parent::buildSavedQueryFromBuiltin($query_key); } private function getStatusOptions() { return array( DifferentialRevisionQuery::STATUS_ANY => pht('All'), DifferentialRevisionQuery::STATUS_OPEN => pht('Open'), DifferentialRevisionQuery::STATUS_ACCEPTED => pht('Accepted'), DifferentialRevisionQuery::STATUS_NEEDS_REVIEW => pht('Needs Review'), DifferentialRevisionQuery::STATUS_NEEDS_REVISION => pht('Needs Revision'), DifferentialRevisionQuery::STATUS_CLOSED => pht('Closed'), DifferentialRevisionQuery::STATUS_ABANDONED => pht('Abandoned'), ); } private function getOrderOptions() { return array( DifferentialRevisionQuery::ORDER_CREATED => pht('Created'), DifferentialRevisionQuery::ORDER_MODIFIED => pht('Updated'), ); } protected function renderResultList( array $revisions, PhabricatorSavedQuery $query, array $handles) { assert_instances_of($revisions, 'DifferentialRevision'); $viewer = $this->requireViewer(); $template = id(new DifferentialRevisionListView()) ->setUser($viewer) ->setNoBox($this->isPanelContext()); $views = array(); if ($query->getQueryKey() == 'active') { $split = DifferentialRevisionQuery::splitResponsible( $revisions, $query->getParameter('responsiblePHIDs')); list($blocking, $active, $waiting) = $split; $views[] = id(clone $template) ->setHeader(pht('Blocking Others')) ->setNoDataString( pht('No revisions are blocked on your action.')) ->setHighlightAge(true) ->setRevisions($blocking) ->setHandles(array()); $views[] = id(clone $template) ->setHeader(pht('Action Required')) ->setNoDataString( pht('No revisions require your action.')) ->setHighlightAge(true) ->setRevisions($active) ->setHandles(array()); $views[] = id(clone $template) ->setHeader(pht('Waiting on Others')) ->setNoDataString( pht('You have no revisions waiting on others.')) ->setRevisions($waiting) ->setHandles(array()); } else { $views[] = id(clone $template) ->setRevisions($revisions) ->setHandles(array()); } $phids = array_mergev(mpull($views, 'getRequiredHandlePHIDs')); if ($phids) { $handles = id(new PhabricatorHandleQuery()) ->setViewer($viewer) ->withPHIDs($phids) ->execute(); } else { $handles = array(); } foreach ($views as $view) { $view->setHandles($handles); } if (count($views) == 1) { // Reduce this to a PHUIObjectItemListView so we can get the free // support from ApplicationSearch. $list = head($views)->render(); } else { $list = $views; } $result = new PhabricatorApplicationSearchResultView(); $result->setContent($list); return $result; } protected function getNewUserBody() { $create_button = id(new PHUIButtonView()) ->setTag('a') ->setText(pht('Create a Diff')) ->setHref('/differential/diff/create/') ->setColor(PHUIButtonView::GREEN); - $icon = $this->getApplication()->getFontIcon(); + $icon = $this->getApplication()->getIcon(); $app_name = $this->getApplication()->getName(); $view = id(new PHUIBigInfoView()) ->setIcon($icon) ->setTitle(pht('Welcome to %s', $app_name)) ->setDescription( pht('Pre-commit code review. Revisions that are waiting on your input '. 'will appear here.')) ->addAction($create_button); return $view; } } diff --git a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php index 0f809e998..fa82b4e80 100644 --- a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php +++ b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php @@ -1,182 +1,182 @@ pht('Diffusion User Guide'), 'href' => PhabricatorEnv::getDoclink('Diffusion User Guide'), ), ); } public function getFactObjectsForAnalysis() { return array( new PhabricatorRepositoryCommit(), ); } public function getRemarkupRules() { return array( new DiffusionCommitRemarkupRule(), new DiffusionRepositoryRemarkupRule(), new DiffusionRepositoryByIDRemarkupRule(), ); } public function getRoutes() { return array( '/(?:'. 'r(?P[A-Z]+)'. '|'. 'R(?P[1-9]\d*):'. ')(?P[a-f0-9]+)' => 'DiffusionCommitController', '/diffusion/' => array( '(?:query/(?P[^/]+)/)?' => 'DiffusionRepositoryListController', 'new/' => 'DiffusionRepositoryNewController', '(?Pcreate)/' => 'DiffusionRepositoryCreateController', '(?Pimport)/' => 'DiffusionRepositoryCreateController', 'pushlog/' => array( '(?:query/(?P[^/]+)/)?' => 'DiffusionPushLogListController', 'view/(?P\d+)/' => 'DiffusionPushEventViewController', ), '(?P[A-Z]+)/' => array( '' => 'DiffusionRepositoryController', 'repository/(?P.*)' => 'DiffusionRepositoryController', 'change/(?P.*)' => 'DiffusionChangeController', 'history/(?P.*)' => 'DiffusionHistoryController', 'browse/(?P.*)' => 'DiffusionBrowseController', 'lastmodified/(?P.*)' => 'DiffusionLastModifiedController', 'diff/' => 'DiffusionDiffController', 'tags/(?P.*)' => 'DiffusionTagListController', 'branches/(?P.*)' => 'DiffusionBranchTableController', 'refs/(?P.*)' => 'DiffusionRefTableController', 'lint/(?P.*)' => 'DiffusionLintController', 'commit/(?P[a-z0-9]+)/branches/' => 'DiffusionCommitBranchesController', 'commit/(?P[a-z0-9]+)/tags/' => 'DiffusionCommitTagsController', 'commit/(?P[a-z0-9]+)/edit/' => 'DiffusionCommitEditController', 'edit/' => array( '' => 'DiffusionRepositoryEditMainController', 'basic/' => 'DiffusionRepositoryEditBasicController', 'encoding/' => 'DiffusionRepositoryEditEncodingController', 'activate/' => 'DiffusionRepositoryEditActivateController', 'dangerous/' => 'DiffusionRepositoryEditDangerousController', 'branches/' => 'DiffusionRepositoryEditBranchesController', 'subversion/' => 'DiffusionRepositoryEditSubversionController', 'actions/' => 'DiffusionRepositoryEditActionsController', '(?Premote)/' => 'DiffusionRepositoryCreateController', '(?Ppolicy)/' => 'DiffusionRepositoryCreateController', 'storage/' => 'DiffusionRepositoryEditStorageController', 'delete/' => 'DiffusionRepositoryEditDeleteController', 'hosting/' => 'DiffusionRepositoryEditHostingController', '(?Pserve)/' => 'DiffusionRepositoryEditHostingController', 'update/' => 'DiffusionRepositoryEditUpdateController', 'symbol/' => 'DiffusionRepositorySymbolsController', 'staging/' => 'DiffusionRepositoryEditStagingController', 'automation/' => 'DiffusionRepositoryEditAutomationController', 'testautomation/' => 'DiffusionRepositoryTestAutomationController', ), 'pathtree/(?P.*)' => 'DiffusionPathTreeController', 'mirror/' => array( 'edit/(?:(?P\d+)/)?' => 'DiffusionMirrorEditController', 'delete/(?P\d+)/' => 'DiffusionMirrorDeleteController', ), ), // NOTE: This must come after the rule above; it just gives us a // catch-all for serving repositories over HTTP. We must accept // requests without the trailing "/" because SVN commands don't // necessarily include it. '(?P[A-Z]+)(?:/.*)?' => 'DiffusionRepositoryDefaultController', 'inline/' => array( 'edit/(?P[^/]+)/' => 'DiffusionInlineCommentController', 'preview/(?P[^/]+)/' => 'DiffusionInlineCommentPreviewController', ), 'services/' => array( 'path/' => array( 'complete/' => 'DiffusionPathCompleteController', 'validate/' => 'DiffusionPathValidateController', ), ), 'symbol/(?P[^/]+)/' => 'DiffusionSymbolController', 'external/' => 'DiffusionExternalController', 'lint/' => 'DiffusionLintController', ), ); } public function getApplicationOrder() { return 0.120; } protected function getCustomCapabilities() { return array( DiffusionDefaultViewCapability::CAPABILITY => array( 'template' => PhabricatorRepositoryRepositoryPHIDType::TYPECONST, 'capability' => PhabricatorPolicyCapability::CAN_VIEW, ), DiffusionDefaultEditCapability::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_ADMIN, 'template' => PhabricatorRepositoryRepositoryPHIDType::TYPECONST, 'capability' => PhabricatorPolicyCapability::CAN_EDIT, ), DiffusionDefaultPushCapability::CAPABILITY => array( 'template' => PhabricatorRepositoryRepositoryPHIDType::TYPECONST, ), DiffusionCreateRepositoriesCapability::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_ADMIN, ), ); } public function getMailCommandObjects() { return array( 'commit' => array( 'name' => pht('Email Commands: Commits'), 'header' => pht('Interacting with Commits'), 'object' => new PhabricatorRepositoryCommit(), 'summary' => pht( 'This page documents the commands you can use to interact with '. 'commits and audits in Diffusion.'), ), ); } public function getApplicationSearchDocumentTypes() { return array( PhabricatorRepositoryCommitPHIDType::TYPECONST, ); } } diff --git a/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php b/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php index 9c3d7eff8..50f761eab 100644 --- a/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php +++ b/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php @@ -1,152 +1,152 @@ getFieldKey()] = array( 'disabled' => $field->shouldDisableByDefault(), ); } return array( $this->newOption( 'metamta.diffusion.subject-prefix', 'string', '[Diffusion]') ->setDescription(pht('Subject prefix for Diffusion mail.')), $this->newOption( 'metamta.diffusion.attach-patches', 'bool', false) ->setBoolOptions( array( pht('Attach Patches'), pht('Do Not Attach Patches'), )) ->setDescription( pht( 'Set this to true if you want patches to be attached to commit '. 'notifications from Diffusion.')), $this->newOption('metamta.diffusion.inline-patches', 'int', 0) ->setSummary(pht('Include patches in Diffusion mail as body text.')) ->setDescription( pht( 'To include patches in Diffusion email bodies, set this to a '. 'positive integer. Patches will be inlined if they are at most '. 'that many lines. By default, patches are not inlined.')), $this->newOption('metamta.diffusion.byte-limit', 'int', 1024 * 1024) ->setDescription(pht('Hard byte limit on including patches in email.')), $this->newOption('metamta.diffusion.time-limit', 'int', 60) ->setDescription(pht('Hard time limit on generating patches.')), $this->newOption( 'audit.can-author-close-audit', 'bool', false) ->setBoolOptions( array( pht('Enable Closing Audits'), pht('Disable Closing Audits'), )) ->setDescription(pht('Controls whether Author can Close Audits.')), $this->newOption('bugtraq.url', 'string', null) ->addExample('https://bugs.php.net/%BUGID%', pht('PHP bugs')) ->addExample('/%BUGID%', pht('Local Maniphest URL')) ->setDescription( pht( 'URL of external bug tracker used by Diffusion. %s will be '. 'substituted by the bug ID.', '%BUGID%')), $this->newOption('bugtraq.logregex', 'list', array()) ->addExample(array('/\B#([1-9]\d*)\b/'), pht('Issue #123')) ->addExample( array('/[Ii]ssues?:?(\s*,?\s*#\d+)+/', '/(\d+)/'), pht('Issue #123, #456')) ->addExample(array('/(?addExample('/[A-Z]{2,}-\d+/', pht('JIRA-1234')) ->setDescription( pht( 'Regular expression to link external bug tracker. See '. 'http://tortoisesvn.net/docs/release/TortoiseSVN_en/'. 'tsvn-dug-bugtracker.html for further explanation.')), $this->newOption('diffusion.allow-http-auth', 'bool', false) ->setBoolOptions( array( pht('Allow HTTP Basic Auth'), pht('Disable HTTP Basic Auth'), )) ->setSummary(pht('Enable HTTP Basic Auth for repositories.')) ->setDescription( pht( "Phabricator can serve repositories over HTTP, using HTTP basic ". "auth.\n\n". "Because HTTP basic auth is less secure than SSH auth, it is ". "disabled by default. You can enable it here if you'd like to use ". "it anyway. There's nothing fundamentally insecure about it as ". "long as Phabricator uses HTTPS, but it presents a much lower ". "barrier to attackers than SSH does.\n\n". "Consider using SSH for authenticated access to repositories ". "instead of HTTP.")), $this->newOption('diffusion.ssh-user', 'string', null) ->setLocked(true) ->setSummary(pht('Login username for SSH connections to repositories.')) ->setDescription( pht( 'When constructing clone URIs to show to users, Diffusion will '. 'fill in this login username. If you have configured a VCS user '. 'like `git`, you should provide it here.')), $this->newOption('diffusion.ssh-port', 'int', null) ->setLocked(true) ->setSummary(pht('Port for SSH connections to repositories.')) ->setDescription( pht( 'When constructing clone URIs to show to users, Diffusion by '. 'default will not display a port assuming the default for your '. 'VCS. Explicitly declare when running on a non-standard port.')), $this->newOption('diffusion.ssh-host', 'string', null) ->setLocked(true) ->setSummary(pht('Host for SSH connections to repositories.')) ->setDescription( pht( 'If you accept Phabricator SSH traffic on a different host '. 'from web traffic (for example, if you use different SSH and '. 'web load balancers), you can set the SSH hostname here. This '. 'is an advanced option.')), $this->newOption('diffusion.fields', $custom_field_type, $default_fields) ->setCustomData( id(new PhabricatorRepositoryCommit()) ->getCustomFieldBaseClass()) ->setDescription( pht('Select and reorder Diffusion fields.')), ); } } diff --git a/src/applications/diviner/application/PhabricatorDivinerApplication.php b/src/applications/diviner/application/PhabricatorDivinerApplication.php index 66a8eac63..83ca5e0da 100644 --- a/src/applications/diviner/application/PhabricatorDivinerApplication.php +++ b/src/applications/diviner/application/PhabricatorDivinerApplication.php @@ -1,83 +1,83 @@ pht('Diviner User Guide'), 'href' => PhabricatorEnv::getDoclink('Diviner User Guide'), ), ); } public function getTitleGlyph() { return "\xE2\x97\x89"; } public function getRoutes() { return array( '/diviner/' => array( '' => 'DivinerMainController', 'query/((?[^/]+)/)?' => 'DivinerAtomListController', 'find/' => 'DivinerFindController', ), '/book/(?P[^/]+)/' => 'DivinerBookController', '/book/(?P[^/]+)/edit/' => 'DivinerBookEditController', '/book/'. '(?P[^/]+)/'. '(?P[^/]+)/'. '(?:(?P[^/]+)/)?'. '(?P[^/]+)/'. '(?:(?P\d+)/)?' => 'DivinerAtomController', ); } public function getApplicationGroup() { return self::GROUP_UTILITIES; } protected function getCustomCapabilities() { return array( DivinerDefaultViewCapability::CAPABILITY => array( 'template' => DivinerBookPHIDType::TYPECONST, 'capability' => PhabricatorPolicyCapability::CAN_VIEW, ), DivinerDefaultEditCapability::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_ADMIN, 'template' => DivinerBookPHIDType::TYPECONST, 'capability' => PhabricatorPolicyCapability::CAN_EDIT, ), ); } public function getRemarkupRules() { return array( new DivinerSymbolRemarkupRule(), ); } public function getApplicationSearchDocumentTypes() { return array( DivinerAtomPHIDType::TYPECONST, DivinerBookPHIDType::TYPECONST, ); } } diff --git a/src/applications/doorkeeper/application/PhabricatorDoorkeeperApplication.php b/src/applications/doorkeeper/application/PhabricatorDoorkeeperApplication.php index b0e155a38..6342dd18b 100644 --- a/src/applications/doorkeeper/application/PhabricatorDoorkeeperApplication.php +++ b/src/applications/doorkeeper/application/PhabricatorDoorkeeperApplication.php @@ -1,40 +1,40 @@ array( 'tags/' => 'DoorkeeperTagsController', ), ); } } diff --git a/src/applications/doorkeeper/option/PhabricatorAsanaConfigOptions.php b/src/applications/doorkeeper/option/PhabricatorAsanaConfigOptions.php index e7f2ef85d..991392f48 100644 --- a/src/applications/doorkeeper/option/PhabricatorAsanaConfigOptions.php +++ b/src/applications/doorkeeper/option/PhabricatorAsanaConfigOptions.php @@ -1,164 +1,164 @@ newOption('asana.workspace-id', 'string', null) ->setSummary(pht('Asana Workspace ID to publish into.')) ->setDescription( pht( 'To enable synchronization into Asana, enter an Asana Workspace '. 'ID here.'. "\n\n". "NOTE: This feature is new and experimental.")), $this->newOption('asana.project-ids', 'wild', null) ->setSummary(pht('Optional Asana projects to use as application tags.')) ->setDescription( pht( 'When Phabricator creates tasks in Asana, it can add the tasks '. 'to Asana projects based on which application the corresponding '. 'object in Phabricator comes from. For example, you can add code '. 'reviews in Asana to a "Differential" project.'. "\n\n". 'NOTE: This feature is new and experimental.')), ); } public function renderContextualDescription( PhabricatorConfigOption $option, AphrontRequest $request) { switch ($option->getKey()) { case 'asana.workspace-id': break; case 'asana.project-ids': return $this->renderContextualProjectDescription($option, $request); default: return parent::renderContextualDescription($option, $request); } $viewer = $request->getUser(); $provider = PhabricatorAsanaAuthProvider::getAsanaProvider(); if (!$provider) { return null; } $account = id(new PhabricatorExternalAccountQuery()) ->setViewer($viewer) ->withUserPHIDs(array($viewer->getPHID())) ->withAccountTypes(array($provider->getProviderType())) ->withAccountDomains(array($provider->getProviderDomain())) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$account) { return null; } $token = $provider->getOAuthAccessToken($account); if (!$token) { return null; } try { $workspaces = id(new PhutilAsanaFuture()) ->setAccessToken($token) ->setRawAsanaQuery('workspaces') ->resolve(); } catch (Exception $ex) { return null; } if (!$workspaces) { return null; } $out = array(); $out[] = sprintf( '| %s | %s |', pht('Workspace ID'), pht('Workspace Name')); $out[] = '| ------------ | -------------- |'; foreach ($workspaces as $workspace) { $out[] = sprintf('| `%s` | `%s` |', $workspace['id'], $workspace['name']); } $out = implode("\n", $out); $out = pht( "The Asana Workspaces your linked account has access to are:\n\n%s", $out); return PhabricatorMarkupEngine::renderOneObject( id(new PhabricatorMarkupOneOff())->setContent($out), 'default', $viewer); } private function renderContextualProjectDescription( PhabricatorConfigOption $option, AphrontRequest $request) { $viewer = $request->getUser(); $publishers = id(new PhutilClassMapQuery()) ->setAncestorClass('DoorkeeperFeedStoryPublisher') ->execute(); $out = array(); $out[] = pht( 'To specify projects to add tasks to, enter a JSON map with publisher '. 'class names as keys and a list of project IDs as values. For example, '. 'to put Differential tasks into Asana projects with IDs `123` and '. '`456`, enter:'. "\n\n". " lang=txt\n". " {\n". " \"DifferentialDoorkeeperRevisionFeedStoryPublisher\" : [123, 456]\n". " }\n"); $out[] = pht('Available publishers class names are:'); foreach ($publishers as $publisher) { $out[] = ' - `'.get_class($publisher).'`'; } $out[] = pht( 'You can find an Asana project ID by clicking the project in Asana and '. 'then examining the URL:'. "\n\n". " lang=txt\n". " https://app.asana.com/0/12345678901234567890/111111111111111111\n". " ^^^^^^^^^^^^^^^^^^^^\n". " This is the ID to use.\n"); $out = implode("\n", $out); return PhabricatorMarkupEngine::renderOneObject( id(new PhabricatorMarkupOneOff())->setContent($out), 'default', $viewer); } } diff --git a/src/applications/drydock/application/PhabricatorDrydockApplication.php b/src/applications/drydock/application/PhabricatorDrydockApplication.php index 6267c26f1..d19e7cb78 100644 --- a/src/applications/drydock/application/PhabricatorDrydockApplication.php +++ b/src/applications/drydock/application/PhabricatorDrydockApplication.php @@ -1,123 +1,123 @@ pht('Drydock User Guide'), 'href' => PhabricatorEnv::getDoclink('Drydock User Guide'), ), ); } public function getRoutes() { return array( '/drydock/' => array( '' => 'DrydockConsoleController', '(?Pblueprint)/' => array( '(?:query/(?P[^/]+)/)?' => 'DrydockBlueprintListController', '(?P[1-9]\d*)/' => array( '' => 'DrydockBlueprintViewController', '(?Pdisable|enable)/' => 'DrydockBlueprintDisableController', 'resources/(?:query/(?P[^/]+)/)?' => 'DrydockResourceListController', 'logs/(?:query/(?P[^/]+)/)?' => 'DrydockLogListController', 'authorizations/(?:query/(?P[^/]+)/)?' => 'DrydockAuthorizationListController', ), 'create/' => 'DrydockBlueprintCreateController', 'edit/(?:(?P[1-9]\d*)/)?' => 'DrydockBlueprintEditController', ), '(?Presource)/' => array( '(?:query/(?P[^/]+)/)?' => 'DrydockResourceListController', '(?P[1-9]\d*)/' => array( '' => 'DrydockResourceViewController', 'release/' => 'DrydockResourceReleaseController', 'leases/(?:query/(?P[^/]+)/)?' => 'DrydockLeaseListController', 'logs/(?:query/(?P[^/]+)/)?' => 'DrydockLogListController', ), ), '(?Please)/' => array( '(?:query/(?P[^/]+)/)?' => 'DrydockLeaseListController', '(?P[1-9]\d*)/' => array( '' => 'DrydockLeaseViewController', 'release/' => 'DrydockLeaseReleaseController', 'logs/(?:query/(?P[^/]+)/)?' => 'DrydockLogListController', ), ), '(?Pauthorization)/' => array( '(?P[1-9]\d*)/' => array( '' => 'DrydockAuthorizationViewController', '(?Pauthorize|decline)/' => 'DrydockAuthorizationAuthorizeController', ), ), '(?Poperation)/' => array( '(?:query/(?P[^/]+)/)?' => 'DrydockRepositoryOperationListController', '(?P[1-9]\d*)/' => array( '' => 'DrydockRepositoryOperationViewController', 'status/' => 'DrydockRepositoryOperationStatusController', 'dismiss/' => 'DrydockRepositoryOperationDismissController', ), ), ), ); } protected function getCustomCapabilities() { return array( DrydockDefaultViewCapability::CAPABILITY => array( 'template' => DrydockBlueprintPHIDType::TYPECONST, 'capability' => PhabricatorPolicyCapability::CAN_VIEW, ), DrydockDefaultEditCapability::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_ADMIN, 'template' => DrydockBlueprintPHIDType::TYPECONST, 'capability' => PhabricatorPolicyCapability::CAN_EDIT, ), DrydockCreateBlueprintsCapability::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_ADMIN, ), ); } } diff --git a/src/applications/drydock/controller/DrydockConsoleController.php b/src/applications/drydock/controller/DrydockConsoleController.php index 77346cea9..c6a8e25fd 100644 --- a/src/applications/drydock/controller/DrydockConsoleController.php +++ b/src/applications/drydock/controller/DrydockConsoleController.php @@ -1,80 +1,80 @@ setBaseURI(new PhutilURI($this->getApplicationURI())); // These are only used on mobile. $nav->addFilter('blueprint', pht('Blueprints')); $nav->addFilter('resource', pht('Resources')); $nav->addFilter('lease', pht('Leases')); $nav->addFilter('operation', pht('Repository Operations')); $nav->selectFilter(null); return $nav; } public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $menu = id(new PHUIObjectItemListView()) ->setUser($viewer); $menu->addItem( id(new PHUIObjectItemView()) ->setHeader(pht('Blueprints')) - ->setFontIcon('fa-map-o') + ->setIcon('fa-map-o') ->setHref($this->getApplicationURI('blueprint/')) ->addAttribute( pht( 'Configure blueprints so Drydock can build resources, like '. 'hosts and working copies.'))); $menu->addItem( id(new PHUIObjectItemView()) ->setHeader(pht('Resources')) - ->setFontIcon('fa-map') + ->setIcon('fa-map') ->setHref($this->getApplicationURI('resource/')) ->addAttribute( pht('View and manage resources Drydock has built, like hosts.'))); $menu->addItem( id(new PHUIObjectItemView()) ->setHeader(pht('Leases')) - ->setFontIcon('fa-link') + ->setIcon('fa-link') ->setHref($this->getApplicationURI('lease/')) ->addAttribute(pht('Manage leases on resources.'))); $menu->addItem( id(new PHUIObjectItemView()) ->setHeader(pht('Repository Operations')) - ->setFontIcon('fa-fighter-jet') + ->setIcon('fa-fighter-jet') ->setHref($this->getApplicationURI('operation/')) ->addAttribute(pht('Review the repository operation queue.'))); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Console')); $box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Drydock Console')) ->setObjectList($menu); return $this->buildApplicationPage( array( $crumbs, $box, ), array( 'title' => pht('Drydock Console'), )); } } diff --git a/src/applications/fact/application/PhabricatorFactApplication.php b/src/applications/fact/application/PhabricatorFactApplication.php index 5967778c7..305ed3abc 100644 --- a/src/applications/fact/application/PhabricatorFactApplication.php +++ b/src/applications/fact/application/PhabricatorFactApplication.php @@ -1,38 +1,38 @@ array( '' => 'PhabricatorFactHomeController', 'chart/' => 'PhabricatorFactChartController', ), ); } } diff --git a/src/applications/feed/application/PhabricatorFeedApplication.php b/src/applications/feed/application/PhabricatorFeedApplication.php index ac8c21cda..f24ff76fe 100644 --- a/src/applications/feed/application/PhabricatorFeedApplication.php +++ b/src/applications/feed/application/PhabricatorFeedApplication.php @@ -1,34 +1,34 @@ array( '(?P\d+)/' => 'PhabricatorFeedDetailController', '(?:query/(?P[^/]+)/)?' => 'PhabricatorFeedListController', ), ); } } diff --git a/src/applications/feed/config/PhabricatorFeedConfigOptions.php b/src/applications/feed/config/PhabricatorFeedConfigOptions.php index 5e6926f35..4b6612f93 100644 --- a/src/applications/feed/config/PhabricatorFeedConfigOptions.php +++ b/src/applications/feed/config/PhabricatorFeedConfigOptions.php @@ -1,42 +1,42 @@ newOption('feed.http-hooks', 'list', array()) ->setLocked(true) ->setSummary(pht('POST notifications of feed events.')) ->setDescription( pht( "If you set this to a list of HTTP URIs, when a feed story is ". "published a task will be created for each URI that posts the ". "story data to the URI. Daemons automagically retry failures 100 ". "times, waiting `\$fail_count * 60s` between each subsequent ". "failure. Be sure to keep the daemon console (`%s`) open ". "while developing and testing your end points. You may need to". "restart your daemons to start sending HTTP requests.\n\n". "NOTE: URIs are not validated, the URI must return HTTP status ". "200 within 30 seconds, and no permission checks are performed.", '/daemon/')), ); } } diff --git a/src/applications/files/application/PhabricatorFilesApplication.php b/src/applications/files/application/PhabricatorFilesApplication.php index 7447ed728..4cdcd7604 100644 --- a/src/applications/files/application/PhabricatorFilesApplication.php +++ b/src/applications/files/application/PhabricatorFilesApplication.php @@ -1,130 +1,130 @@ $this->getInboundEmailSupportLink(), ), pht('Learn More'))); } protected function getCustomCapabilities() { return array( FilesDefaultViewCapability::CAPABILITY => array( 'caption' => pht('Default view policy for newly created files.'), 'template' => PhabricatorFileFilePHIDType::TYPECONST, 'capability' => PhabricatorPolicyCapability::CAN_VIEW, ), ); } public function getRoutes() { return array( '/F(?P[1-9]\d*)' => 'PhabricatorFileInfoController', '/file/' => array( '(query/(?P[^/]+)/)?' => 'PhabricatorFileListController', 'upload/' => 'PhabricatorFileUploadController', 'dropupload/' => 'PhabricatorFileDropUploadController', 'compose/' => 'PhabricatorFileComposeController', 'comment/(?P[1-9]\d*)/' => 'PhabricatorFileCommentController', 'delete/(?P[1-9]\d*)/' => 'PhabricatorFileDeleteController', 'edit/(?P[1-9]\d*)/' => 'PhabricatorFileEditController', 'info/(?P[^/]+)/' => 'PhabricatorFileInfoController', 'proxy/' => 'PhabricatorFileProxyController', 'transforms/(?P[1-9]\d*)/' => 'PhabricatorFileTransformListController', 'uploaddialog/' => 'PhabricatorFileUploadDialogController', 'download/(?P[^/]+)/' => 'PhabricatorFileDialogController', 'iconset/(?P[^/]+)/' => array( 'select/' => 'PhabricatorFileIconSetSelectController', ), ) + $this->getResourceSubroutes(), ); } public function getResourceRoutes() { return array( '/file/' => $this->getResourceSubroutes(), ); } private function getResourceSubroutes() { return array( 'data/'. '(?:@(?P[^/]+)/)?'. '(?P[^/]+)/'. '(?P[^/]+)/'. '(?:(?P[^/]+)/)?'. '.*' => 'PhabricatorFileDataController', 'xform/'. '(?:@(?P[^/]+)/)?'. '(?P[^/]+)/'. '(?P[^/]+)/'. '(?P[^/]+)/' => 'PhabricatorFileTransformController', ); } public function getMailCommandObjects() { return array( 'file' => array( 'name' => pht('Email Commands: Files'), 'header' => pht('Interacting with Files'), 'object' => new PhabricatorFile(), 'summary' => pht( 'This page documents the commands you can use to interact with '. 'files.'), ), ); } } diff --git a/src/applications/files/config/PhabricatorFilesConfigOptions.php b/src/applications/files/config/PhabricatorFilesConfigOptions.php index e9d899f65..43e12edde 100644 --- a/src/applications/files/config/PhabricatorFilesConfigOptions.php +++ b/src/applications/files/config/PhabricatorFilesConfigOptions.php @@ -1,183 +1,183 @@ 'image/jpeg', 'image/jpg' => 'image/jpg', 'image/png' => 'image/png', 'image/gif' => 'image/gif', 'text/plain' => 'text/plain; charset=utf-8', 'text/x-diff' => 'text/plain; charset=utf-8', // ".ico" favicon files, which have mime type diversity. See: // http://en.wikipedia.org/wiki/ICO_(file_format)#MIME_type 'image/x-ico' => 'image/x-icon', 'image/x-icon' => 'image/x-icon', 'image/vnd.microsoft.icon' => 'image/x-icon', 'audio/x-wav' => 'audio/x-wav', 'application/ogg' => 'application/ogg', 'audio/mpeg' => 'audio/mpeg', ); $image_default = array( 'image/jpeg' => true, 'image/jpg' => true, 'image/png' => true, 'image/gif' => true, 'image/x-ico' => true, 'image/x-icon' => true, 'image/vnd.microsoft.icon' => true, ); $audio_default = array( 'audio/x-wav' => true, 'application/ogg' => true, 'audio/mpeg' => true, ); // largely lifted from http://en.wikipedia.org/wiki/Internet_media_type $icon_default = array( // audio file icon 'audio/basic' => 'fa-file-audio-o', 'audio/L24' => 'fa-file-audio-o', 'audio/mp4' => 'fa-file-audio-o', 'audio/mpeg' => 'fa-file-audio-o', 'audio/ogg' => 'fa-file-audio-o', 'audio/vorbis' => 'fa-file-audio-o', 'audio/vnd.rn-realaudio' => 'fa-file-audio-o', 'audio/vnd.wave' => 'fa-file-audio-o', 'audio/webm' => 'fa-file-audio-o', // movie file icon 'video/mpeg' => 'fa-file-movie-o', 'video/mp4' => 'fa-file-movie-o', 'video/ogg' => 'fa-file-movie-o', 'video/quicktime' => 'fa-file-movie-o', 'video/webm' => 'fa-file-movie-o', 'video/x-matroska' => 'fa-file-movie-o', 'video/x-ms-wmv' => 'fa-file-movie-o', 'video/x-flv' => 'fa-file-movie-o', // pdf file icon 'application/pdf' => 'fa-file-pdf-o', // zip file icon 'application/zip' => 'fa-file-zip-o', // msword icon 'application/msword' => 'fa-file-word-o', // msexcel 'application/vnd.ms-excel' => 'fa-file-excel-o', // mspowerpoint 'application/vnd.ms-powerpoint' => 'fa-file-powerpoint-o', ) + array_fill_keys(array_keys($image_default), 'fa-file-image-o'); // NOTE: These options are locked primarily because adding "text/plain" // as an image MIME type increases SSRF vulnerability by allowing users // to load text files from remote servers as "images" (see T6755 for // discussion). return array( $this->newOption('files.viewable-mime-types', 'wild', $viewable_default) ->setLocked(true) ->setSummary( pht('Configure which MIME types are viewable in the browser.')) ->setDescription( pht( "Configure which uploaded file types may be viewed directly ". "in the browser. Other file types will be downloaded instead ". "of displayed. This is mainly a usability consideration, since ". "browsers tend to freak out when viewing enormous binary files.". "\n\n". "The keys in this map are viewable MIME types; the values are ". "the MIME types they are delivered as when they are viewed in ". "the browser.")), $this->newOption('files.image-mime-types', 'set', $image_default) ->setLocked(true) ->setSummary(pht('Configure which MIME types are images.')) ->setDescription( pht( 'List of MIME types which can be used as the `%s` for an `%s` tag.', 'src', '')), $this->newOption('files.audio-mime-types', 'set', $audio_default) ->setLocked(true) ->setSummary(pht('Configure which MIME types are audio.')) ->setDescription( pht( 'List of MIME types which can be used to render an `%s` tag.', '