Select the types of activity you want to include in your feed.
@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
···55 * @task cookie Managing Cookies
66 * @task cluster Working With a Phabricator Cluster
77 */
88-final class AphrontRequest {
88+final class AphrontRequest extends Phobject {
991010 // NOTE: These magic request-type parameters are automatically included in
1111 // certain requests (e.g., by phabricator_form(), JX.Request,
···2727 private $user;
2828 private $applicationConfiguration;
2929 private $uriData;
3030+ private $cookiePrefix;
30313132 public function __construct($host, $path) {
3233 $this->host = $host;
+1-1
src/aphront/AphrontURIMapper.php
···11<?php
2233-final class AphrontURIMapper {
33+final class AphrontURIMapper extends Phobject {
4455 private $map;
66
···7171 'This Phabricator install is not configured with any enabled '.
7272 'authentication providers which can be used to log in. If you '.
7373 'have accidentally locked yourself out by disabling all providers, '.
7474- 'you can use `%s` to recover access to an administrative account.'.
7474+ 'you can use `%s` to recover access to an administrative account.',
7575 'phabricator/bin/auth recover <username>'));
7676 }
7777
···1010 * a Sunday -> Saturday list, whilest the profile view shows a more simple
1111 * seven day rolling list of events.
1212 */
1313-final class CalendarTimeUtil {
1313+final class CalendarTimeUtil extends Phobject {
14141515 public static function getCalendarEventEpochs(
1616 PhabricatorUser $user,
+1-1
src/applications/celerity/CelerityAPI.php
···44 * Indirection layer which provisions for a terrifying future where we need to
55 * build multiple resource responses per page.
66 */
77-final class CelerityAPI {
77+final class CelerityAPI extends Phobject {
8899 private static $response;
1010
+2-1
src/applications/celerity/CelerityResourceMap.php
···66 * not need to invoke it directly; instead, you call higher-level Celerity APIs
77 * and it uses the resource map to satisfy your requests.
88 */
99-final class CelerityResourceMap {
99+final class CelerityResourceMap extends Phobject {
10101111 private static $instances = array();
1212···1616 private $packageMap;
1717 private $nameMap;
1818 private $hashMap;
1919+ private $componentMap;
19202021 public function __construct(CelerityResources $resources) {
2122 $this->resources = $resources;
···11<?php
2233-final class CeleritySpriteGenerator {
33+final class CeleritySpriteGenerator extends Phobject {
4455 public function buildMenuSheet() {
66 $sprites = array();
···11<?php
2233-abstract class PhabricatorConfigOptionType {
33+abstract class PhabricatorConfigOptionType extends Phobject {
4455 public function validateOption(PhabricatorConfigOption $option, $value) {
66 return;
···11<?php
2233-final class ConpherenceTransactionRenderer {
33+final class ConpherenceTransactionRenderer extends Phobject {
4455 public static function renderTransactions(
66 PhabricatorUser $user,
···44 * Can't find a good place for this, so I'm putting it in the most notably
55 * wrong place.
66 */
77-final class DifferentialGetWorkingCopy {
77+final class DifferentialGetWorkingCopy extends Phobject {
8899 /**
1010 * Creates and/or cleans a workspace for the requested repo.
···11<?php
2233-abstract class DifferentialLandingStrategy {
33+abstract class DifferentialLandingStrategy extends Phobject {
4455 abstract public function processLandRequest(
66 AphrontRequest $request,
···11<?php
2233-final class DiffusionMercurialWireProtocol {
33+final class DiffusionMercurialWireProtocol extends Phobject {
4455 public static function getCommandArgs($command) {
66 // We need to enumerate all of the Mercurial wire commands because the
···44 * This protocol has a good spec here:
55 *
66 * http://svn.apache.org/repos/asf/subversion/trunk/subversion/libsvn_ra_svn/protocol
77- *
87 */
98final class DiffusionSubversionServeSSHWorkflow
109 extends DiffusionSubversionSSHWorkflow {
···11<?php
2233-abstract class DiffusionExternalSymbolsSource {
33+abstract class DiffusionExternalSymbolsSource extends Phobject {
4455 /**
66 * @return list of PhabricatorRepositorySymbol
···11<?php
2233-abstract class PhabricatorFactEngine {
33+abstract class PhabricatorFactEngine extends Phobject {
4455 final public static function loadAllEngines() {
66 $classes = id(new PhutilSymbolLoader())
···3636 "public, and a login is not required to view them! This is ".
3737 "intended for things like open source projects that want to ".
3838 "expose an activity feed on the project homepage.\n\n".
3939- "NOTE: You must also set `policy.allow-public` to true for this ".
4040- "setting to work properly.")),
3939+ "NOTE: You must also set `%s` to true for this ".
4040+ "setting to work properly.",
4141+ 'policy.allow-public')),
4142 $this->newOption('feed.http-hooks', 'list<string>', array())
4243 ->setLocked(true)
4344 ->setSummary(pht('POST notifications of feed events.'))
4445 ->setDescription(
4546 pht(
4646- "If you set this to a list of http URIs, when a feed story is ".
4747- "published a task will be created for each uri that posts the ".
4848- "story data to the uri. Daemons automagically retry failures 100 ".
4949- "times, waiting \$fail_count * 60s between each subsequent ".
5050- "failure. Be sure to keep the daemon console (/daemon/) open ".
4747+ "If you set this to a list of HTTP URIs, when a feed story is ".
4848+ "published a task will be created for each URI that posts the ".
4949+ "story data to the URI. Daemons automagically retry failures 100 ".
5050+ "times, waiting `\$fail_count * 60s` between each subsequent ".
5151+ "failure. Be sure to keep the daemon console (`%s`) open ".
5152 "while developing and testing your end points. You may need to".
5252- "restart your daemons to start sending http requests.\n\n".
5353- "NOTE: URIs are not validated, the URI must return http status ".
5454- "200 within 30 seconds, and no permission checks are performed.")),
5353+ "restart your daemons to start sending HTTP requests.\n\n".
5454+ "NOTE: URIs are not validated, the URI must return HTTP status ".
5555+ "200 within 30 seconds, and no permission checks are performed.",
5656+ '/daemon/')),
5557 );
5658 }
5759
···11<?php
2233-final class HeraldRepetitionPolicyConfig {
33+final class HeraldRepetitionPolicyConfig extends Phobject {
4455 const FIRST = 'first'; // only execute the first time (no repeating)
66 const EVERY = 'every'; // repeat every time
···11<?php
2233-abstract class HeraldCustomAction {
33+abstract class HeraldCustomAction extends Phobject {
4455 abstract public function appliesToAdapter(HeraldAdapter $adapter);
66
···11<?php
2233-abstract class PhabricatorTestDataGenerator {
33+abstract class PhabricatorTestDataGenerator extends Phobject {
4455 public function generate() {
66 return;
···11<?php
2233-abstract class ManiphestExcelFormat {
33+abstract class ManiphestExcelFormat extends Phobject {
4455 final public static function loadAllFormats() {
66 $classes = id(new PhutilSymbolLoader())
···11<?php
2233-abstract class PhabricatorMailImplementationAdapter {
33+abstract class PhabricatorMailImplementationAdapter extends Phobject {
4455 abstract public function setFrom($email, $name = '');
66 abstract public function addReplyTo($email, $name = '');
···11<?php
2233-final class PhabricatorMetaMTAEmailBodyParser {
33+final class PhabricatorMetaMTAEmailBodyParser extends Phobject {
4455 /**
66 * Mails can have bodies such as
···11+<?php
22+33+final class PhabricatorMetaMTAApplicationEmailTransactionQuery
44+ extends PhabricatorApplicationTransactionQuery {
55+66+ public function getTemplateApplicationTransaction() {
77+ return new PhabricatorMetaMTAApplicationEmailTransaction();
88+ }
99+1010+}
···11<?php
2233-final class PhabricatorOwnerPathQuery {
33+final class PhabricatorOwnerPathQuery extends Phobject {
4455 public static function loadAffectedPaths(
66 PhabricatorRepository $repository,
···99 return $key->loadAndValidateFromPHID(
1010 $phid,
1111 $viewer,
1212- PassphraseCredentialTypeSSHPrivateKey::PROVIDES_TYPE);
1212+ PassphraseSSHPrivateKeyCredentialType::PROVIDES_TYPE);
1313 }
14141515 public function getKeyfileEnvelope() {
1616 $credential = $this->requireCredential();
17171818- $file_type = PassphraseCredentialTypeSSHPrivateKeyFile::CREDENTIAL_TYPE;
1818+ $file_type = PassphraseSSHPrivateKeyFileCredentialType::CREDENTIAL_TYPE;
1919 if ($credential->getCredentialType() != $file_type) {
2020 // If the credential does not store a file, write the key text out to a
2121 // temporary file so we can pass it to `ssh`.
···118118 }
119119120120 public function canEstablishWebSessions() {
121121- if (!$this->isUserActivated()) {
122122- return false;
123123- }
124124-125121 if ($this->getIsMailingList()) {
126122 return false;
127123 }
···762758 // TODO: We might let the user switch which space they're "in" later on;
763759 // for now just use the global space if one exists.
764760765765- $spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($this);
761761+ // If the viewer has access to the default space, use that.
762762+ $spaces = PhabricatorSpacesNamespaceQuery::getViewerActiveSpaces($this);
766763 foreach ($spaces as $space) {
767764 if ($space->getIsDefaultNamespace()) {
768765 return $space->getPHID();
769766 }
767767+ }
768768+769769+ // Otherwise, use the space with the lowest ID that they have access to.
770770+ // This just tends to keep the default stable and predictable over time,
771771+ // so adding a new space won't change behavior for users.
772772+ if ($spaces) {
773773+ $spaces = msort($spaces, 'getID');
774774+ return head($spaces)->getPHID();
770775 }
771776772777 return null;
···11<?php
2233-abstract class PhabricatorPHIDType {
33+abstract class PhabricatorPHIDType extends Phobject {
4455 final public function getTypeConstant() {
66 $class = new ReflectionClass($this);
···158158 *
159159 * @return dict<string, PhabricatorPHIDType> Map of type constants to types.
160160 */
161161- public static function getAllTypes() {
161161+ final public static function getAllTypes() {
162162 static $types;
163163 if ($types === null) {
164164 $objects = id(new PhutilSymbolLoader())
···11<?php
2233-abstract class PhortuneCartImplementation {
33+abstract class PhortuneCartImplementation extends Phobject {
4455 /**
66 * Load implementations for a given set of carts.
···11<?php
2233-abstract class PhortuneProductImplementation {
33+abstract class PhortuneProductImplementation extends Phobject {
4455 abstract public function loadImplementationsForRefs(
66 PhabricatorUser $viewer,
···11<?php
2233-abstract class PhortuneSubscriptionImplementation {
33+abstract class PhortuneSubscriptionImplementation extends Phobject {
4455 abstract public function loadImplementationsForRefs(
66 PhabricatorUser $viewer,
···11<?php
2233-abstract class PhabricatorPolicyRule {
33+/**
44+ * @task objectpolicy Implementing Object Policies
55+ */
66+abstract class PhabricatorPolicyRule extends Phobject {
4758 const CONTROL_TYPE_TEXT = 'text';
69 const CONTROL_TYPE_SELECT = 'select';
···811 const CONTROL_TYPE_NONE = 'none';
9121013 abstract public function getRuleDescription();
1111- abstract public function applyRule(PhabricatorUser $viewer, $value);
1414+ abstract public function applyRule(
1515+ PhabricatorUser $viewer,
1616+ $value,
1717+ PhabricatorPolicyInterface $object);
12181313- public function willApplyRules(PhabricatorUser $viewer, array $values) {
1919+ public function willApplyRules(
2020+ PhabricatorUser $viewer,
2121+ array $values,
2222+ array $objects) {
1423 return;
1524 }
1625···20292130 public function getValueControlTemplate() {
2231 return null;
3232+ }
3333+3434+ /**
3535+ * Return `true` if this rule can be applied to the given object.
3636+ *
3737+ * Some policy rules may only operation on certain kinds of objects. For
3838+ * example, a "task author" rule
3939+ */
4040+ public function canApplyToObject(PhabricatorPolicyInterface $object) {
4141+ return true;
2342 }
24432544 protected function getDatasourceTemplate(
···7594 */
7695 public function ruleHasEffect($value) {
7796 return true;
9797+ }
9898+9999+100100+/* -( Transaction Hints )-------------------------------------------------- */
101101+102102+103103+ /**
104104+ * Tell policy rules about upcoming transaction effects.
105105+ *
106106+ * Before transaction effects are applied, we try to stop users from making
107107+ * edits which will lock them out of objects. We can't do this perfectly,
108108+ * since they can set a policy to "the moon is full" moments before it wanes,
109109+ * but we try to prevent as many mistakes as possible.
110110+ *
111111+ * Some policy rules depend on complex checks against object state which
112112+ * we can't set up ahead of time. For example, subscriptions require database
113113+ * writes.
114114+ *
115115+ * In cases like this, instead of doing writes, you can pass a hint about an
116116+ * object to a policy rule. The rule can then look for hints and use them in
117117+ * rendering a verdict about whether the user will be able to see the object
118118+ * or not after applying the policy change.
119119+ *
120120+ * @param PhabricatorPolicyInterface Object to pass a hint about.
121121+ * @param PhabricatorPolicyRule Rule to pass hint to.
122122+ * @param wild Hint.
123123+ * @return void
124124+ */
125125+ public static function passTransactionHintToRule(
126126+ PhabricatorPolicyInterface $object,
127127+ PhabricatorPolicyRule $rule,
128128+ $hint) {
129129+130130+ $cache = PhabricatorCaches::getRequestCache();
131131+ $cache->setKey(self::getObjectPolicyCacheKey($object, $rule), $hint);
132132+ }
133133+134134+ protected function getTransactionHint(
135135+ PhabricatorPolicyInterface $object) {
136136+137137+ $cache = PhabricatorCaches::getRequestCache();
138138+ return $cache->getKey(self::getObjectPolicyCacheKey($object, $this));
139139+ }
140140+141141+ private static function getObjectPolicyCacheKey(
142142+ PhabricatorPolicyInterface $object,
143143+ PhabricatorPolicyRule $rule) {
144144+ $hash = spl_object_hash($object);
145145+ $rule = get_class($rule);
146146+ return 'policycache.'.$hash.'.'.$rule;
147147+ }
148148+149149+150150+/* -( Implementing Object Policies )--------------------------------------- */
151151+152152+153153+ /**
154154+ * Return a unique string like "maniphest.author" to expose this rule as an
155155+ * object policy.
156156+ *
157157+ * Object policy rules, like "Task Author", are more advanced than basic
158158+ * policy rules (like "All Users") but not as powerful as custom rules.
159159+ *
160160+ * @return string Unique identifier for this rule.
161161+ * @task objectpolicy
162162+ */
163163+ public function getObjectPolicyKey() {
164164+ return null;
165165+ }
166166+167167+ public function getObjectPolicyFullKey() {
168168+ $key = $this->getObjectPolicyKey();
169169+170170+ if (!$key) {
171171+ throw new Exception(
172172+ pht(
173173+ 'This policy rule (of class "%s") does not have an associated '.
174174+ 'object policy key.',
175175+ get_class($this)));
176176+ }
177177+178178+ return PhabricatorPolicyQuery::OBJECT_POLICY_PREFIX.$key;
179179+ }
180180+181181+ public function getObjectPolicyName() {
182182+ throw new PhutilMethodNotImplementedException();
183183+ }
184184+185185+ public function getObjectPolicyShortName() {
186186+ return $this->getObjectPolicyName();
187187+ }
188188+189189+ public function getObjectPolicyIcon() {
190190+ return 'fa-cube';
191191+ }
192192+193193+ public function getPolicyExplanation() {
194194+ throw new PhutilMethodNotImplementedException();
78195 }
7919680197}
···1616 * button.)
1717 *
1818 */
1919-final class DifferentialReleephRequestFieldSpecification {
1919+final class DifferentialReleephRequestFieldSpecification extends Phobject {
20202121 // TODO: This class is essentially dead right now, see T2222.
2222
···11<?php
2233-abstract class ReleephFieldSelector {
33+abstract class ReleephFieldSelector extends Phobject {
4455 final public function __construct() {
66 // <empty>
···149149 if ($object instanceof PhabricatorSpacesInterface) {
150150 if (!empty($map['spacePHIDs'])) {
151151 $query->withSpacePHIDs($map['spacePHIDs']);
152152+ } else {
153153+ // If the user doesn't search for objects in specific spaces, we
154154+ // default to "all active spaces you have permission to view".
155155+ $query->withSpaceIsArchived(false);
152156 }
153157 }
154158
···55 * three capabilities: indexing, searching, and reconstruction (this can be
66 * stubbed out if an engine can't reasonably do it, it is used for debugging).
77 */
88-abstract class PhabricatorSearchEngine {
88+abstract class PhabricatorSearchEngine extends Phobject {
991010/* -( Engine Metadata )---------------------------------------------------- */
1111
···11+<?php
22+33+final class PhabricatorSubscriptionsSubscribersPolicyRule
44+ extends PhabricatorPolicyRule {
55+66+ private $subscribed = array();
77+ private $sourcePHIDs = array();
88+99+ public function getObjectPolicyKey() {
1010+ return 'subscriptions.subscribers';
1111+ }
1212+1313+ public function getObjectPolicyName() {
1414+ return pht('Subscribers');
1515+ }
1616+1717+ public function getPolicyExplanation() {
1818+ return pht('Subscribers can take this action.');
1919+ }
2020+2121+ public function getRuleDescription() {
2222+ return pht('subscribers');
2323+ }
2424+2525+ public function canApplyToObject(PhabricatorPolicyInterface $object) {
2626+ return ($object instanceof PhabricatorSubscribableInterface);
2727+ }
2828+2929+ public function willApplyRules(
3030+ PhabricatorUser $viewer,
3131+ array $values,
3232+ array $objects) {
3333+3434+ // We want to let the user see the object if they're a subscriber or
3535+ // a member of any project which is a subscriber. Additionally, because
3636+ // subscriber state is complex, we need to read hints passed from
3737+ // the TransactionEditor to predict policy state after transactions apply.
3838+3939+ $viewer_phid = $viewer->getPHID();
4040+ if (!$viewer_phid) {
4141+ return;
4242+ }
4343+4444+ if (empty($this->subscribed[$viewer_phid])) {
4545+ $this->subscribed[$viewer_phid] = array();
4646+ }
4747+4848+ // Load the project PHIDs the user is a member of.
4949+ if (!isset($this->sourcePHIDs[$viewer_phid])) {
5050+ $source_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
5151+ $viewer_phid,
5252+ PhabricatorProjectMemberOfProjectEdgeType::EDGECONST);
5353+ $source_phids[] = $viewer_phid;
5454+ $this->sourcePHIDs[$viewer_phid] = $source_phids;
5555+ }
5656+5757+ // Look for transaction hints.
5858+ foreach ($objects as $key => $object) {
5959+ $cache = $this->getTransactionHint($object);
6060+ if ($cache === null) {
6161+ // We don't have a hint for this object, so we'll deal with it below.
6262+ continue;
6363+ }
6464+6565+ // We have a hint, so use that as the source of truth.
6666+ unset($objects[$key]);
6767+6868+ foreach ($this->sourcePHIDs[$viewer_phid] as $source_phid) {
6969+ if (isset($cache[$source_phid])) {
7070+ $this->subscribed[$viewer_phid][$object->getPHID()] = true;
7171+ break;
7272+ }
7373+ }
7474+ }
7575+7676+ $phids = mpull($objects, 'getPHID');
7777+ if (!$phids) {
7878+ return;
7979+ }
8080+8181+ $edge_query = id(new PhabricatorEdgeQuery())
8282+ ->withSourcePHIDs($this->sourcePHIDs[$viewer_phid])
8383+ ->withEdgeTypes(
8484+ array(
8585+ PhabricatorSubscribedToObjectEdgeType::EDGECONST,
8686+ ))
8787+ ->withDestinationPHIDs($phids);
8888+8989+ $edge_query->execute();
9090+9191+ $subscribed = $edge_query->getDestinationPHIDs();
9292+ if (!$subscribed) {
9393+ return;
9494+ }
9595+9696+ $this->subscribed[$viewer_phid] += array_fill_keys($subscribed, true);
9797+ }
9898+9999+ public function applyRule(
100100+ PhabricatorUser $viewer,
101101+ $value,
102102+ PhabricatorPolicyInterface $object) {
103103+104104+ $viewer_phid = $viewer->getPHID();
105105+ if (!$viewer_phid) {
106106+ return false;
107107+ }
108108+109109+ if ($object->isAutomaticallySubscribed($viewer_phid)) {
110110+ return true;
111111+ }
112112+113113+ $subscribed = idx($this->subscribed, $viewer_phid);
114114+ return isset($subscribed[$object->getPHID()]);
115115+ }
116116+117117+ public function getValueControlType() {
118118+ return self::CONTROL_TYPE_NONE;
119119+ }
120120+121121+}
···11<?php
2233-abstract class PhabricatorSystemAction {
33+abstract class PhabricatorSystemAction extends Phobject {
4455 abstract public function getActionConstant();
66 abstract public function getScoreThreshold();
···310310 $space_phid = $default_space->getPHID();
311311 }
312312 }
313313+313314 return $space_phid;
314315 case PhabricatorTransactions::TYPE_EDGE:
315316 $edge_type = $xaction->getMetadataValue('edge:type');
···360361 case PhabricatorTransactions::TYPE_SPACE:
361362 $space_phid = $xaction->getNewValue();
362363 if (!strlen($space_phid)) {
363363- // If an install has no Spaces, we might end up with the empty string
364364- // here instead of a strict `null`. Just make this work like callers
365365- // might reasonably expect.
366366- return null;
364364+ // If an install has no Spaces or the Spaces controls are not visible
365365+ // to the viewer, we might end up with the empty string here instead
366366+ // of a strict `null`, because some controller just used `getStr()`
367367+ // to read the space PHID from the request.
368368+ // Just make this work like callers might reasonably expect so we
369369+ // don't need to handle this specially in every EditController.
370370+ return $this->getActor()->getDefaultSpacePHID();
367371 } else {
368372 return $space_phid;
369373 }
···840844 $object->save();
841845 } catch (AphrontDuplicateKeyQueryException $ex) {
842846 $object->killTransaction();
847847+848848+ // This callback has an opportunity to throw a better exception,
849849+ // so execution may end here.
850850+ $this->didCatchDuplicateKeyException($object, $xactions, $ex);
851851+843852 throw $ex;
844853 }
845854···933942 $object,
934943 $herald_xactions);
935944936936- $adapter = $this->getHeraldAdapter();
937937- $this->heraldEmailPHIDs = $adapter->getEmailPHIDs();
938938- $this->heraldForcedEmailPHIDs = $adapter->getForcedEmailPHIDs();
939939-940945 // Merge the new transactions into the transaction list: we want to
941946 // send email and publish feed stories about them, too.
942947 $xactions = array_merge($xactions, $herald_xactions);
943948 }
949949+950950+ // If Herald did not generate transactions, we may still need to handle
951951+ // "Send an Email" rules.
952952+ $adapter = $this->getHeraldAdapter();
953953+ $this->heraldEmailPHIDs = $adapter->getEmailPHIDs();
954954+ $this->heraldForcedEmailPHIDs = $adapter->getForcedEmailPHIDs();
944955 }
945956946957 $this->didApplyTransactions($xactions);
···10131024 ));
1014102510151026 return $xactions;
10271027+ }
10281028+10291029+ protected function didCatchDuplicateKeyException(
10301030+ PhabricatorLiskDAO $object,
10311031+ array $xactions,
10321032+ Exception $ex) {
10331033+ return;
10161034 }
1017103510181036 public function publishTransactions(
···20022020 $transaction_type) {
20032021 $errors = array();
2004202220052005- $all_spaces = PhabricatorSpacesNamespaceQuery::getAllSpaces();
20062006- $viewer_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces(
20072007- $this->getActor());
20232023+ $actor = $this->getActor();
20242024+20252025+ $has_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpacesExist($actor);
20262026+ $actor_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($actor);
20272027+ $active_spaces = PhabricatorSpacesNamespaceQuery::getViewerActiveSpaces(
20282028+ $actor);
20082029 foreach ($xactions as $xaction) {
20092030 $space_phid = $xaction->getNewValue();
2010203120112032 if ($space_phid === null) {
20122012- if (!$all_spaces) {
20332033+ if (!$has_spaces) {
20132034 // The install doesn't have any spaces, so this is fine.
20142035 continue;
20152036 }
···2026204720272048 // If the PHID isn't `null`, it needs to be a valid space that the
20282049 // viewer can see.
20292029- if (empty($viewer_spaces[$space_phid])) {
20502050+ if (empty($actor_spaces[$space_phid])) {
20302051 $errors[] = new PhabricatorApplicationTransactionValidationError(
20312052 $transaction_type,
20322053 pht('Invalid'),
···20342055 'You can not shift this object in the selected space, because '.
20352056 'the space does not exist or you do not have access to it.'),
20362057 $xaction);
20582058+ } else if (empty($active_spaces[$space_phid])) {
20592059+20602060+ // It's OK to edit objects in an archived space, so just move on if
20612061+ // we aren't adjusting the value.
20622062+ $old_space_phid = $this->getTransactionOldValue($object, $xaction);
20632063+ if ($space_phid == $old_space_phid) {
20642064+ continue;
20652065+ }
20662066+20672067+ $errors[] = new PhabricatorApplicationTransactionValidationError(
20682068+ $transaction_type,
20692069+ pht('Archived'),
20702070+ pht(
20712071+ 'You can not shift this object into the selected space, because '.
20722072+ 'the space is archived. Objects can not be created inside (or '.
20732073+ 'moved into) archived spaces.'),
20742074+ $xaction);
20372075 }
20382076 }
20392077···20452083 PhabricatorLiskDAO $object,
20462084 array $xactions) {
2047208520482048- return clone $object;
20862086+ $copy = clone $object;
20872087+20882088+ foreach ($xactions as $xaction) {
20892089+ switch ($xaction->getTransactionType()) {
20902090+ case PhabricatorTransactions::TYPE_SUBSCRIBERS:
20912091+ $clone_xaction = clone $xaction;
20922092+ $clone_xaction->setOldValue(array_values($this->subscribers));
20932093+ $clone_xaction->setNewValue(
20942094+ $this->getPHIDTransactionNewValue(
20952095+ $clone_xaction));
20962096+20972097+ PhabricatorPolicyRule::passTransactionHintToRule(
20982098+ $copy,
20992099+ new PhabricatorSubscriptionsSubscribersPolicyRule(),
21002100+ array_fuse($clone_xaction->getNewValue()));
21012101+21022102+ break;
21032103+ case PhabricatorTransactions::TYPE_SPACE:
21042104+ $space_phid = $this->getTransactionNewValue($object, $xaction);
21052105+ $copy->setSpacePHID($space_phid);
21062106+ break;
21072107+ }
21082108+ }
21092109+21102110+ return $copy;
20492111 }
2050211220512113 protected function validateAllTransactions(
···5656 final protected function receiveEmail(PhabricatorMetaMTAReceivedMail $mail) {
5757 $viewer = $this->getActor();
5858 $object = $this->getMailReceiver();
5959+ $app_email = $this->getApplicationEmail();
6060+6161+ $is_new = !$object->getID();
6262+6363+ // If this is a new object which implements the Spaces interface and was
6464+ // created by sending mail to an ApplicationEmail address, put the object
6565+ // in the same Space the address is in.
6666+ if ($is_new) {
6767+ if ($object instanceof PhabricatorSpacesInterface) {
6868+ if ($app_email) {
6969+ $space_phid = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID(
7070+ $app_email);
7171+ $object->setSpacePHID($space_phid);
7272+ }
7373+ }
7474+ }
59756076 $body_data = $mail->parseBody();
6177 $body = $body_data['body'];
···2222Phabricator's preprocessor provides some standard color variables. You can
2323reference these with `{$color}`. For example:
24242525+ lang=css
2526 span.critical {
2627 color: {$red};
2728 }
···7879Since many rules are specific to handheld devices, the `.device` class selects
7980either tablets or phones:
80818282+ lang=css
8183 .device {
8284 /* Phone or tablet (not desktop). */
8385 }
···35353636= Setup - Creating a Client =
37373838-# Visit https://phabricator.example.com/oauthserver/client/create/
3838+# Visit {nav Your Local Install > OAuth Server > Create Application}
3939# Fill out the form
4040# Profit
41414242= Obtaining an Authorization Code =
43434444-POST or GET https://phabricator.example.com/oauthserver/auth/ with the
4444+POST or GET `https://phabricator.example.com/oauthserver/auth/` with the
4545following parameters:
46464747- Required - **client_id** - the id of the newly registered client.
···76767777= Obtaining an Access Token =
78787979-POST or GET https://phabricator.example.com/oauthserver/token/
7979+POST or GET `https://phabricator.example.com/oauthserver/token/`
8080with the following parameters:
81818282- Required - **client_id** - the id of the client
···101101Simply include a query param with the key of "access_token" and the value
102102as the earlier obtained access token. For example:
103103104104-https://phabricator.example.com/api/user.whoami?access_token=ykc7ly7vtibj334oga4fnfbuvnwz4ocp
104104+```https://phabricator.example.com/api/user.whoami?access_token=ykc7ly7vtibj334oga4fnfbuvnwz4ocp```
105105106106If the token has expired or is otherwise invalid, the client will receive
107107an error indicating as such. In these cases, the client should re-initiate
+163-135
src/docs/flavor/php_pitfalls.diviner
···44This document discusses difficult traps and pitfalls in PHP, and how to avoid,
55work around, or at least understand them.
6677-= array_merge() in Incredibly Slow When Merging A List of Arrays =
77+= `array_merge()` in Incredibly Slow When Merging A List of Arrays =
8899If you merge a list of arrays like this:
10101111- COUNTEREXAMPLE
1111+ COUNTEREXAMPLE, lang=php
1212 $result = array();
1313 foreach ($list_of_lists as $one_list) {
1414 $result = array_merge($result, $one_list);
···2121In a libphutil environment, you can use @{function@libphutil:array_mergev}
2222instead.
23232424-= var_export() Hates Baby Animals =
2424+= `var_export()` Hates Baby Animals =
25252626-If you try to var_export() an object that contains recursive references, your
2626+If you try to `var_export()` an object that contains recursive references, your
2727program will terminate. You have no chance to intercept or react to this or
2828-otherwise stop it from happening. Avoid var_export() unless you are certain
2929-you have only simple data. You can use print_r() or var_dump() to display
2828+otherwise stop it from happening. Avoid `var_export()` unless you are certain
2929+you have only simple data. You can use `print_r()` or `var_dump()` to display
3030complex variables safely.
31313232-= isset(), empty() and Truthiness =
3232+= `isset()`, `empty()` and Truthiness =
33333434A value is "truthy" if it evaluates to true in an `if` clause:
35353636+ lang=php
3637 $value = something();
3738 if ($value) {
3839 // Value is truthy.
···59606061This is wrong because it prevents users from making the comment "0". //THIS
6162COMMENT IS TOTALLY AWESOME AND I MAKE IT ALL THE TIME SO YOU HAD BETTER NOT
6262-BREAK IT!!!// A better test is probably strlen().
6363+BREAK IT!!!// A better test is probably `strlen()`.
63646465In addition to truth tests with `if`, PHP has two special truthiness operators
6565-which look like functions but aren't: empty() and isset(). These operators help
6666-deal with undeclared variables.
6666+which look like functions but aren't: `empty()` and `isset()`. These operators
6767+help deal with undeclared variables.
67686869In PHP, there are two major cases where you get undeclared variables -- either
6970you directly use a variable without declaring it:
70717171- COUNTEREXAMPLE
7272+ COUNTEREXAMPLE, lang=php
7273 function f() {
7374 if ($not_declared) {
7475 // ...
···8485 }
8586 }
86878787-When you do either of these, PHP issues a warning. Avoid these warnings by using
8888-empty() and isset() to do tests that are safe to apply to undeclared variables.
8888+When you do either of these, PHP issues a warning. Avoid these warnings by
8989+using `empty()` and `isset()` to do tests that are safe to apply to undeclared
9090+variables.
89919090-empty() evaluates truthiness exactly opposite of if(). isset() returns true for
9191-everything except null. This is the truth table:
9292+`empty()` evaluates truthiness exactly opposite of `if()`. `isset()` returns
9393+`true` for everything except `null`. This is the truth table:
92949393- VALUE if() empty() isset()
9595+| Value | `if()` | `empty()` | `isset()` |
9696+|-------|--------|-----------|-----------|
9797+| `null` | `false` | `true` | `false` |
9898+| `0` | `false` | `true` | `true` |
9999+| `0.0` | `false` | `true` | `true` |
100100+| `"0"` | `false` | `true` | `true` |
101101+| `""` | `false` | `true` | `true` |
102102+| `false` | `false` | `true` | `true` |
103103+| `array()` | `false` | `true` | `true` |
104104+| Everything else | `true` | `false` | `true` |
941059595- null false true false
9696- 0 false true true
9797- 0.0 false true true
9898- "0" false true true
9999- "" false true true
100100- false false true true
101101- array() false true true
102102- EVERYTHING ELSE true false true
106106+The value of these operators is that they accept undeclared variables and do
107107+not issue a warning. Specifically, if you try to do this you get a warning:
103108104104-The value of these operators is that they accept undeclared variables and do not
105105-issue a warning. Specifically, if you try to do this you get a warning:
106106-107107- COUNTEREXAMPLE
108108- if ($not_previously_declared) { // PHP Notice: Undefined variable!
109109- // ...
110110- }
109109+```lang=php, COUNTEREXAMPLE
110110+if ($not_previously_declared) { // PHP Notice: Undefined variable!
111111+ // ...
112112+}
113113+```
111114112115But these are fine:
113116114114- if (empty($not_previously_declared)) { // No notice, returns true.
115115- // ...
116116- }
117117- if (isset($not_previously_declared)) { // No notice, returns false.
118118- // ...
119119- }
117117+```lang=php
118118+if (empty($not_previously_declared)) { // No notice, returns true.
119119+ // ...
120120+}
121121+if (isset($not_previously_declared)) { // No notice, returns false.
122122+ // ...
123123+}
124124+```
120125121121-So, isset() really means is_declared_and_is_set_to_something_other_than_null().
122122-empty() really means is_falsey_or_is_not_declared(). Thus:
126126+So, `isset()` really means
127127+`is_declared_and_is_set_to_something_other_than_null()`. `empty()` really means
128128+`is_falsey_or_is_not_declared()`. Thus:
123129124124- - If a variable is known to exist, test falsiness with if (!$v), not empty().
125125- In particular, test for empty arrays with if (!$array). There is no reason
126126- to ever use empty() on a declared variable.
127127- - When you use isset() on an array key, like isset($array['key']), it will
128128- evaluate to "false" if the key exists but has the value null! Test for index
129129- existence with array_key_exists().
130130+ - If a variable is known to exist, test falsiness with `if (!$v)`, not
131131+ `empty()`. In particular, test for empty arrays with `if (!$array)`. There
132132+ is no reason to ever use `empty()` on a declared variable.
133133+ - When you use `isset()` on an array key, like `isset($array['key'])`, it
134134+ will evaluate to "false" if the key exists but has the value `null`! Test
135135+ for index existence with `array_key_exists()`.
130136131131-Put another way, use isset() if you want to type "if ($value !== null)" but are
132132-testing something that may not be declared. Use empty() if you want to type
133133-"if (!$value)" but you are testing something that may not be declared.
137137+Put another way, use `isset()` if you want to type `if ($value !== null)` but
138138+are testing something that may not be declared. Use `empty()` if you want to
139139+type `if (!$value)` but you are testing something that may not be declared.
134140135141= usort(), uksort(), and uasort() are Slow =
136142137143This family of functions is often extremely slow for large datasets. You should
138144avoid them if at all possible. Instead, build an array which contains surrogate
139145keys that are naturally sortable with a function that uses native comparison
140140-(e.g., sort(), asort(), ksort(), or natcasesort()). Sort this array instead, and
141141-use it to reorder the original array.
146146+(e.g., `sort()`, `asort()`, `ksort()`, or `natcasesort()`). Sort this array
147147+instead, and use it to reorder the original array.
142148143149In a libphutil environment, you can often do this easily with
144150@{function@libphutil:isort} or @{function@libphutil:msort}.
145151146146-= array_intersect() and array_diff() are Also Slow =
152152+= `array_intersect()` and `array_diff()` are Also Slow =
147153148154These functions are much slower for even moderately large inputs than
149149-array_intersect_key() and array_diff_key(), because they can not make the
155155+`array_intersect_key()` and `array_diff_key()`, because they can not make the
150156assumption that their inputs are unique scalars as the `key` varieties can.
151157Strongly prefer the `key` varieties.
152158153153-= array_uintersect() and array_udiff() are Definitely Slow Too =
159159+= `array_uintersect()` and `array_udiff()` are Definitely Slow Too =
154160155161These functions have the problems of both the `usort()` family and the
156162`array_diff()` family. Avoid them.
157163158158-= foreach() Does Not Create Scope =
164164+= `foreach()` Does Not Create Scope =
159165160160-Variables survive outside of the scope of foreach(). More problematically,
161161-references survive outside of the scope of foreach(). This code mutates
166166+Variables survive outside of the scope of `foreach()`. More problematically,
167167+references survive outside of the scope of `foreach()`. This code mutates
162168`$array` because the reference leaks from the first loop to the second:
163169164164- COUNTEREXAMPLE
165165- $array = range(1, 3);
166166- echo implode(',', $array); // Outputs '1,2,3'
167167- foreach ($array as &$value) {}
168168- echo implode(',', $array); // Outputs '1,2,3'
169169- foreach ($array as $value) {}
170170- echo implode(',', $array); // Outputs '1,2,2'
170170+```lang=php, COUNTEREXAMPLE
171171+$array = range(1, 3);
172172+echo implode(',', $array); // Outputs '1,2,3'
173173+foreach ($array as &$value) {}
174174+echo implode(',', $array); // Outputs '1,2,3'
175175+foreach ($array as $value) {}
176176+echo implode(',', $array); // Outputs '1,2,2'
177177+```
171178172179The easiest way to avoid this is to avoid using foreach-by-reference. If you do
173180use it, unset the reference after the loop:
174181175175- foreach ($array as &$value) {
176176- // ...
177177- }
178178- unset($value);
179179-180180-= unserialize() is Incredibly Slow on Large Datasets =
182182+```lang=php
183183+foreach ($array as &$value) {
184184+ // ...
185185+}
186186+unset($value);
187187+```
181188182182-The performance of unserialize() is nonlinear in the number of zvals you
183183-unserialize, roughly O(N^2).
189189+= `unserialize()` is Incredibly Slow on Large Datasets =
184190185185- zvals approximate time
186186- 10000 5ms
187187- 100000 85ms
188188- 1000000 8,000ms
189189- 10000000 72 billion years
191191+The performance of `unserialize()` is nonlinear in the number of zvals you
192192+unserialize, roughly `O(N^2)`.
190193194194+| zvals | Approximate time |
195195+|-------|------------------|
196196+| 10000 |5ms |
197197+| 100000 | 85ms |
198198+| 1000000 | 8,000ms |
199199+| 10000000 | 72 billion years |
191200192192-= call_user_func() Breaks References =
201201+= `call_user_func()` Breaks References =
193202194194-If you use call_use_func() to invoke a function which takes parameters by
203203+If you use `call_use_func()` to invoke a function which takes parameters by
195204reference, the variables you pass in will have their references broken and will
196205emerge unmodified. That is, if you have a function that takes references:
197206198198- function add_one(&$v) {
199199- $v++;
200200- }
207207+```lang=php
208208+function add_one(&$v) {
209209+ $v++;
210210+}
211211+```
201212202202-...and you call it with call_user_func():
213213+...and you call it with `call_user_func()`:
203214204204- COUNTEREXAMPLE
205205- $x = 41;
206206- call_user_func('add_one', $x);
215215+```lang=php, COUNTEREXAMPLE
216216+$x = 41;
217217+call_user_func('add_one', $x);
218218+```
207219208208-...`$x` will not be modified. The solution is to use call_user_func_array()
220220+...`$x` will not be modified. The solution is to use `call_user_func_array()`
209221and wrap the reference in an array:
210222211211- $x = 41;
212212- call_user_func_array(
213213- 'add_one',
214214- array(&$x)); // Note '&$x'!
223223+```lang=php
224224+$x = 41;
225225+call_user_func_array(
226226+ 'add_one',
227227+ array(&$x)); // Note '&$x'!
228228+```
215229216230This will work as expected.
217231218218-= You Can't Throw From __toString() =
232232+= You Can't Throw From `__toString()` =
219233220220-If you throw from __toString(), your program will terminate uselessly and you
234234+If you throw from `__toString()`, your program will terminate uselessly and you
221235won't get the exception.
222236223237= An Object Can Have Any Scalar as a Property =
224238225239Object properties are not limited to legal variable names:
226240227227- $property = '!@#$%^&*()';
228228- $obj->$property = 'zebra';
229229- echo $obj->$property; // Outputs 'zebra'.
241241+```lang=php
242242+$property = '!@#$%^&*()';
243243+$obj->$property = 'zebra';
244244+echo $obj->$property; // Outputs 'zebra'.
245245+```
230246231247So, don't make assumptions about property names.
232248233233-= There is an (object) Cast =
249249+= There is an `(object)` Cast =
234250235251You can cast a dictionary into an object.
236252237237- $obj = (object)array('flavor' => 'coconut');
238238- echo $obj->flavor; // Outputs 'coconut'.
239239- echo get_class($obj); // Outputs 'stdClass'.
253253+```lang=php
254254+$obj = (object)array('flavor' => 'coconut');
255255+echo $obj->flavor; // Outputs 'coconut'.
256256+echo get_class($obj); // Outputs 'stdClass'.
257257+```
240258241259This is occasionally useful, mostly to force an object to become a Javascript
242242-dictionary (vs a list) when passed to json_encode().
260260+dictionary (vs a list) when passed to `json_encode()`.
243261244244-= Invoking "new" With an Argument Vector is Really Hard =
262262+= Invoking `new` With an Argument Vector is Really Hard =
245263246246-If you have some `$class_name` and some `$argv` of constructor
247247-arguments and you want to do this:
264264+If you have some `$class_name` and some `$argv` of constructor arguments
265265+and you want to do this:
248266249249- new $class_name($argv[0], $argv[1], ...);
267267+```lang=php
268268+new $class_name($argv[0], $argv[1], ...);
269269+```
250270251271...you'll probably invent a very interesting, very novel solution that is very
252272wrong. In a libphutil environment, solve this problem with
253253-@{function@libphutil:newv}. Elsewhere, copy newv()'s implementation.
273273+@{function@libphutil:newv}. Elsewhere, copy `newv()`'s implementation.
254274255275= Equality is not Transitive =
256276257277This isn't terribly surprising since equality isn't transitive in a lot of
258258-languages, but the == operator is not transitive:
278278+languages, but the `==` operator is not transitive:
259279260260- $a = ''; $b = 0; $c = '0a';
261261- $a == $b; // true
262262- $b == $c; // true
263263- $c == $a; // false!
280280+```lang=php
281281+$a = ''; $b = 0; $c = '0a';
282282+$a == $b; // true
283283+$b == $c; // true
284284+$c == $a; // false!
285285+```
264286265287When either operand is an integer, the other operand is cast to an integer
266266-before comparison. Avoid this and similar pitfalls by using the === operator,
288288+before comparison. Avoid this and similar pitfalls by using the `===` operator,
267289which is transitive.
268290269291= All 676 Letters in the Alphabet =
270292271293This doesn't do what you'd expect it to do in C:
272294273273- for ($c = 'a'; $c <= 'z'; $c++) {
274274- // ...
275275- }
295295+```lang=php
296296+for ($c = 'a'; $c <= 'z'; $c++) {
297297+ // ...
298298+}
299299+```
276300277277-This is because the successor to 'z' is 'aa', which is "less than" 'z'. The
278278-loop will run for ~700 iterations until it reaches 'zz' and terminates. That is,
279279-`$c` will take on these values:
301301+This is because the successor to `z` is `aa`, which is "less than" `z`.
302302+The loop will run for ~700 iterations until it reaches `zz` and terminates.
303303+That is, `$c` will take on these values:
280304281281- a
282282- b
283283- ...
284284- y
285285- z
286286- aa // loop continues because 'aa' <= 'z'
287287- ab
288288- ...
289289- mf
290290- mg
291291- ...
292292- zw
293293- zx
294294- zy
295295- zz // loop now terminates because 'zz' > 'z'
305305+```
306306+a
307307+b
308308+...
309309+y
310310+z
311311+aa // loop continues because 'aa' <= 'z'
312312+ab
313313+...
314314+mf
315315+mg
316316+...
317317+zw
318318+zx
319319+zy
320320+zz // loop now terminates because 'zz' > 'z'
321321+```
296322297323Instead, use this loop:
298324299299- foreach (range('a', 'z') as $c) {
300300- // ...
301301- }
325325+```lang=php
326326+foreach (range('a', 'z') as $c) {
327327+ // ...
328328+}
329329+```
+169
src/docs/user/userguide/spaces.diviner
···11+@title Spaces User Guide
22+@group userguide
33+44+Guide to the Spaces application.
55+66+Overview
77+========
88+99+IMPORTANT: Spaces is a prototype application.
1010+1111+The Spaces application makes it easier to manage large groups of objects which
1212+share the same access policy. For example:
1313+1414+ - An organization might make a Space for a project in order to satisfy a
1515+ contractual obligation to limit access, even internally.
1616+ - An open source organization might make a Space for work related to
1717+ internal governance, to separate private and public discussions.
1818+ - A contracting company might make Spaces for clients, to separate them from
1919+ one another.
2020+ - A company might create a Space for consultants, to give them limited
2121+ access to only the resources they need to do their work.
2222+ - An ambitious manager might create a Space to hide her team's work from her
2323+ enemies at the company, that she might use the element of surprise to later
2424+ expand her domain.
2525+2626+Phabricator's access control policies are generally powerful enough to handle
2727+these use cases on their own, but applying the same policy to a large group
2828+of objects requires a lot of effort and is error-prone.
2929+3030+Spaces build on top of policies and make it easier and more reliable to
3131+configure, review, and manage groups of objects with similar policies.
3232+3333+3434+Creating Spaces
3535+=================
3636+3737+Spaces are optional, and are inactive by default. You don't need to configure
3838+them if you don't plan to use them. You can always set them up later.
3939+4040+To activate Spaces, you need to create at least two spaces. Create spaces from
4141+the web UI, by navigating to {nav Spaces > Create Space}. By default, only
4242+administrators can create new Spaces, but you can configure this in the
4343+{nav Applications} application.
4444+4545+The first Space you create will be a special "default" Space, and all existing
4646+objects will be shifted into this space as soon as you create it. Spaces you
4747+create later will be normal spaces, and begin with no objects inside them.
4848+4949+Create the first space (you may want to name it something like "Default" or
5050+"Global" or "Public", depending on the nature of your organization), then
5151+create a second Space. Usually, the second space will be something like
5252+"Secret Plans" and have a more restrictive "Visible To" policy.
5353+5454+5555+Using Spaces
5656+============
5757+5858+Once you've created at least two spaces, you can begin using them.
5959+6060+Application UIs will change for users who can see at least two Spaces, opening
6161+up new controls which let them work with spaces. They will now be able to
6262+choose which space to create new objects into, be able to move objects between
6363+spaces, and be able to search for objects in a specific space or set of spaces.
6464+6565+In list and detail views, objects will show which space they're in if they're
6666+in a non-default space.
6767+6868+Users with access to only one space won't see these controls, even if many
6969+spaces exist. This simplifies the UI for users with limited access.
7070+7171+7272+Space Policies
7373+==============
7474+7575+Briefly, Spaces affect policies like this:
7676+7777+ - Spaces apply their view policy to all objects inside the space.
7878+ - Space policies are absolute, and stronger than all other policies. A
7979+ user who can not see a Space can **never** see objects inside the space.
8080+ - Normal policies are still checked: spaces can only reduce access.
8181+8282+When you create a Space, you choose a view policy for that space by using the
8383+**Visible To** control. This policy controls both who can see the space, and
8484+who can see objects inside the space.
8585+8686+Spaces apply their view policy to all objects inside the space: if you can't
8787+see a space, you can never see objects inside it. This policy check is absolute
8888+and stronger than all other policy rules, including policy exceptions.
8989+9090+For example, a user can never see a task in a space they can't see, even if
9191+they are an admin and the author and owner of the task, and subscribed to the
9292+task and the view and edit policies are set to "All Users", and they created
9393+the Space originally and the moon is full and they are pure of heart and
9494+possessed of the noblest purpose. Spaces are impenetrable.
9595+9696+Even if a user satisfies the view policy for a space, they must still pass the
9797+view policy on the object: the space check is a new check in addition to any
9898+check on the object, and can only limit access.
9999+100100+The edit policy for a space only affects the Space itself, and is not applied
101101+to objects inside the space.
102102+103103+104104+Archiving Spaces
105105+================
106106+107107+If you no longer need a Space, you can archive it by choosing
108108+{nav Archive Space} from the detail view. This hides the space and all the
109109+objects in it without deleting any data.
110110+111111+New objects can't be created into archived spaces, and existing objects can't
112112+be shifted into archived spaces. The UI won't give you options to choose
113113+these spaces when creating or editing objects.
114114+115115+Additionally, objects (like tasks) in archived spaces won't be shown in most
116116+search result lists by default. If you need to find objects in an archived
117117+space, use the `Spaces` constraint to specifically search for objects in that
118118+space.
119119+120120+You can reactivate a space later by choosing {nav Activate Space}.
121121+122122+123123+Application Email
124124+=================
125125+126126+After activating Spaces, you can choose a Space when configuring inbound email
127127+addresses in {nav Applications}.
128128+129129+Spaces affect policies for application email just like they do for other
130130+objects: to see or use the address, you must be able to see the space which
131131+contains it.
132132+133133+Objects created from inbound email will be created in the Space the email is
134134+associated with.
135135+136136+137137+Limitations and Caveats
138138+=======================
139139+140140+Some information is shared between spaces, so they do not completely isolate
141141+users from other activity on the install. This section discusses limitations
142142+of the isolation model. Most of these limitations are intrinsic to the policy
143143+model Phabricator uses.
144144+145145+**Shared IDs**: Spaces do not have unique object IDs: there is only one `T1`,
146146+not a separate one in each space. It can be moved between spaces, but `T1`
147147+always refers to the same object. In most cases, this makes working with
148148+spaces simpler and easier.
149149+150150+However, because IDs are shared, users in any space can look at object IDs to
151151+determine how many objects exist in other spaces, even if they can't see those
152152+objects. If a user creates a new task and sees that it is `T5000`, they can
153153+know that there are 4,999 other tasks they don't have permission to see.
154154+155155+**Globally Unique Values**: Some values (like usernames, email addresses,
156156+project hashtags, repository callsigns, and application emails) must be
157157+globally unique.
158158+159159+As with normal policies, users may be able to determine that a `#yolo` project
160160+exists, even if they can't see it: they can try to create a project using the
161161+`#yolo` hashtag, and will receive an error if it is a duplicate.
162162+163163+**User Accounts**: Spaces do not apply to users, and can not hide the existence
164164+of user accounts.
165165+166166+For example, if you are a contracting company and have Coke and Pepsi as
167167+clients, the CEO of Coke and the CEO of Pepsi will each be able to see that the
168168+other has an account on the install, even if all the work you are doing for
169169+them is separated into "Coke" and "Pepsi" spaces.
···88 * Generally, you should not use this class directly. It is used by
99 * @{class:PhabricatorCustomField} to manage field storage on objects.
1010 */
1111-final class PhabricatorCustomFieldAttachment {
1111+final class PhabricatorCustomFieldAttachment extends Phobject {
12121313 private $lists = array();
1414
···33/**
44 * Defines the api for protocol adapters for @{class:PhabricatorBot}
55 */
66-abstract class PhabricatorProtocolAdapter {
66+abstract class PhabricatorProtocolAdapter extends Phobject {
7788 private $config;
99
···44 * Responds to IRC messages. You plug a bunch of these into a
55 * @{class:PhabricatorBot} to give it special behavior.
66 */
77-abstract class PhabricatorBotHandler {
77+abstract class PhabricatorBotHandler extends Phobject {
8899 private $bot;
1010
···44 * Represents something which can be the target of messages, like a user or
55 * channel.
66 */
77-abstract class PhabricatorBotTarget {
77+abstract class PhabricatorBotTarget extends Phobject {
8899 private $name;
1010
···11<?php
2233-final class PhabricatorEventEngine {
33+final class PhabricatorEventEngine extends Phobject {
4455 public static function initialize() {
66 // NOTE: If any of this fails, we just log it and move on. It's important
+1-1
src/infrastructure/javelin/Javelin.php
···11<?php
2233-final class Javelin {
33+final class Javelin extends Phobject {
4455 public static function initBehavior(
66 $behavior,
···2020 public function getInfoDescription() {
2121 return pht(
2222 'This linter is intended for use with the Javelin JS library and '.
2323- 'extensions. Use `javelinsymbols` to run Javelin rules on Javascript '.
2424- 'source files.');
2323+ 'extensions. Use `%s` to run Javelin rules on Javascript source files.',
2424+ 'javelinsymbols');
2525 }
26262727 private function getBinaryPath() {
+1-1
src/infrastructure/log/PhabricatorAccessLog.php
···11<?php
2233-final class PhabricatorAccessLog {
33+final class PhabricatorAccessLog extends Phobject {
4455 private static $log;
66
···1212 * This is less efficient than batching rendering, but appropriate for small
1313 * amounts of one-off text in form instructions.
1414 */
1515-final class PhabricatorMarkupOneOff implements PhabricatorMarkupInterface {
1515+final class PhabricatorMarkupOneOff
1616+ extends Phobject
1717+ implements PhabricatorMarkupInterface {
16181719 private $content;
1820 private $preserveLinebreaks;
···2233final class PhabricatorYoutubeRemarkupRule extends PhutilRemarkupRule {
4455+ private $uri;
66+57 public function getPriority() {
68 return 350.0;
79 }
+1-1
src/infrastructure/query/PhabricatorQuery.php
···33/**
44 * @task format Formatting Query Clauses
55 */
66-abstract class PhabricatorQuery {
66+abstract class PhabricatorQuery extends Phobject {
778899 abstract public function execute();
···338338 }
339339340340 protected function didRejectResult(PhabricatorPolicyInterface $object) {
341341+ // Some objects (like commits) may be rejected because related objects
342342+ // (like repositories) can not be loaded. In some cases, we may need these
343343+ // related objects to determine the object policy, so it's expected that
344344+ // we may occasionally be unable to determine the policy.
345345+346346+ try {
347347+ $policy = $object->getPolicy(PhabricatorPolicyCapability::CAN_VIEW);
348348+ } catch (Exception $ex) {
349349+ $policy = null;
350350+ }
351351+352352+ // Mark this object as filtered so handles can render "Restricted" instead
353353+ // of "Unknown".
354354+ $phid = $object->getPHID();
355355+ $this->addPolicyFilteredPHIDs(array($phid => $phid));
356356+341357 $this->getPolicyFilter()->rejectObject(
342358 $object,
343343- $object->getPolicy(PhabricatorPolicyCapability::CAN_VIEW),
359359+ $policy,
344360 PhabricatorPolicyCapability::CAN_VIEW);
345361 }
346362
···11<?php
2233-abstract class PhabricatorLiskSerializer {
33+abstract class PhabricatorLiskSerializer extends Phobject {
4455 abstract public function willReadValue($value);
66 abstract public function willWriteValue($value);
···11<?php
2233-abstract class PhabricatorSQLPatchList {
33+abstract class PhabricatorSQLPatchList extends Phobject {
4455 abstract public function getNamespace();
66 abstract public function getPatches();
···33/**
44 * Used by unit tests to build storage fixtures.
55 */
66-final class PhabricatorStorageFixtureScopeGuard {
66+final class PhabricatorStorageFixtureScopeGuard extends Phobject {
7788 private $name;
99
···222222 case 'taskstatus':
223223 case 'legaldocuments':
224224 case 'applicationemail':
225225+ case 'space':
225226 var tokenizer = this._newTokenizer(type);
226227 input = tokenizer[0];
227228 get_fn = tokenizer[1];
···101101 * Get the workflow URI to create or edit a policy with a given PHID.
102102 */
103103 var get_custom_uri = function(phid, capability) {
104104- var uri = '/policy/edit/';
104104+ var uri = config.editURI;
105105 if (phid != config.customPlaceholder) {
106106 uri += phid + '/';
107107 }