Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/aphront/handler/PhabricatorPolicyRequestExceptionHandler.php b/src/aphront/handler/PhabricatorPolicyRequestExceptionHandler.php
index cb1dea3533..442cb08f66 100644
--- a/src/aphront/handler/PhabricatorPolicyRequestExceptionHandler.php
+++ b/src/aphront/handler/PhabricatorPolicyRequestExceptionHandler.php
@@ -1,95 +1,107 @@
<?php
final class PhabricatorPolicyRequestExceptionHandler
extends PhabricatorRequestExceptionHandler {
public function getRequestExceptionHandlerPriority() {
return 320000;
}
public function getRequestExceptionHandlerDescription() {
return pht(
'Handles policy exceptions which occur when a user tries to '.
'do something they do not have permission to do.');
}
public function canHandleRequestThrowable(
AphrontRequest $request,
$throwable) {
if (!$this->isPhabricatorSite($request)) {
return false;
}
return ($throwable instanceof PhabricatorPolicyException);
}
public function handleRequestThrowable(
AphrontRequest $request,
$throwable) {
$viewer = $this->getViewer($request);
if (!$viewer->isLoggedIn()) {
// If the user isn't logged in, just give them a login form. This is
// probably a generally more useful response than a policy dialog that
// they have to click through to get a login form.
//
// Possibly we should add a header here like "you need to login to see
// the thing you are trying to look at".
$auth_app_class = 'PhabricatorAuthApplication';
$auth_app = PhabricatorApplication::getByClass($auth_app_class);
return id(new PhabricatorAuthStartController())
->setRequest($request)
->setCurrentApplication($auth_app)
->handleRequest($request);
}
$content = array(
phutil_tag(
'div',
array(
'class' => 'aphront-policy-rejection',
),
$throwable->getRejection()),
);
$list = null;
if ($throwable->getCapabilityName()) {
$list = $throwable->getMoreInfo();
foreach ($list as $key => $item) {
$list[$key] = $item;
}
$content[] = phutil_tag(
'div',
array(
'class' => 'aphront-capability-details',
),
pht(
'Users with the "%s" capability:',
$throwable->getCapabilityName()));
}
$dialog = id(new AphrontDialogView())
->setTitle($throwable->getTitle())
->setClass('aphront-access-dialog')
->setUser($viewer)
->appendChild($content);
if ($list) {
$dialog->appendList($list);
}
+ // If the install is in developer mode, include a stack trace for the
+ // exception. When debugging things, it isn't always obvious where a
+ // policy exception came from and this can make it easier to hunt down
+ // bugs or improve ambiguous/confusing messaging.
+
+ $is_developer = PhabricatorEnv::getEnvConfig('phabricator.developer-mode');
+ if ($is_developer) {
+ $dialog->appendChild(
+ id(new AphrontStackTraceView())
+ ->setTrace($throwable->getTrace()));
+ }
+
if ($request->isAjax()) {
$dialog->addCancelButton('/', pht('Close'));
} else {
$dialog->addCancelButton('/', pht('OK'));
}
return $dialog;
}
}
diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php
index e276e035e4..1cabeb0709 100644
--- a/src/applications/base/PhabricatorApplication.php
+++ b/src/applications/base/PhabricatorApplication.php
@@ -1,666 +1,666 @@
<?php
/**
* @task info Application Information
* @task ui UI Integration
* @task uri URI Routing
* @task mail Email integration
* @task fact Fact Integration
* @task meta Application Management
*/
abstract class PhabricatorApplication
extends PhabricatorLiskDAO
implements
PhabricatorPolicyInterface,
PhabricatorApplicationTransactionInterface {
const GROUP_CORE = 'core';
const GROUP_UTILITIES = 'util';
const GROUP_ADMIN = 'admin';
const GROUP_DEVELOPER = 'developer';
final public static function getApplicationGroups() {
return array(
self::GROUP_CORE => pht('Core Applications'),
self::GROUP_UTILITIES => pht('Utilities'),
self::GROUP_ADMIN => pht('Administration'),
self::GROUP_DEVELOPER => pht('Developer Tools'),
);
}
final public function getApplicationName() {
return 'application';
}
final public function getTableName() {
return 'application_application';
}
final protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
) + parent::getConfiguration();
}
final public function generatePHID() {
return $this->getPHID();
}
final public function save() {
// When "save()" is called on applications, we just return without
// actually writing anything to the database.
return $this;
}
/* -( 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
* unlaunchable 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 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) {
foreach ($articles as $article) {
$item = id(new PhabricatorActionView())
->setName($article['name'])
->setHref($article['href'])
->addSigil('help-item')
->setOpenInNewWindow(true);
$items[] = $item;
}
}
$command_specs = $this->getMailCommandObjects();
if ($command_specs) {
foreach ($command_specs as $key => $spec) {
$object = $spec['object'];
$class = get_class($this);
$href = '/applications/mailcommands/'.$class.'/'.$key.'/';
$item = id(new PhabricatorActionView())
->setName($spec['name'])
->setHref($href)
->addSigil('help-item')
->setOpenInNewWindow(true);
$items[] = $item;
}
}
if ($items) {
$divider = id(new PhabricatorActionView())
->addSigil('help-item')
->setType(PhabricatorActionView::TYPE_DIVIDER);
array_unshift($items, $divider);
}
return array_values($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 )----------------------------------------------------- */
/**
* 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<PHUIListItemView> List of menu items.
* @task ui
*/
public function buildMainMenuItems(
PhabricatorUser $user,
PhabricatorController $controller = null) {
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_fragment = $viewer->getCacheFragment();
$key = 'app.'.$class.'.installed.'.$viewer_fragment;
$result = $cache->getKey($key);
if ($result === null) {
if (!self::isClassInstalled($class)) {
$result = false;
} else {
$application = self::getByClass($class);
if (!$application->canUninstall()) {
// If the application can not be uninstalled, always allow viewers
// to see it. In particular, this allows logged-out viewers to see
// Settings and load global default settings even if the install
// does not allow public viewers.
$result = true;
} else {
$result = PhabricatorPolicyFilter::hasCapability(
$viewer,
self::getByClass($class),
PhabricatorPolicyCapability::CAN_VIEW);
}
}
$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;
}
/* -( 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;
+ return true;
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<id>[0-9]\d*)/)?'.
'(?:'.
'(?:'.
'(?P<editAction>parameters|nodefault|nocreate|nomanage|comment)/'.
'|'.
'(?:form/(?P<formKey>[^/]+)/)?(?:page/(?P<pageKey>[^/]+)/)?'.
')'.
')?';
}
protected function getBulkRoutePattern($base = null) {
return $base.'(?:query/(?P<queryKey>[^/]+)/)?';
}
protected function getQueryRoutePattern($base = null) {
return $base.'(?:query/(?P<queryKey>[^/]+)/(?:(?P<queryAction>[^/]+)/)?)?';
}
protected function getProfileMenuRouting($controller) {
$edit_route = $this->getEditRoutePattern();
$mode_route = '(?P<itemEditMode>global|custom)/';
return array(
'(?P<itemAction>view)/(?P<itemID>[^/]+)/' => $controller,
'(?P<itemAction>hide)/(?P<itemID>[^/]+)/' => $controller,
'(?P<itemAction>default)/(?P<itemID>[^/]+)/' => $controller,
'(?P<itemAction>configure)/' => $controller,
'(?P<itemAction>configure)/'.$mode_route => $controller,
'(?P<itemAction>reorder)/'.$mode_route => $controller,
'(?P<itemAction>edit)/'.$edit_route => $controller,
'(?P<itemAction>new)/'.$mode_route.'(?<itemKey>[^/]+)/'.$edit_route
=> $controller,
'(?P<itemAction>builtin)/(?<itemID>[^/]+)/'.$edit_route
=> $controller,
);
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new PhabricatorApplicationEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new PhabricatorApplicationApplicationTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
}
diff --git a/src/applications/config/editor/PhabricatorConfigEditor.php b/src/applications/config/editor/PhabricatorConfigEditor.php
index f776c3ec0c..deccf1ef5c 100644
--- a/src/applications/config/editor/PhabricatorConfigEditor.php
+++ b/src/applications/config/editor/PhabricatorConfigEditor.php
@@ -1,160 +1,165 @@
<?php
final class PhabricatorConfigEditor
extends PhabricatorApplicationTransactionEditor {
public function getEditorApplicationClass() {
return 'PhabricatorConfigApplication';
}
public function getEditorObjectsDescription() {
return pht('Phabricator Configuration');
}
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = PhabricatorConfigTransaction::TYPE_EDIT;
return $types;
}
protected function getCustomTransactionOldValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorConfigTransaction::TYPE_EDIT:
return array(
'deleted' => (int)$object->getIsDeleted(),
'value' => $object->getValue(),
);
}
}
protected function getCustomTransactionNewValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorConfigTransaction::TYPE_EDIT:
return $xaction->getNewValue();
}
}
protected function applyCustomInternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorConfigTransaction::TYPE_EDIT:
$v = $xaction->getNewValue();
// If this is a defined configuration option (vs a straggler from an
// old version of Phabricator or a configuration file misspelling)
// submit it to the validation gauntlet.
$key = $object->getConfigKey();
$all_options = PhabricatorApplicationConfigOptions::loadAllOptions();
$option = idx($all_options, $key);
if ($option) {
$option->getGroup()->validateOption(
$option,
$v['value']);
}
$object->setIsDeleted((int)$v['deleted']);
$object->setValue($v['value']);
break;
}
}
protected function applyCustomExternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
return;
}
protected function mergeTransactions(
PhabricatorApplicationTransaction $u,
PhabricatorApplicationTransaction $v) {
$type = $u->getTransactionType();
switch ($type) {
case PhabricatorConfigTransaction::TYPE_EDIT:
return $v;
}
return parent::mergeTransactions($u, $v);
}
protected function transactionHasEffect(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
$type = $xaction->getTransactionType();
switch ($type) {
case PhabricatorConfigTransaction::TYPE_EDIT:
// If an edit deletes an already-deleted entry, no-op it.
if (idx($old, 'deleted') && idx($new, 'deleted')) {
return false;
}
break;
}
return parent::transactionHasEffect($object, $xaction);
}
protected function didApplyTransactions($object, array $xactions) {
// Force all the setup checks to run on the next page load.
PhabricatorSetupCheck::deleteSetupCheckCache();
return $xactions;
}
public static function storeNewValue(
PhabricatorUser $user,
PhabricatorConfigEntry $config_entry,
$value,
- PhabricatorContentSource $source) {
+ PhabricatorContentSource $source,
+ $acting_as_phid = null) {
$xaction = id(new PhabricatorConfigTransaction())
->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT)
->setNewValue(
array(
'deleted' => false,
'value' => $value,
));
$editor = id(new PhabricatorConfigEditor())
->setActor($user)
->setContinueOnNoEffect(true)
->setContentSource($source);
+ if ($acting_as_phid) {
+ $editor->setActingAsPHID($acting_as_phid);
+ }
+
$editor->applyTransactions($config_entry, array($xaction));
}
public static function deleteConfig(
PhabricatorUser $user,
PhabricatorConfigEntry $config_entry,
PhabricatorContentSource $source) {
$xaction = id(new PhabricatorConfigTransaction())
->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT)
->setNewValue(
array(
'deleted' => true,
'value' => null,
));
$editor = id(new PhabricatorConfigEditor())
->setActor($user)
->setContinueOnNoEffect(true)
->setContentSource($source);
$editor->applyTransactions($config_entry, array($xaction));
}
}
diff --git a/src/applications/meta/xactions/PhabricatorApplicationPolicyChangeTransaction.php b/src/applications/meta/xactions/PhabricatorApplicationPolicyChangeTransaction.php
index 91b5c73249..59dfc09486 100644
--- a/src/applications/meta/xactions/PhabricatorApplicationPolicyChangeTransaction.php
+++ b/src/applications/meta/xactions/PhabricatorApplicationPolicyChangeTransaction.php
@@ -1,197 +1,206 @@
<?php
final class PhabricatorApplicationPolicyChangeTransaction
extends PhabricatorApplicationTransactionType {
const TRANSACTIONTYPE = 'application.policy';
const METADATA_ATTRIBUTE = 'capability.name';
private $policies;
public function generateOldValue($object) {
$application = $object;
$capability = $this->getCapabilityName();
return $application->getPolicy($capability);
}
public function applyExternalEffects($object, $value) {
$application = $object;
$user = $this->getActor();
$key = 'phabricator.application-settings';
$config_entry = PhabricatorConfigEntry::loadConfigEntry($key);
$current_value = $config_entry->getValue();
$phid = $application->getPHID();
if (empty($current_value[$phid])) {
$current_value[$application->getPHID()] = array();
}
if (empty($current_value[$phid]['policy'])) {
$current_value[$phid]['policy'] = array();
}
$new = array($this->getCapabilityName() => $value);
$current_value[$phid]['policy'] = $new + $current_value[$phid]['policy'];
$editor = $this->getEditor();
$content_source = $editor->getContentSource();
+
+ // NOTE: We allow applications to have custom edit policies, but they are
+ // currently stored in the Config application. The ability to edit Config
+ // values is always restricted to administrators, today. Empower this
+ // particular edit to punch through possible stricter policies, so normal
+ // users can change application configuration if the application allows
+ // them to do so.
+
PhabricatorConfigEditor::storeNewValue(
- $user,
+ PhabricatorUser::getOmnipotentUser(),
$config_entry,
$current_value,
- $content_source);
+ $content_source,
+ $user->getPHID());
}
public function getTitle() {
$old = $this->renderPolicy($this->getOldValue());
$new = $this->renderPolicy($this->getNewValue());
return pht(
'%s changed the "%s" policy from "%s" to "%s".',
$this->renderAuthor(),
$this->renderCapability(),
$old,
$new);
}
public function getTitleForFeed() {
$old = $this->renderPolicy($this->getOldValue());
$new = $this->renderPolicy($this->getNewValue());
return pht(
'%s changed the "%s" policy for application %s from "%s" to "%s".',
$this->renderAuthor(),
$this->renderCapability(),
$this->renderObject(),
$old,
$new);
}
public function validateTransactions($object, array $xactions) {
$user = $this->getActor();
$application = $object;
$policies = id(new PhabricatorPolicyQuery())
->setViewer($user)
->setObject($application)
->execute();
$errors = array();
foreach ($xactions as $xaction) {
$new = $xaction->getNewValue();
$capability = $xaction->getMetadataValue(self::METADATA_ATTRIBUTE);
if (empty($policies[$new])) {
// Not a standard policy, check for a custom policy.
$policy = id(new PhabricatorPolicyQuery())
->setViewer($user)
->withPHIDs(array($new))
->executeOne();
if (!$policy) {
$errors[] = $this->newInvalidError(
pht('Policy does not exist.'));
continue;
}
} else {
$policy = idx($policies, $new);
}
if (!$policy->isValidPolicyForEdit()) {
$errors[] = $this->newInvalidError(
pht('Can\'t set the policy to a policy you can\'t view!'));
continue;
}
if ($new == PhabricatorPolicies::POLICY_PUBLIC) {
$capobj = PhabricatorPolicyCapability::getCapabilityByKey(
$capability);
if (!$capobj || !$capobj->shouldAllowPublicPolicySetting()) {
$errors[] = $this->newInvalidError(
pht('Can\'t set non-public policies to public.'));
continue;
}
}
if (!$application->isCapabilityEditable($capability)) {
$errors[] = $this->newInvalidError(
pht('Capability "%s" is not editable for this application.',
$capability));
continue;
}
}
// If we're changing these policies, the viewer needs to still be able to
// view or edit the application under the new policy.
$validate_map = array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
$validate_map = array_fill_keys($validate_map, array());
foreach ($xactions as $xaction) {
$capability = $xaction->getMetadataValue(self::METADATA_ATTRIBUTE);
if (!isset($validate_map[$capability])) {
continue;
}
$validate_map[$capability][] = $xaction;
}
foreach ($validate_map as $capability => $cap_xactions) {
if (!$cap_xactions) {
continue;
}
$editor = $this->getEditor();
$policy_errors = $editor->validatePolicyTransaction(
$object,
$cap_xactions,
self::TRANSACTIONTYPE,
$capability);
foreach ($policy_errors as $error) {
$errors[] = $error;
}
}
return $errors;
}
private function renderPolicy($name) {
$policies = $this->getAllPolicies();
if (empty($policies[$name])) {
// Not a standard policy, check for a custom policy.
$policy = id(new PhabricatorPolicyQuery())
->setViewer($this->getViewer())
->withPHIDs(array($name))
->executeOne();
$policies[$name] = $policy;
}
$policy = idx($policies, $name);
return $this->renderValue($policy->getFullName());
}
private function getAllPolicies() {
if (!$this->policies) {
$viewer = $this->getViewer();
$application = $this->getObject();
$this->policies = id(new PhabricatorPolicyQuery())
->setViewer($viewer)
->setObject($application)
->execute();
}
return $this->policies;
}
private function renderCapability() {
$application = $this->getObject();
$capability = $this->getCapabilityName();
return $application->getCapabilityLabel($capability);
}
private function getCapabilityName() {
return $this->getMetadataValue(self::METADATA_ATTRIBUTE);
}
}
diff --git a/src/applications/meta/xactions/PhabricatorApplicationUninstallTransaction.php b/src/applications/meta/xactions/PhabricatorApplicationUninstallTransaction.php
index 12fbc8ebd4..b76e63b5c0 100644
--- a/src/applications/meta/xactions/PhabricatorApplicationUninstallTransaction.php
+++ b/src/applications/meta/xactions/PhabricatorApplicationUninstallTransaction.php
@@ -1,79 +1,83 @@
<?php
final class PhabricatorApplicationUninstallTransaction
extends PhabricatorApplicationTransactionType {
const TRANSACTIONTYPE = 'application.uninstall';
public function generateOldValue($object) {
$key = 'phabricator.uninstalled-applications';
$config_entry = PhabricatorConfigEntry::loadConfigEntry($key);
$list = $config_entry->getValue();
$uninstalled = PhabricatorEnv::getEnvConfig($key);
if (isset($uninstalled[get_class($object)])) {
return 'uninstalled';
} else {
return 'installed';
}
}
public function generateNewValue($object, $value) {
if ($value === 'uninstall') {
return 'uninstalled';
} else {
return 'installed';
}
}
public function applyExternalEffects($object, $value) {
$application = $object;
$user = $this->getActor();
$key = 'phabricator.uninstalled-applications';
$config_entry = PhabricatorConfigEntry::loadConfigEntry($key);
$list = $config_entry->getValue();
$uninstalled = PhabricatorEnv::getEnvConfig($key);
if (isset($uninstalled[get_class($application)])) {
unset($list[get_class($application)]);
} else {
$list[get_class($application)] = true;
}
$editor = $this->getEditor();
$content_source = $editor->getContentSource();
+
+ // Today, changing config requires "Administrator", but "Can Edit" on
+ // applications to let you uninstall them may be granted to any user.
PhabricatorConfigEditor::storeNewValue(
- $user,
+ PhabricatorUser::getOmnipotentUser(),
$config_entry,
$list,
- $content_source);
+ $content_source,
+ $user->getPHID());
}
public function getTitle() {
if ($this->getNewValue() === 'uninstalled') {
return pht(
'%s uninstalled this application.',
$this->renderAuthor());
} else {
return pht(
'%s installed this application.',
$this->renderAuthor());
}
}
public function getTitleForFeed() {
if ($this->getNewValue() === 'uninstalled') {
return pht(
'%s uninstalled %s.',
$this->renderAuthor(),
$this->renderObject());
} else {
return pht(
'%s installed %s.',
$this->renderAuthor(),
$this->renderObject());
}
}
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Apr 28, 10:02 AM (1 d, 16 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
107918
Default Alt Text
(35 KB)

Event Timeline