Path: blob/master/src/applications/files/query/PhabricatorFileQuery.php
12242 views
<?php12final class PhabricatorFileQuery3extends PhabricatorCursorPagedPolicyAwareQuery {45private $ids;6private $phids;7private $authorPHIDs;8private $explicitUploads;9private $transforms;10private $dateCreatedAfter;11private $dateCreatedBefore;12private $contentHashes;13private $minLength;14private $maxLength;15private $names;16private $isPartial;17private $isDeleted;18private $needTransforms;19private $builtinKeys;20private $isBuiltin;21private $storageEngines;22private $attachedObjectPHIDs;2324public function withIDs(array $ids) {25$this->ids = $ids;26return $this;27}2829public function withPHIDs(array $phids) {30$this->phids = $phids;31return $this;32}3334public function withAuthorPHIDs(array $phids) {35$this->authorPHIDs = $phids;36return $this;37}3839public function withDateCreatedBefore($date_created_before) {40$this->dateCreatedBefore = $date_created_before;41return $this;42}4344public function withDateCreatedAfter($date_created_after) {45$this->dateCreatedAfter = $date_created_after;46return $this;47}4849public function withContentHashes(array $content_hashes) {50$this->contentHashes = $content_hashes;51return $this;52}5354public function withBuiltinKeys(array $keys) {55$this->builtinKeys = $keys;56return $this;57}5859public function withIsBuiltin($is_builtin) {60$this->isBuiltin = $is_builtin;61return $this;62}6364public function withAttachedObjectPHIDs(array $phids) {65$this->attachedObjectPHIDs = $phids;66return $this;67}6869/**70* Select files which are transformations of some other file. For example,71* you can use this query to find previously generated thumbnails of an image72* file.73*74* As a parameter, provide a list of transformation specifications. Each75* specification is a dictionary with the keys `originalPHID` and `transform`.76* The `originalPHID` is the PHID of the original file (the file which was77* transformed) and the `transform` is the name of the transform to query78* for. If you pass `true` as the `transform`, all transformations of the79* file will be selected.80*81* For example:82*83* array(84* array(85* 'originalPHID' => 'PHID-FILE-aaaa',86* 'transform' => 'sepia',87* ),88* array(89* 'originalPHID' => 'PHID-FILE-bbbb',90* 'transform' => true,91* ),92* )93*94* This selects the `"sepia"` transformation of the file with PHID95* `PHID-FILE-aaaa` and all transformations of the file with PHID96* `PHID-FILE-bbbb`.97*98* @param list<dict> List of transform specifications, described above.99* @return this100*/101public function withTransforms(array $specs) {102foreach ($specs as $spec) {103if (!is_array($spec) ||104empty($spec['originalPHID']) ||105empty($spec['transform'])) {106throw new Exception(107pht(108"Transform specification must be a dictionary with keys ".109"'%s' and '%s'!",110'originalPHID',111'transform'));112}113}114115$this->transforms = $specs;116return $this;117}118119public function withLengthBetween($min, $max) {120$this->minLength = $min;121$this->maxLength = $max;122return $this;123}124125public function withNames(array $names) {126$this->names = $names;127return $this;128}129130public function withIsPartial($partial) {131$this->isPartial = $partial;132return $this;133}134135public function withIsDeleted($deleted) {136$this->isDeleted = $deleted;137return $this;138}139140public function withNameNgrams($ngrams) {141return $this->withNgramsConstraint(142id(new PhabricatorFileNameNgrams()),143$ngrams);144}145146public function withStorageEngines(array $engines) {147$this->storageEngines = $engines;148return $this;149}150151public function showOnlyExplicitUploads($explicit_uploads) {152$this->explicitUploads = $explicit_uploads;153return $this;154}155156public function needTransforms(array $transforms) {157$this->needTransforms = $transforms;158return $this;159}160161public function newResultObject() {162return new PhabricatorFile();163}164165protected function loadPage() {166$files = $this->loadStandardPage($this->newResultObject());167168if (!$files) {169return $files;170}171172// Figure out which files we need to load attached objects for. In most173// cases, we need to load attached objects to perform policy checks for174// files.175176// However, in some special cases where we know files will always be177// visible, we skip this. See T8478 and T13106.178$need_objects = array();179$need_xforms = array();180foreach ($files as $file) {181$always_visible = false;182183if ($file->getIsProfileImage()) {184$always_visible = true;185}186187if ($file->isBuiltin()) {188$always_visible = true;189}190191if ($always_visible) {192// We just treat these files as though they aren't attached to193// anything. This saves a query in common cases when we're loading194// profile images or builtins. We could be slightly more nuanced195// about this and distinguish between "not attached to anything" and196// "might be attached but policy checks don't need to care".197$file->attachObjectPHIDs(array());198continue;199}200201$need_objects[] = $file;202$need_xforms[] = $file;203}204205$viewer = $this->getViewer();206$is_omnipotent = $viewer->isOmnipotent();207208// If we have any files left which do need objects, load the edges now.209$object_phids = array();210if ($need_objects) {211$attachments_map = $this->newAttachmentsMap($need_objects);212213foreach ($need_objects as $file) {214$file_phid = $file->getPHID();215$phids = $attachments_map[$file_phid];216217$file->attachObjectPHIDs($phids);218219if ($is_omnipotent) {220// If the viewer is omnipotent, we don't need to load the associated221// objects either since the viewer can certainly see the object.222// Skipping this can improve performance and prevent cycles. This223// could possibly become part of the profile/builtin code above which224// short circuits attacment policy checks in cases where we know them225// to be unnecessary.226continue;227}228229foreach ($phids as $phid) {230$object_phids[$phid] = true;231}232}233}234235// If this file is a transform of another file, load that file too. If you236// can see the original file, you can see the thumbnail.237238// TODO: It might be nice to put this directly on PhabricatorFile and239// remove the PhabricatorTransformedFile table, which would be a little240// simpler.241242if ($need_xforms) {243$xforms = id(new PhabricatorTransformedFile())->loadAllWhere(244'transformedPHID IN (%Ls)',245mpull($need_xforms, 'getPHID'));246$xform_phids = mpull($xforms, 'getOriginalPHID', 'getTransformedPHID');247foreach ($xform_phids as $derived_phid => $original_phid) {248$object_phids[$original_phid] = true;249}250} else {251$xform_phids = array();252}253254$object_phids = array_keys($object_phids);255256// Now, load the objects.257258$objects = array();259if ($object_phids) {260// NOTE: We're explicitly turning policy exceptions off, since the rule261// here is "you can see the file if you can see ANY associated object".262// Without this explicit flag, we'll incorrectly throw unless you can263// see ALL associated objects.264265$objects = id(new PhabricatorObjectQuery())266->setParentQuery($this)267->setViewer($this->getViewer())268->withPHIDs($object_phids)269->setRaisePolicyExceptions(false)270->execute();271$objects = mpull($objects, null, 'getPHID');272}273274foreach ($files as $file) {275$file_objects = array_select_keys($objects, $file->getObjectPHIDs());276$file->attachObjects($file_objects);277}278279foreach ($files as $key => $file) {280$original_phid = idx($xform_phids, $file->getPHID());281if ($original_phid == PhabricatorPHIDConstants::PHID_VOID) {282// This is a special case for builtin files, which are handled283// oddly.284$original = null;285} else if ($original_phid) {286$original = idx($objects, $original_phid);287if (!$original) {288// If the viewer can't see the original file, also prevent them from289// seeing the transformed file.290$this->didRejectResult($file);291unset($files[$key]);292continue;293}294} else {295$original = null;296}297$file->attachOriginalFile($original);298}299300return $files;301}302303private function newAttachmentsMap(array $files) {304$file_phids = mpull($files, 'getPHID');305306$attachments_table = new PhabricatorFileAttachment();307$attachments_conn = $attachments_table->establishConnection('r');308309$attachments = queryfx_all(310$attachments_conn,311'SELECT filePHID, objectPHID FROM %R WHERE filePHID IN (%Ls)312AND attachmentMode IN (%Ls)',313$attachments_table,314$file_phids,315array(316PhabricatorFileAttachment::MODE_ATTACH,317));318319$attachments_map = array_fill_keys($file_phids, array());320foreach ($attachments as $row) {321$file_phid = $row['filePHID'];322$object_phid = $row['objectPHID'];323$attachments_map[$file_phid][] = $object_phid;324}325326return $attachments_map;327}328329protected function didFilterPage(array $files) {330$xform_keys = $this->needTransforms;331if ($xform_keys !== null) {332$xforms = id(new PhabricatorTransformedFile())->loadAllWhere(333'originalPHID IN (%Ls) AND transform IN (%Ls)',334mpull($files, 'getPHID'),335$xform_keys);336337if ($xforms) {338$xfiles = id(new PhabricatorFile())->loadAllWhere(339'phid IN (%Ls)',340mpull($xforms, 'getTransformedPHID'));341$xfiles = mpull($xfiles, null, 'getPHID');342}343344$xform_map = array();345foreach ($xforms as $xform) {346$xfile = idx($xfiles, $xform->getTransformedPHID());347if (!$xfile) {348continue;349}350$original_phid = $xform->getOriginalPHID();351$xform_key = $xform->getTransform();352$xform_map[$original_phid][$xform_key] = $xfile;353}354355$default_xforms = array_fill_keys($xform_keys, null);356357foreach ($files as $file) {358$file_xforms = idx($xform_map, $file->getPHID(), array());359$file_xforms += $default_xforms;360$file->attachTransforms($file_xforms);361}362}363364return $files;365}366367protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {368$joins = parent::buildJoinClauseParts($conn);369370if ($this->transforms) {371$joins[] = qsprintf(372$conn,373'JOIN %T t ON t.transformedPHID = f.phid',374id(new PhabricatorTransformedFile())->getTableName());375}376377if ($this->shouldJoinAttachmentsTable()) {378$joins[] = qsprintf(379$conn,380'JOIN %R attachments ON attachments.filePHID = f.phid381AND attachmentMode IN (%Ls)',382new PhabricatorFileAttachment(),383array(384PhabricatorFileAttachment::MODE_ATTACH,385));386}387388return $joins;389}390391private function shouldJoinAttachmentsTable() {392return ($this->attachedObjectPHIDs !== null);393}394395protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {396$where = parent::buildWhereClauseParts($conn);397398if ($this->ids !== null) {399$where[] = qsprintf(400$conn,401'f.id IN (%Ld)',402$this->ids);403}404405if ($this->phids !== null) {406$where[] = qsprintf(407$conn,408'f.phid IN (%Ls)',409$this->phids);410}411412if ($this->authorPHIDs !== null) {413$where[] = qsprintf(414$conn,415'f.authorPHID IN (%Ls)',416$this->authorPHIDs);417}418419if ($this->explicitUploads !== null) {420$where[] = qsprintf(421$conn,422'f.isExplicitUpload = %d',423(int)$this->explicitUploads);424}425426if ($this->transforms !== null) {427$clauses = array();428foreach ($this->transforms as $transform) {429if ($transform['transform'] === true) {430$clauses[] = qsprintf(431$conn,432'(t.originalPHID = %s)',433$transform['originalPHID']);434} else {435$clauses[] = qsprintf(436$conn,437'(t.originalPHID = %s AND t.transform = %s)',438$transform['originalPHID'],439$transform['transform']);440}441}442$where[] = qsprintf($conn, '%LO', $clauses);443}444445if ($this->dateCreatedAfter !== null) {446$where[] = qsprintf(447$conn,448'f.dateCreated >= %d',449$this->dateCreatedAfter);450}451452if ($this->dateCreatedBefore !== null) {453$where[] = qsprintf(454$conn,455'f.dateCreated <= %d',456$this->dateCreatedBefore);457}458459if ($this->contentHashes !== null) {460$where[] = qsprintf(461$conn,462'f.contentHash IN (%Ls)',463$this->contentHashes);464}465466if ($this->minLength !== null) {467$where[] = qsprintf(468$conn,469'byteSize >= %d',470$this->minLength);471}472473if ($this->maxLength !== null) {474$where[] = qsprintf(475$conn,476'byteSize <= %d',477$this->maxLength);478}479480if ($this->names !== null) {481$where[] = qsprintf(482$conn,483'name in (%Ls)',484$this->names);485}486487if ($this->isPartial !== null) {488$where[] = qsprintf(489$conn,490'isPartial = %d',491(int)$this->isPartial);492}493494if ($this->isDeleted !== null) {495$where[] = qsprintf(496$conn,497'isDeleted = %d',498(int)$this->isDeleted);499}500501if ($this->builtinKeys !== null) {502$where[] = qsprintf(503$conn,504'builtinKey IN (%Ls)',505$this->builtinKeys);506}507508if ($this->isBuiltin !== null) {509if ($this->isBuiltin) {510$where[] = qsprintf(511$conn,512'builtinKey IS NOT NULL');513} else {514$where[] = qsprintf(515$conn,516'builtinKey IS NULL');517}518}519520if ($this->storageEngines !== null) {521$where[] = qsprintf(522$conn,523'storageEngine IN (%Ls)',524$this->storageEngines);525}526527if ($this->attachedObjectPHIDs !== null) {528$where[] = qsprintf(529$conn,530'attachments.objectPHID IN (%Ls)',531$this->attachedObjectPHIDs);532}533534return $where;535}536537protected function getPrimaryTableAlias() {538return 'f';539}540541public function getQueryApplicationClass() {542return 'PhabricatorFilesApplication';543}544545}546547548