2012-10-31 09:57:46 -07:00
|
|
|
<?php
|
|
|
|
|
|
|
|
final class PhabricatorFileQuery
|
|
|
|
extends PhabricatorCursorPagedPolicyAwareQuery {
|
|
|
|
|
|
|
|
private $ids;
|
|
|
|
private $phids;
|
2012-12-16 16:33:02 -08:00
|
|
|
private $authorPHIDs;
|
2013-03-22 04:59:50 -07:00
|
|
|
private $explicitUploads;
|
Provide "builtin" files and use them to fix Pholio when files are deleted
Summary:
Fixes T3132. Currently, if a user deletes a file which is present in a mock, that mock throws an exception when loading. If the file is also the cover photo, the mock list throws an exception as well.
In other applications, we can sometimes deal with this (a sub-object vanishing) by implicitly hiding the parent object (for example, we can just vanish feed stories about objects which no longer exist). We can also sometimes deal with it by preventing sub-objects from being directly deleted.
However, neither approach is reasonable in this case.
If we vanish the whole mock, we'll lose all the comments and it will generally be weird. Vanishing a mock is a big deal compared to vanishing a feed story. We'll also need to load more data on the list view to prevent showing a mock on the list view and then realizing we need to vanish it on the detail view (because all of its images have been deleted).
We permit total deletion of files to allow users to recover from accidentally uploading sensitive files (which has happened a few times), and I'm hesitant to remove this capability because I think it serves a real need, so we can't prevent sub-objects from being deleted.
So we're left in a relatively unique situation. To solve this, I've added a "builtin" mechanism, which allows us to expose some resource we ship with as a PhabricatorFile. Then we just swap it out in place of the original file and proceed forward normally, as though nothing happened. The user sees a placeholder image instead of the original, but everything else works reasonably and this seems like a fairly acceptable outcome.
I believe we can use this mechanism to simplify some other code too, like default profile pictures.
Test Plan: Deleted a Pholio mock cover image's file. Implemented change, saw functional Pholio again with beautiful life-affirming "?" art replacing soul-shattering exception.
Reviewers: btrahan, chad
Reviewed By: chad
CC: aran
Maniphest Tasks: T3132
Differential Revision: https://secure.phabricator.com/D5870
2013-05-08 18:12:52 -07:00
|
|
|
private $transforms;
|
2013-05-31 10:51:05 -07:00
|
|
|
private $dateCreatedAfter;
|
|
|
|
private $dateCreatedBefore;
|
Provide `phragment.getstate` and `phragment.getpatch` Conduit methods
Summary:
This provides a `phragment.getstate` and a `phragment.getpatch` Conduit method.
`phragment.getstate` - This returns the current state of the fragment and all of it's children.
`phragment.getpatch` - This accepts a base path and a mapping of paths to hashes. The mapping is for the caller to specify the current state of the files it has. This returns a list of patches that the caller needs to apply to it's files to get to the latest version.
Test Plan:
Ran the following script in a folder which had content matching a fragment and it's children:
```
#!/bin/bash
STATE=""
for i in $(find ./ -type f); do
HASH=$(cat $i | sha1sum | awk '{ print $1 }')
BASE=${i:2}
STATE="$STATE,\"$BASE\":\"$HASH\""
done
STATE=${STATE:1}
STATE="{$STATE}"
echo '{"path":"tychaia3.zip","state":'$STATE'}' | arc --conduit-uri=http://phabricator.local/ call-conduit phragment.getpatch
```
and I got:
```
{"error":null,"errorMessage":null,"response":[]}
```
I updated one of the child fragments with a new file and ran the script again (patch has been omitted due to it's size):
```
{"error":null,"errorMessage":null,"response":[{"path":"Content\/TitleFont.xnb","hash_old":"4a927d7b90582e50cdd330de9f4b59b0cc5eb5c7","hash_new":"25867504642a3a403102274c68fbb9b430c1980f","patch":"..."}]}
```
Reviewers: epriestley, #blessed_reviewers
Reviewed By: epriestley
CC: Korvin, epriestley, aran, staticshock
Maniphest Tasks: T4205
Differential Revision: https://secure.phabricator.com/D7739
2013-12-11 11:19:23 +11:00
|
|
|
private $contentHashes;
|
2015-03-14 08:28:46 -07:00
|
|
|
private $minLength;
|
|
|
|
private $maxLength;
|
|
|
|
private $names;
|
|
|
|
private $isPartial;
|
2012-10-31 09:57:46 -07:00
|
|
|
|
|
|
|
public function withIDs(array $ids) {
|
|
|
|
$this->ids = $ids;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function withPHIDs(array $phids) {
|
|
|
|
$this->phids = $phids;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2012-12-16 16:33:02 -08:00
|
|
|
public function withAuthorPHIDs(array $phids) {
|
|
|
|
$this->authorPHIDs = $phids;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2013-05-31 10:51:05 -07:00
|
|
|
public function withDateCreatedBefore($date_created_before) {
|
|
|
|
$this->dateCreatedBefore = $date_created_before;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function withDateCreatedAfter($date_created_after) {
|
|
|
|
$this->dateCreatedAfter = $date_created_after;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
Provide `phragment.getstate` and `phragment.getpatch` Conduit methods
Summary:
This provides a `phragment.getstate` and a `phragment.getpatch` Conduit method.
`phragment.getstate` - This returns the current state of the fragment and all of it's children.
`phragment.getpatch` - This accepts a base path and a mapping of paths to hashes. The mapping is for the caller to specify the current state of the files it has. This returns a list of patches that the caller needs to apply to it's files to get to the latest version.
Test Plan:
Ran the following script in a folder which had content matching a fragment and it's children:
```
#!/bin/bash
STATE=""
for i in $(find ./ -type f); do
HASH=$(cat $i | sha1sum | awk '{ print $1 }')
BASE=${i:2}
STATE="$STATE,\"$BASE\":\"$HASH\""
done
STATE=${STATE:1}
STATE="{$STATE}"
echo '{"path":"tychaia3.zip","state":'$STATE'}' | arc --conduit-uri=http://phabricator.local/ call-conduit phragment.getpatch
```
and I got:
```
{"error":null,"errorMessage":null,"response":[]}
```
I updated one of the child fragments with a new file and ran the script again (patch has been omitted due to it's size):
```
{"error":null,"errorMessage":null,"response":[{"path":"Content\/TitleFont.xnb","hash_old":"4a927d7b90582e50cdd330de9f4b59b0cc5eb5c7","hash_new":"25867504642a3a403102274c68fbb9b430c1980f","patch":"..."}]}
```
Reviewers: epriestley, #blessed_reviewers
Reviewed By: epriestley
CC: Korvin, epriestley, aran, staticshock
Maniphest Tasks: T4205
Differential Revision: https://secure.phabricator.com/D7739
2013-12-11 11:19:23 +11:00
|
|
|
public function withContentHashes(array $content_hashes) {
|
|
|
|
$this->contentHashes = $content_hashes;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
When deleting a file, delete all transformations of the file
Summary:
Fixes T3143. When a user deletes a file, delete all transforms of the file too. In particular, this means that deleting an image deletes all the thumbnails of the image.
In most cases, this aligns with user expectations. The only sort of weird case I can come up with is that memes are transformations of the source macro image, so deleting grumpycat will delete all the hilarious grumpycat memes. This seems not-too-unreasonable, though, and desirable if someone accidentally uploads an inappropriate image which is promptly turned into a meme.
Test Plan:
Added a unit test which covers both inbound and outbound transformations.
Uploaded a file and deleted it, verified its thumbnail was also deleted.
Reviewers: chad, btrahan, joseph.kampf
Reviewed By: btrahan
CC: aran, joseph.kampf
Maniphest Tasks: T3143
Differential Revision: https://secure.phabricator.com/D5879
2013-05-09 16:08:35 -07:00
|
|
|
/**
|
|
|
|
* Select files which are transformations of some other file. For example,
|
|
|
|
* you can use this query to find previously generated thumbnails of an image
|
|
|
|
* file.
|
|
|
|
*
|
|
|
|
* As a parameter, provide a list of transformation specifications. Each
|
|
|
|
* specification is a dictionary with the keys `originalPHID` and `transform`.
|
|
|
|
* The `originalPHID` is the PHID of the original file (the file which was
|
|
|
|
* transformed) and the `transform` is the name of the transform to query
|
|
|
|
* for. If you pass `true` as the `transform`, all transformations of the
|
|
|
|
* file will be selected.
|
|
|
|
*
|
|
|
|
* For example:
|
|
|
|
*
|
|
|
|
* array(
|
|
|
|
* array(
|
|
|
|
* 'originalPHID' => 'PHID-FILE-aaaa',
|
|
|
|
* 'transform' => 'sepia',
|
|
|
|
* ),
|
|
|
|
* array(
|
|
|
|
* 'originalPHID' => 'PHID-FILE-bbbb',
|
|
|
|
* 'transform' => true,
|
|
|
|
* ),
|
|
|
|
* )
|
|
|
|
*
|
|
|
|
* This selects the `"sepia"` transformation of the file with PHID
|
|
|
|
* `PHID-FILE-aaaa` and all transformations of the file with PHID
|
|
|
|
* `PHID-FILE-bbbb`.
|
|
|
|
*
|
|
|
|
* @param list<dict> List of transform specifications, described above.
|
|
|
|
* @return this
|
|
|
|
*/
|
Provide "builtin" files and use them to fix Pholio when files are deleted
Summary:
Fixes T3132. Currently, if a user deletes a file which is present in a mock, that mock throws an exception when loading. If the file is also the cover photo, the mock list throws an exception as well.
In other applications, we can sometimes deal with this (a sub-object vanishing) by implicitly hiding the parent object (for example, we can just vanish feed stories about objects which no longer exist). We can also sometimes deal with it by preventing sub-objects from being directly deleted.
However, neither approach is reasonable in this case.
If we vanish the whole mock, we'll lose all the comments and it will generally be weird. Vanishing a mock is a big deal compared to vanishing a feed story. We'll also need to load more data on the list view to prevent showing a mock on the list view and then realizing we need to vanish it on the detail view (because all of its images have been deleted).
We permit total deletion of files to allow users to recover from accidentally uploading sensitive files (which has happened a few times), and I'm hesitant to remove this capability because I think it serves a real need, so we can't prevent sub-objects from being deleted.
So we're left in a relatively unique situation. To solve this, I've added a "builtin" mechanism, which allows us to expose some resource we ship with as a PhabricatorFile. Then we just swap it out in place of the original file and proceed forward normally, as though nothing happened. The user sees a placeholder image instead of the original, but everything else works reasonably and this seems like a fairly acceptable outcome.
I believe we can use this mechanism to simplify some other code too, like default profile pictures.
Test Plan: Deleted a Pholio mock cover image's file. Implemented change, saw functional Pholio again with beautiful life-affirming "?" art replacing soul-shattering exception.
Reviewers: btrahan, chad
Reviewed By: chad
CC: aran
Maniphest Tasks: T3132
Differential Revision: https://secure.phabricator.com/D5870
2013-05-08 18:12:52 -07:00
|
|
|
public function withTransforms(array $specs) {
|
|
|
|
foreach ($specs as $spec) {
|
|
|
|
if (!is_array($spec) ||
|
|
|
|
empty($spec['originalPHID']) ||
|
|
|
|
empty($spec['transform'])) {
|
|
|
|
throw new Exception(
|
|
|
|
"Transform specification must be a dictionary with keys ".
|
|
|
|
"'originalPHID' and 'transform'!");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->transforms = $specs;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2015-03-14 08:28:46 -07:00
|
|
|
public function withLengthBetween($min, $max) {
|
|
|
|
$this->minLength = $min;
|
|
|
|
$this->maxLength = $max;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function withNames(array $names) {
|
|
|
|
$this->names = $names;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function withIsPartial($partial) {
|
|
|
|
$this->isPartial = $partial;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2013-03-22 04:59:50 -07:00
|
|
|
public function showOnlyExplicitUploads($explicit_uploads) {
|
|
|
|
$this->explicitUploads = $explicit_uploads;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2013-03-01 11:28:02 -08:00
|
|
|
protected function loadPage() {
|
2012-10-31 09:57:46 -07:00
|
|
|
$table = new PhabricatorFile();
|
|
|
|
$conn_r = $table->establishConnection('r');
|
|
|
|
|
|
|
|
$data = queryfx_all(
|
|
|
|
$conn_r,
|
When deleting a file, delete all transformations of the file
Summary:
Fixes T3143. When a user deletes a file, delete all transforms of the file too. In particular, this means that deleting an image deletes all the thumbnails of the image.
In most cases, this aligns with user expectations. The only sort of weird case I can come up with is that memes are transformations of the source macro image, so deleting grumpycat will delete all the hilarious grumpycat memes. This seems not-too-unreasonable, though, and desirable if someone accidentally uploads an inappropriate image which is promptly turned into a meme.
Test Plan:
Added a unit test which covers both inbound and outbound transformations.
Uploaded a file and deleted it, verified its thumbnail was also deleted.
Reviewers: chad, btrahan, joseph.kampf
Reviewed By: btrahan
CC: aran, joseph.kampf
Maniphest Tasks: T3143
Differential Revision: https://secure.phabricator.com/D5879
2013-05-09 16:08:35 -07:00
|
|
|
'SELECT f.* FROM %T f %Q %Q %Q %Q',
|
2012-10-31 09:57:46 -07:00
|
|
|
$table->getTableName(),
|
Provide "builtin" files and use them to fix Pholio when files are deleted
Summary:
Fixes T3132. Currently, if a user deletes a file which is present in a mock, that mock throws an exception when loading. If the file is also the cover photo, the mock list throws an exception as well.
In other applications, we can sometimes deal with this (a sub-object vanishing) by implicitly hiding the parent object (for example, we can just vanish feed stories about objects which no longer exist). We can also sometimes deal with it by preventing sub-objects from being directly deleted.
However, neither approach is reasonable in this case.
If we vanish the whole mock, we'll lose all the comments and it will generally be weird. Vanishing a mock is a big deal compared to vanishing a feed story. We'll also need to load more data on the list view to prevent showing a mock on the list view and then realizing we need to vanish it on the detail view (because all of its images have been deleted).
We permit total deletion of files to allow users to recover from accidentally uploading sensitive files (which has happened a few times), and I'm hesitant to remove this capability because I think it serves a real need, so we can't prevent sub-objects from being deleted.
So we're left in a relatively unique situation. To solve this, I've added a "builtin" mechanism, which allows us to expose some resource we ship with as a PhabricatorFile. Then we just swap it out in place of the original file and proceed forward normally, as though nothing happened. The user sees a placeholder image instead of the original, but everything else works reasonably and this seems like a fairly acceptable outcome.
I believe we can use this mechanism to simplify some other code too, like default profile pictures.
Test Plan: Deleted a Pholio mock cover image's file. Implemented change, saw functional Pholio again with beautiful life-affirming "?" art replacing soul-shattering exception.
Reviewers: btrahan, chad
Reviewed By: chad
CC: aran
Maniphest Tasks: T3132
Differential Revision: https://secure.phabricator.com/D5870
2013-05-08 18:12:52 -07:00
|
|
|
$this->buildJoinClause($conn_r),
|
2012-10-31 09:57:46 -07:00
|
|
|
$this->buildWhereClause($conn_r),
|
|
|
|
$this->buildOrderClause($conn_r),
|
|
|
|
$this->buildLimitClause($conn_r));
|
|
|
|
|
2013-10-01 08:43:34 -07:00
|
|
|
$files = $table->loadAllFromArray($data);
|
|
|
|
|
|
|
|
if (!$files) {
|
|
|
|
return $files;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We need to load attached objects to perform policy checks for files.
|
|
|
|
// First, load the edges.
|
|
|
|
|
2015-01-03 10:33:25 +11:00
|
|
|
$edge_type = PhabricatorFileHasObjectEdgeType::EDGECONST;
|
2014-09-04 12:49:31 -07:00
|
|
|
$file_phids = mpull($files, 'getPHID');
|
2013-10-01 08:43:34 -07:00
|
|
|
$edges = id(new PhabricatorEdgeQuery())
|
2014-09-04 12:49:31 -07:00
|
|
|
->withSourcePHIDs($file_phids)
|
2013-10-01 08:43:34 -07:00
|
|
|
->withEdgeTypes(array($edge_type))
|
|
|
|
->execute();
|
|
|
|
|
|
|
|
$object_phids = array();
|
|
|
|
foreach ($files as $file) {
|
|
|
|
$phids = array_keys($edges[$file->getPHID()][$edge_type]);
|
|
|
|
$file->attachObjectPHIDs($phids);
|
|
|
|
foreach ($phids as $phid) {
|
|
|
|
$object_phids[$phid] = true;
|
|
|
|
}
|
|
|
|
}
|
2014-09-04 12:49:31 -07:00
|
|
|
|
|
|
|
// If this file is a transform of another file, load that file too. If you
|
|
|
|
// can see the original file, you can see the thumbnail.
|
|
|
|
|
|
|
|
// TODO: It might be nice to put this directly on PhabricatorFile and remove
|
|
|
|
// the PhabricatorTransformedFile table, which would be a little simpler.
|
|
|
|
|
|
|
|
$xforms = id(new PhabricatorTransformedFile())->loadAllWhere(
|
|
|
|
'transformedPHID IN (%Ls)',
|
|
|
|
$file_phids);
|
|
|
|
$xform_phids = mpull($xforms, 'getOriginalPHID', 'getTransformedPHID');
|
|
|
|
foreach ($xform_phids as $derived_phid => $original_phid) {
|
|
|
|
$object_phids[$original_phid] = true;
|
|
|
|
}
|
|
|
|
|
Fix some file policy issues and add a "Query Workspace"
Summary:
Ref T603. Several issues here:
1. Currently, `FileQuery` does not actually respect object attachment edges when doing policy checks. Everything else works fine, but this was missing an `array_keys()`.
2. Once that's fixed, we hit a bunch of recursion issues. For example, when loading a User we load the profile picture, and then that loads the User, and that loads the profile picture, etc.
3. Introduce a "Query Workspace", which holds objects we know we've loaded and know we can see but haven't finished filtering and/or attaching data to. This allows subqueries to look up objects instead of querying for them.
- We can probably generalize this a bit to make a few other queries more efficient. Pholio currently has a similar (but less general) "mock cache". However, it's keyed by ID instead of PHID so it's not easy to reuse this right now.
This is a bit complex for the problem being solved, but I think it's the cleanest approach and I believe the primitive will be useful in the future.
Test Plan: Looked at pastes, macros, mocks and projects as a logged-in and logged-out user.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran
Maniphest Tasks: T603
Differential Revision: https://secure.phabricator.com/D7309
2013-10-14 14:36:06 -07:00
|
|
|
$object_phids = array_keys($object_phids);
|
2013-10-01 08:43:34 -07:00
|
|
|
|
|
|
|
// Now, load the objects.
|
|
|
|
|
|
|
|
$objects = array();
|
|
|
|
if ($object_phids) {
|
Fix an issue where file queries would throw incorrectly
Summary:
Ref T4589. When you look at a file, we load attached objects in order to run the "you can see this if you can see any attached object" policy check.
However, right now the subquery inherits the "throw on filter" flag from the parent query. This inheritance makes sense in other cases[1], but because this is an "ANY" rule it does not make sense here. In practice, it means that if the file is attached to several objects, and any of them gets filtered, you can not see the file.
Instead, explicitly drop the flag for this subquery.
[1] Sort of. It doesn't produce wrong results in other cases, but now that I think about it might produce a less-tailored error than it could. I'll look into this the next time I'm poking around.
Test Plan:
- Viewed an "All Users" file attached to a private Mock.
- Prior to this patch, I incorrectly received an exception when the Mock was loaded. This is wrong; I should be able to see the file because the policy is "All Users".
- After the patch, I can correctly view the file, just not the associated mock.
{F127074}
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: 20after4, aran, epriestley
Maniphest Tasks: T4589
Differential Revision: https://secure.phabricator.com/D8498
2014-08-02 14:46:36 -07:00
|
|
|
// NOTE: We're explicitly turning policy exceptions off, since the rule
|
|
|
|
// here is "you can see the file if you can see ANY associated object".
|
|
|
|
// Without this explicit flag, we'll incorrectly throw unless you can
|
|
|
|
// see ALL associated objects.
|
|
|
|
|
2013-10-01 08:43:34 -07:00
|
|
|
$objects = id(new PhabricatorObjectQuery())
|
Fix some file policy issues and add a "Query Workspace"
Summary:
Ref T603. Several issues here:
1. Currently, `FileQuery` does not actually respect object attachment edges when doing policy checks. Everything else works fine, but this was missing an `array_keys()`.
2. Once that's fixed, we hit a bunch of recursion issues. For example, when loading a User we load the profile picture, and then that loads the User, and that loads the profile picture, etc.
3. Introduce a "Query Workspace", which holds objects we know we've loaded and know we can see but haven't finished filtering and/or attaching data to. This allows subqueries to look up objects instead of querying for them.
- We can probably generalize this a bit to make a few other queries more efficient. Pholio currently has a similar (but less general) "mock cache". However, it's keyed by ID instead of PHID so it's not easy to reuse this right now.
This is a bit complex for the problem being solved, but I think it's the cleanest approach and I believe the primitive will be useful in the future.
Test Plan: Looked at pastes, macros, mocks and projects as a logged-in and logged-out user.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran
Maniphest Tasks: T603
Differential Revision: https://secure.phabricator.com/D7309
2013-10-14 14:36:06 -07:00
|
|
|
->setParentQuery($this)
|
2013-10-01 08:43:34 -07:00
|
|
|
->setViewer($this->getViewer())
|
|
|
|
->withPHIDs($object_phids)
|
Fix an issue where file queries would throw incorrectly
Summary:
Ref T4589. When you look at a file, we load attached objects in order to run the "you can see this if you can see any attached object" policy check.
However, right now the subquery inherits the "throw on filter" flag from the parent query. This inheritance makes sense in other cases[1], but because this is an "ANY" rule it does not make sense here. In practice, it means that if the file is attached to several objects, and any of them gets filtered, you can not see the file.
Instead, explicitly drop the flag for this subquery.
[1] Sort of. It doesn't produce wrong results in other cases, but now that I think about it might produce a less-tailored error than it could. I'll look into this the next time I'm poking around.
Test Plan:
- Viewed an "All Users" file attached to a private Mock.
- Prior to this patch, I incorrectly received an exception when the Mock was loaded. This is wrong; I should be able to see the file because the policy is "All Users".
- After the patch, I can correctly view the file, just not the associated mock.
{F127074}
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: 20after4, aran, epriestley
Maniphest Tasks: T4589
Differential Revision: https://secure.phabricator.com/D8498
2014-08-02 14:46:36 -07:00
|
|
|
->setRaisePolicyExceptions(false)
|
2013-10-01 08:43:34 -07:00
|
|
|
->execute();
|
|
|
|
$objects = mpull($objects, null, 'getPHID');
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($files as $file) {
|
|
|
|
$file_objects = array_select_keys($objects, $file->getObjectPHIDs());
|
|
|
|
$file->attachObjects($file_objects);
|
|
|
|
}
|
|
|
|
|
2014-09-04 12:49:31 -07:00
|
|
|
foreach ($files as $key => $file) {
|
|
|
|
$original_phid = idx($xform_phids, $file->getPHID());
|
|
|
|
if ($original_phid == PhabricatorPHIDConstants::PHID_VOID) {
|
|
|
|
// This is a special case for builtin files, which are handled
|
|
|
|
// oddly.
|
|
|
|
$original = null;
|
|
|
|
} else if ($original_phid) {
|
|
|
|
$original = idx($objects, $original_phid);
|
|
|
|
if (!$original) {
|
|
|
|
// If the viewer can't see the original file, also prevent them from
|
|
|
|
// seeing the transformed file.
|
|
|
|
$this->didRejectResult($file);
|
|
|
|
unset($files[$key]);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$original = null;
|
|
|
|
}
|
|
|
|
$file->attachOriginalFile($original);
|
|
|
|
}
|
|
|
|
|
2013-10-01 08:43:34 -07:00
|
|
|
return $files;
|
2012-10-31 09:57:46 -07:00
|
|
|
}
|
|
|
|
|
Provide "builtin" files and use them to fix Pholio when files are deleted
Summary:
Fixes T3132. Currently, if a user deletes a file which is present in a mock, that mock throws an exception when loading. If the file is also the cover photo, the mock list throws an exception as well.
In other applications, we can sometimes deal with this (a sub-object vanishing) by implicitly hiding the parent object (for example, we can just vanish feed stories about objects which no longer exist). We can also sometimes deal with it by preventing sub-objects from being directly deleted.
However, neither approach is reasonable in this case.
If we vanish the whole mock, we'll lose all the comments and it will generally be weird. Vanishing a mock is a big deal compared to vanishing a feed story. We'll also need to load more data on the list view to prevent showing a mock on the list view and then realizing we need to vanish it on the detail view (because all of its images have been deleted).
We permit total deletion of files to allow users to recover from accidentally uploading sensitive files (which has happened a few times), and I'm hesitant to remove this capability because I think it serves a real need, so we can't prevent sub-objects from being deleted.
So we're left in a relatively unique situation. To solve this, I've added a "builtin" mechanism, which allows us to expose some resource we ship with as a PhabricatorFile. Then we just swap it out in place of the original file and proceed forward normally, as though nothing happened. The user sees a placeholder image instead of the original, but everything else works reasonably and this seems like a fairly acceptable outcome.
I believe we can use this mechanism to simplify some other code too, like default profile pictures.
Test Plan: Deleted a Pholio mock cover image's file. Implemented change, saw functional Pholio again with beautiful life-affirming "?" art replacing soul-shattering exception.
Reviewers: btrahan, chad
Reviewed By: chad
CC: aran
Maniphest Tasks: T3132
Differential Revision: https://secure.phabricator.com/D5870
2013-05-08 18:12:52 -07:00
|
|
|
private function buildJoinClause(AphrontDatabaseConnection $conn_r) {
|
|
|
|
$joins = array();
|
|
|
|
|
|
|
|
if ($this->transforms) {
|
|
|
|
$joins[] = qsprintf(
|
|
|
|
$conn_r,
|
|
|
|
'JOIN %T t ON t.transformedPHID = f.phid',
|
|
|
|
id(new PhabricatorTransformedFile())->getTableName());
|
|
|
|
}
|
|
|
|
|
|
|
|
return implode(' ', $joins);
|
|
|
|
}
|
|
|
|
|
2012-10-31 09:57:46 -07:00
|
|
|
private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
|
|
|
|
$where = array();
|
|
|
|
|
|
|
|
$where[] = $this->buildPagingClause($conn_r);
|
|
|
|
|
2015-03-14 08:28:46 -07:00
|
|
|
if ($this->ids !== null) {
|
2012-10-31 09:57:46 -07:00
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn_r,
|
Provide "builtin" files and use them to fix Pholio when files are deleted
Summary:
Fixes T3132. Currently, if a user deletes a file which is present in a mock, that mock throws an exception when loading. If the file is also the cover photo, the mock list throws an exception as well.
In other applications, we can sometimes deal with this (a sub-object vanishing) by implicitly hiding the parent object (for example, we can just vanish feed stories about objects which no longer exist). We can also sometimes deal with it by preventing sub-objects from being directly deleted.
However, neither approach is reasonable in this case.
If we vanish the whole mock, we'll lose all the comments and it will generally be weird. Vanishing a mock is a big deal compared to vanishing a feed story. We'll also need to load more data on the list view to prevent showing a mock on the list view and then realizing we need to vanish it on the detail view (because all of its images have been deleted).
We permit total deletion of files to allow users to recover from accidentally uploading sensitive files (which has happened a few times), and I'm hesitant to remove this capability because I think it serves a real need, so we can't prevent sub-objects from being deleted.
So we're left in a relatively unique situation. To solve this, I've added a "builtin" mechanism, which allows us to expose some resource we ship with as a PhabricatorFile. Then we just swap it out in place of the original file and proceed forward normally, as though nothing happened. The user sees a placeholder image instead of the original, but everything else works reasonably and this seems like a fairly acceptable outcome.
I believe we can use this mechanism to simplify some other code too, like default profile pictures.
Test Plan: Deleted a Pholio mock cover image's file. Implemented change, saw functional Pholio again with beautiful life-affirming "?" art replacing soul-shattering exception.
Reviewers: btrahan, chad
Reviewed By: chad
CC: aran
Maniphest Tasks: T3132
Differential Revision: https://secure.phabricator.com/D5870
2013-05-08 18:12:52 -07:00
|
|
|
'f.id IN (%Ld)',
|
2012-10-31 09:57:46 -07:00
|
|
|
$this->ids);
|
|
|
|
}
|
|
|
|
|
2015-03-14 08:28:46 -07:00
|
|
|
if ($this->phids !== null) {
|
2012-10-31 09:57:46 -07:00
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn_r,
|
Provide "builtin" files and use them to fix Pholio when files are deleted
Summary:
Fixes T3132. Currently, if a user deletes a file which is present in a mock, that mock throws an exception when loading. If the file is also the cover photo, the mock list throws an exception as well.
In other applications, we can sometimes deal with this (a sub-object vanishing) by implicitly hiding the parent object (for example, we can just vanish feed stories about objects which no longer exist). We can also sometimes deal with it by preventing sub-objects from being directly deleted.
However, neither approach is reasonable in this case.
If we vanish the whole mock, we'll lose all the comments and it will generally be weird. Vanishing a mock is a big deal compared to vanishing a feed story. We'll also need to load more data on the list view to prevent showing a mock on the list view and then realizing we need to vanish it on the detail view (because all of its images have been deleted).
We permit total deletion of files to allow users to recover from accidentally uploading sensitive files (which has happened a few times), and I'm hesitant to remove this capability because I think it serves a real need, so we can't prevent sub-objects from being deleted.
So we're left in a relatively unique situation. To solve this, I've added a "builtin" mechanism, which allows us to expose some resource we ship with as a PhabricatorFile. Then we just swap it out in place of the original file and proceed forward normally, as though nothing happened. The user sees a placeholder image instead of the original, but everything else works reasonably and this seems like a fairly acceptable outcome.
I believe we can use this mechanism to simplify some other code too, like default profile pictures.
Test Plan: Deleted a Pholio mock cover image's file. Implemented change, saw functional Pholio again with beautiful life-affirming "?" art replacing soul-shattering exception.
Reviewers: btrahan, chad
Reviewed By: chad
CC: aran
Maniphest Tasks: T3132
Differential Revision: https://secure.phabricator.com/D5870
2013-05-08 18:12:52 -07:00
|
|
|
'f.phid IN (%Ls)',
|
2012-10-31 09:57:46 -07:00
|
|
|
$this->phids);
|
|
|
|
}
|
|
|
|
|
2015-03-14 08:28:46 -07:00
|
|
|
if ($this->authorPHIDs !== null) {
|
2012-12-16 16:33:02 -08:00
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn_r,
|
Provide "builtin" files and use them to fix Pholio when files are deleted
Summary:
Fixes T3132. Currently, if a user deletes a file which is present in a mock, that mock throws an exception when loading. If the file is also the cover photo, the mock list throws an exception as well.
In other applications, we can sometimes deal with this (a sub-object vanishing) by implicitly hiding the parent object (for example, we can just vanish feed stories about objects which no longer exist). We can also sometimes deal with it by preventing sub-objects from being directly deleted.
However, neither approach is reasonable in this case.
If we vanish the whole mock, we'll lose all the comments and it will generally be weird. Vanishing a mock is a big deal compared to vanishing a feed story. We'll also need to load more data on the list view to prevent showing a mock on the list view and then realizing we need to vanish it on the detail view (because all of its images have been deleted).
We permit total deletion of files to allow users to recover from accidentally uploading sensitive files (which has happened a few times), and I'm hesitant to remove this capability because I think it serves a real need, so we can't prevent sub-objects from being deleted.
So we're left in a relatively unique situation. To solve this, I've added a "builtin" mechanism, which allows us to expose some resource we ship with as a PhabricatorFile. Then we just swap it out in place of the original file and proceed forward normally, as though nothing happened. The user sees a placeholder image instead of the original, but everything else works reasonably and this seems like a fairly acceptable outcome.
I believe we can use this mechanism to simplify some other code too, like default profile pictures.
Test Plan: Deleted a Pholio mock cover image's file. Implemented change, saw functional Pholio again with beautiful life-affirming "?" art replacing soul-shattering exception.
Reviewers: btrahan, chad
Reviewed By: chad
CC: aran
Maniphest Tasks: T3132
Differential Revision: https://secure.phabricator.com/D5870
2013-05-08 18:12:52 -07:00
|
|
|
'f.authorPHID IN (%Ls)',
|
2012-12-16 16:33:02 -08:00
|
|
|
$this->authorPHIDs);
|
|
|
|
}
|
|
|
|
|
2015-03-14 08:28:46 -07:00
|
|
|
if ($this->explicitUploads !== null) {
|
2013-03-22 04:59:50 -07:00
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn_r,
|
Provide "builtin" files and use them to fix Pholio when files are deleted
Summary:
Fixes T3132. Currently, if a user deletes a file which is present in a mock, that mock throws an exception when loading. If the file is also the cover photo, the mock list throws an exception as well.
In other applications, we can sometimes deal with this (a sub-object vanishing) by implicitly hiding the parent object (for example, we can just vanish feed stories about objects which no longer exist). We can also sometimes deal with it by preventing sub-objects from being directly deleted.
However, neither approach is reasonable in this case.
If we vanish the whole mock, we'll lose all the comments and it will generally be weird. Vanishing a mock is a big deal compared to vanishing a feed story. We'll also need to load more data on the list view to prevent showing a mock on the list view and then realizing we need to vanish it on the detail view (because all of its images have been deleted).
We permit total deletion of files to allow users to recover from accidentally uploading sensitive files (which has happened a few times), and I'm hesitant to remove this capability because I think it serves a real need, so we can't prevent sub-objects from being deleted.
So we're left in a relatively unique situation. To solve this, I've added a "builtin" mechanism, which allows us to expose some resource we ship with as a PhabricatorFile. Then we just swap it out in place of the original file and proceed forward normally, as though nothing happened. The user sees a placeholder image instead of the original, but everything else works reasonably and this seems like a fairly acceptable outcome.
I believe we can use this mechanism to simplify some other code too, like default profile pictures.
Test Plan: Deleted a Pholio mock cover image's file. Implemented change, saw functional Pholio again with beautiful life-affirming "?" art replacing soul-shattering exception.
Reviewers: btrahan, chad
Reviewed By: chad
CC: aran
Maniphest Tasks: T3132
Differential Revision: https://secure.phabricator.com/D5870
2013-05-08 18:12:52 -07:00
|
|
|
'f.isExplicitUpload = true');
|
|
|
|
}
|
|
|
|
|
2015-03-14 08:28:46 -07:00
|
|
|
if ($this->transforms !== null) {
|
Provide "builtin" files and use them to fix Pholio when files are deleted
Summary:
Fixes T3132. Currently, if a user deletes a file which is present in a mock, that mock throws an exception when loading. If the file is also the cover photo, the mock list throws an exception as well.
In other applications, we can sometimes deal with this (a sub-object vanishing) by implicitly hiding the parent object (for example, we can just vanish feed stories about objects which no longer exist). We can also sometimes deal with it by preventing sub-objects from being directly deleted.
However, neither approach is reasonable in this case.
If we vanish the whole mock, we'll lose all the comments and it will generally be weird. Vanishing a mock is a big deal compared to vanishing a feed story. We'll also need to load more data on the list view to prevent showing a mock on the list view and then realizing we need to vanish it on the detail view (because all of its images have been deleted).
We permit total deletion of files to allow users to recover from accidentally uploading sensitive files (which has happened a few times), and I'm hesitant to remove this capability because I think it serves a real need, so we can't prevent sub-objects from being deleted.
So we're left in a relatively unique situation. To solve this, I've added a "builtin" mechanism, which allows us to expose some resource we ship with as a PhabricatorFile. Then we just swap it out in place of the original file and proceed forward normally, as though nothing happened. The user sees a placeholder image instead of the original, but everything else works reasonably and this seems like a fairly acceptable outcome.
I believe we can use this mechanism to simplify some other code too, like default profile pictures.
Test Plan: Deleted a Pholio mock cover image's file. Implemented change, saw functional Pholio again with beautiful life-affirming "?" art replacing soul-shattering exception.
Reviewers: btrahan, chad
Reviewed By: chad
CC: aran
Maniphest Tasks: T3132
Differential Revision: https://secure.phabricator.com/D5870
2013-05-08 18:12:52 -07:00
|
|
|
$clauses = array();
|
|
|
|
foreach ($this->transforms as $transform) {
|
When deleting a file, delete all transformations of the file
Summary:
Fixes T3143. When a user deletes a file, delete all transforms of the file too. In particular, this means that deleting an image deletes all the thumbnails of the image.
In most cases, this aligns with user expectations. The only sort of weird case I can come up with is that memes are transformations of the source macro image, so deleting grumpycat will delete all the hilarious grumpycat memes. This seems not-too-unreasonable, though, and desirable if someone accidentally uploads an inappropriate image which is promptly turned into a meme.
Test Plan:
Added a unit test which covers both inbound and outbound transformations.
Uploaded a file and deleted it, verified its thumbnail was also deleted.
Reviewers: chad, btrahan, joseph.kampf
Reviewed By: btrahan
CC: aran, joseph.kampf
Maniphest Tasks: T3143
Differential Revision: https://secure.phabricator.com/D5879
2013-05-09 16:08:35 -07:00
|
|
|
if ($transform['transform'] === true) {
|
|
|
|
$clauses[] = qsprintf(
|
|
|
|
$conn_r,
|
|
|
|
'(t.originalPHID = %s)',
|
|
|
|
$transform['originalPHID']);
|
|
|
|
} else {
|
|
|
|
$clauses[] = qsprintf(
|
|
|
|
$conn_r,
|
|
|
|
'(t.originalPHID = %s AND t.transform = %s)',
|
|
|
|
$transform['originalPHID'],
|
|
|
|
$transform['transform']);
|
|
|
|
}
|
Provide "builtin" files and use them to fix Pholio when files are deleted
Summary:
Fixes T3132. Currently, if a user deletes a file which is present in a mock, that mock throws an exception when loading. If the file is also the cover photo, the mock list throws an exception as well.
In other applications, we can sometimes deal with this (a sub-object vanishing) by implicitly hiding the parent object (for example, we can just vanish feed stories about objects which no longer exist). We can also sometimes deal with it by preventing sub-objects from being directly deleted.
However, neither approach is reasonable in this case.
If we vanish the whole mock, we'll lose all the comments and it will generally be weird. Vanishing a mock is a big deal compared to vanishing a feed story. We'll also need to load more data on the list view to prevent showing a mock on the list view and then realizing we need to vanish it on the detail view (because all of its images have been deleted).
We permit total deletion of files to allow users to recover from accidentally uploading sensitive files (which has happened a few times), and I'm hesitant to remove this capability because I think it serves a real need, so we can't prevent sub-objects from being deleted.
So we're left in a relatively unique situation. To solve this, I've added a "builtin" mechanism, which allows us to expose some resource we ship with as a PhabricatorFile. Then we just swap it out in place of the original file and proceed forward normally, as though nothing happened. The user sees a placeholder image instead of the original, but everything else works reasonably and this seems like a fairly acceptable outcome.
I believe we can use this mechanism to simplify some other code too, like default profile pictures.
Test Plan: Deleted a Pholio mock cover image's file. Implemented change, saw functional Pholio again with beautiful life-affirming "?" art replacing soul-shattering exception.
Reviewers: btrahan, chad
Reviewed By: chad
CC: aran
Maniphest Tasks: T3132
Differential Revision: https://secure.phabricator.com/D5870
2013-05-08 18:12:52 -07:00
|
|
|
}
|
|
|
|
$where[] = qsprintf($conn_r, '(%Q)', implode(') OR (', $clauses));
|
2013-03-22 04:59:50 -07:00
|
|
|
}
|
|
|
|
|
2015-03-14 08:28:46 -07:00
|
|
|
if ($this->dateCreatedAfter !== null) {
|
2013-05-31 10:51:05 -07:00
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn_r,
|
|
|
|
'f.dateCreated >= %d',
|
|
|
|
$this->dateCreatedAfter);
|
|
|
|
}
|
|
|
|
|
2015-03-14 08:28:46 -07:00
|
|
|
if ($this->dateCreatedBefore !== null) {
|
2013-05-31 10:51:05 -07:00
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn_r,
|
|
|
|
'f.dateCreated <= %d',
|
|
|
|
$this->dateCreatedBefore);
|
|
|
|
}
|
|
|
|
|
2015-03-14 08:28:46 -07:00
|
|
|
if ($this->contentHashes !== null) {
|
Provide `phragment.getstate` and `phragment.getpatch` Conduit methods
Summary:
This provides a `phragment.getstate` and a `phragment.getpatch` Conduit method.
`phragment.getstate` - This returns the current state of the fragment and all of it's children.
`phragment.getpatch` - This accepts a base path and a mapping of paths to hashes. The mapping is for the caller to specify the current state of the files it has. This returns a list of patches that the caller needs to apply to it's files to get to the latest version.
Test Plan:
Ran the following script in a folder which had content matching a fragment and it's children:
```
#!/bin/bash
STATE=""
for i in $(find ./ -type f); do
HASH=$(cat $i | sha1sum | awk '{ print $1 }')
BASE=${i:2}
STATE="$STATE,\"$BASE\":\"$HASH\""
done
STATE=${STATE:1}
STATE="{$STATE}"
echo '{"path":"tychaia3.zip","state":'$STATE'}' | arc --conduit-uri=http://phabricator.local/ call-conduit phragment.getpatch
```
and I got:
```
{"error":null,"errorMessage":null,"response":[]}
```
I updated one of the child fragments with a new file and ran the script again (patch has been omitted due to it's size):
```
{"error":null,"errorMessage":null,"response":[{"path":"Content\/TitleFont.xnb","hash_old":"4a927d7b90582e50cdd330de9f4b59b0cc5eb5c7","hash_new":"25867504642a3a403102274c68fbb9b430c1980f","patch":"..."}]}
```
Reviewers: epriestley, #blessed_reviewers
Reviewed By: epriestley
CC: Korvin, epriestley, aran, staticshock
Maniphest Tasks: T4205
Differential Revision: https://secure.phabricator.com/D7739
2013-12-11 11:19:23 +11:00
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn_r,
|
|
|
|
'f.contentHash IN (%Ls)',
|
|
|
|
$this->contentHashes);
|
|
|
|
}
|
|
|
|
|
2015-03-14 08:28:46 -07:00
|
|
|
if ($this->minLength !== null) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn_r,
|
|
|
|
'byteSize >= %d',
|
|
|
|
$this->minLength);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->maxLength !== null) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn_r,
|
|
|
|
'byteSize <= %d',
|
|
|
|
$this->maxLength);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->names !== null) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn_r,
|
|
|
|
'name in (%Ls)',
|
|
|
|
$this->names);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->isPartial !== null) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn_r,
|
|
|
|
'isPartial = %d',
|
|
|
|
(int)$this->isPartial);
|
|
|
|
}
|
|
|
|
|
2012-10-31 09:57:46 -07:00
|
|
|
return $this->formatWhereClause($where);
|
|
|
|
}
|
|
|
|
|
2015-04-11 19:00:53 -07:00
|
|
|
protected function getPrimaryTableAlias() {
|
|
|
|
return 'f';
|
Provide "builtin" files and use them to fix Pholio when files are deleted
Summary:
Fixes T3132. Currently, if a user deletes a file which is present in a mock, that mock throws an exception when loading. If the file is also the cover photo, the mock list throws an exception as well.
In other applications, we can sometimes deal with this (a sub-object vanishing) by implicitly hiding the parent object (for example, we can just vanish feed stories about objects which no longer exist). We can also sometimes deal with it by preventing sub-objects from being directly deleted.
However, neither approach is reasonable in this case.
If we vanish the whole mock, we'll lose all the comments and it will generally be weird. Vanishing a mock is a big deal compared to vanishing a feed story. We'll also need to load more data on the list view to prevent showing a mock on the list view and then realizing we need to vanish it on the detail view (because all of its images have been deleted).
We permit total deletion of files to allow users to recover from accidentally uploading sensitive files (which has happened a few times), and I'm hesitant to remove this capability because I think it serves a real need, so we can't prevent sub-objects from being deleted.
So we're left in a relatively unique situation. To solve this, I've added a "builtin" mechanism, which allows us to expose some resource we ship with as a PhabricatorFile. Then we just swap it out in place of the original file and proceed forward normally, as though nothing happened. The user sees a placeholder image instead of the original, but everything else works reasonably and this seems like a fairly acceptable outcome.
I believe we can use this mechanism to simplify some other code too, like default profile pictures.
Test Plan: Deleted a Pholio mock cover image's file. Implemented change, saw functional Pholio again with beautiful life-affirming "?" art replacing soul-shattering exception.
Reviewers: btrahan, chad
Reviewed By: chad
CC: aran
Maniphest Tasks: T3132
Differential Revision: https://secure.phabricator.com/D5870
2013-05-08 18:12:52 -07:00
|
|
|
}
|
|
|
|
|
Lock policy queries to their applications
Summary:
While we mostly have reasonable effective object accessibility when you lock a user out of an application, it's primarily enforced at the controller level. Users can still, e.g., load the handles of objects they can't actually see. Instead, lock the queries to the applications so that you can, e.g., never load a revision if you don't have access to Differential.
This has several parts:
- For PolicyAware queries, provide an application class name method.
- If the query specifies a class name and the user doesn't have permission to use it, fail the entire query unconditionally.
- For handles, simplify query construction and count all the PHIDs as "restricted" so we get a UI full of "restricted" instead of "unknown" handles.
Test Plan:
- Added a unit test to verify I got all the class names right.
- Browsed around, logged in/out as a normal user with public policies on and off.
- Browsed around, logged in/out as a restricted user with public policies on and off. With restrictions, saw all traces of restricted apps removed or restricted.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran
Differential Revision: https://secure.phabricator.com/D7367
2013-10-21 17:20:27 -07:00
|
|
|
public function getQueryApplicationClass() {
|
2014-07-23 10:03:09 +10:00
|
|
|
return 'PhabricatorFilesApplication';
|
Lock policy queries to their applications
Summary:
While we mostly have reasonable effective object accessibility when you lock a user out of an application, it's primarily enforced at the controller level. Users can still, e.g., load the handles of objects they can't actually see. Instead, lock the queries to the applications so that you can, e.g., never load a revision if you don't have access to Differential.
This has several parts:
- For PolicyAware queries, provide an application class name method.
- If the query specifies a class name and the user doesn't have permission to use it, fail the entire query unconditionally.
- For handles, simplify query construction and count all the PHIDs as "restricted" so we get a UI full of "restricted" instead of "unknown" handles.
Test Plan:
- Added a unit test to verify I got all the class names right.
- Browsed around, logged in/out as a normal user with public policies on and off.
- Browsed around, logged in/out as a restricted user with public policies on and off. With restrictions, saw all traces of restricted apps removed or restricted.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran
Differential Revision: https://secure.phabricator.com/D7367
2013-10-21 17:20:27 -07:00
|
|
|
}
|
|
|
|
|
2012-10-31 09:57:46 -07:00
|
|
|
}
|