diff --git a/resources/builtin/missing.png b/resources/builtin/missing.png new file mode 100644 index 0000000000000000000000000000000000000000..f301d02626b7fac1ea1122bc5957865fbe877ffb GIT binary patch literal 3781 zcmeAS@N?(olHy`uVBq!ia0y~yU}|7sV0^&A#K6F?YqR%e1_lPn64!{5;QX|b^2DN4 z2FH~Aq*MjZ+{EB@w}FfdWj%4dKI|^3?N`*Ur~^l zoSj;tkd&I9nP;o?e)oPQh0GLNrEpVU1K$GY)Qn7zs-o23D!-8As_bOT6eW8*1)B=1 zirj+S)RIJnirk#MVyg;UC9t_xdBs*BVSOb9u#%E&TP292B76fBob!uP6-@Pvb(0MY z4HeAH^bAc*&CCpS6pRcEjr0wS^bO2(4Na^}&8&>f6`(-LPQj)qCCw_x#SLm#QA(Pv zQbtKhft9{~d3m{Bxv^e;QM$gNrKKgv3?n1mqLeh<;>x^|#0uTKVr7^KE~&-IMVSR9 znfZANAQKal@=Hr>m6V{yR^%4Ijm;~D22*l=u6{*gfxe-h0mw@bzvdSB`r=obn+sQ6 zToROua4%Mai$f|4QuTvUi}Op1l2e0H)09DODzI`cN=+=uFAB-e&w+(vKt_H^esM;A zfr4|enSzFIVsd64NI(;;!`Ii!Gq1QLF)umQ)5TV)BDX*-xZPro(>$o&6x?pHz^PZ?PQgYW6tzfE4HF7-aRV{IsS=bz?GzA6KQ%AK zR;ftI-mc@F!Z|A3Pmk}ZdNf2mh0_u0#$^EOlw2dLJ zMa8pfZ`}6VZ~uQvu1!i(n(OEPZ*ngaBb(cUM~{@cZoe#qSm-{mY0`*|MqQ?%FoB28oqq_^5xE* zJLivYdNkkL+gskgPDiY})#+eb69aR2W!tr_-X2UPwQEI@#Vqa!Awj%J+c4KKB)ig_Wp=!Gz$|?Pu!V(Z_BF7 z%E~hS{`q6G>J5hphk-$r=OmNaXE{E6`t<3-(yXmkC3)Ne2?;D!d*iNrfAi+exw+QP z^Bt{hY~IudFJx$DU|w8OSa>mOtDm2r(#F-RS38H>*ZeRrHJy4gB}atAz~I!cvfX#1 z)?UjnnP*#VR$_YW{*DkGvAa#oED{n+aGfYMzs# z)?RyCcK!PGcD=pT-}R0?no)Zdr19?$X~ww7NXcJU@~?bKb7o+?KtjTos=aZ$ z@Al2-=H_mGJ$v@-pEdhJv{vdvf_O{R+MhpvF8uiY!Gi;~<_-;whHN`7zZB^{dhqDq zzki(;3Vbd7z{MgV(f#l2eD9@~9z1&aGSh5!>2Ym=goHi3a=*$JZ{EB);^QM@4g-U8 z%d@qvzARx2pLO=xlP62A&v$HSEbP2+Y(sTXk&%fo`)p<=o*u&kJ7eDc_)$?%n3ksI zIVo;^dc8wK<42qD;FTN&i!ZLYnq@DxeA%)+j6CL34o@yHcFEX$GpA?I`nbJW+i(9> zac|I`+V#BwWU^iC<(EY}W3;ADQhCX45w_@5TY^ABLeHWdH@&wpu}J(_`)m8-7v1-N zyDv`W`=$j-QhIyi)?dx4{r_)o-1_7Cf)8f2bsv3n=8TWs{C>7-T`3j`iP~D}Ubn4L zy)V|STla0h@XD~&xw*Nrva)B-o%8ec{pqA4kdQDX=|=BF^Hi-OmMjeT5M^>lWZvom<74;JHqTT!nNVB5qx^l`(x8>sU;n*)v4MeEnft@} zcs84iyu5W6GfH;HR~eOa>SHz?xmK%sVe zx_M^!yIK-^cn&`MAPGVs0e4fA7IHs+|7>Sq ZW{BikuF2CiMH$qw^mO%eS?83{1OQz?(<}f0 literal 0 HcmV?d00001 diff --git a/src/applications/files/query/PhabricatorFileQuery.php b/src/applications/files/query/PhabricatorFileQuery.php index d5731ac13b..5a110afaf9 100644 --- a/src/applications/files/query/PhabricatorFileQuery.php +++ b/src/applications/files/query/PhabricatorFileQuery.php @@ -7,6 +7,7 @@ final class PhabricatorFileQuery private $phids; private $authorPHIDs; private $explicitUploads; + private $transforms; public function withIDs(array $ids) { $this->ids = $ids; @@ -23,6 +24,21 @@ final class PhabricatorFileQuery return $this; } + 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; + } + public function showOnlyExplicitUploads($explicit_uploads) { $this->explicitUploads = $explicit_uploads; return $this; @@ -34,8 +50,9 @@ final class PhabricatorFileQuery $data = queryfx_all( $conn_r, - 'SELECT * FROM %T f %Q %Q %Q', + 'SELECT * FROM %T f %Q %Q %Q %Q', $table->getTableName(), + $this->buildJoinClause($conn_r), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); @@ -43,6 +60,19 @@ final class PhabricatorFileQuery return $table->loadAllFromArray($data); } + 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); + } + private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); @@ -51,31 +81,47 @@ final class PhabricatorFileQuery if ($this->ids) { $where[] = qsprintf( $conn_r, - 'id IN (%Ld)', + 'f.id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, - 'phid IN (%Ls)', + 'f.phid IN (%Ls)', $this->phids); } if ($this->authorPHIDs) { $where[] = qsprintf( $conn_r, - 'authorPHID IN (%Ls)', + 'f.authorPHID IN (%Ls)', $this->authorPHIDs); } if ($this->explicitUploads) { $where[] = qsprintf( $conn_r, - 'isExplicitUpload = true'); + 'f.isExplicitUpload = true'); + } + + if ($this->transforms) { + $clauses = array(); + foreach ($this->transforms as $transform) { + $clauses[] = qsprintf( + $conn_r, + '(t.originalPHID = %s AND t.transform = %s)', + $transform['originalPHID'], + $transform['transform']); + } + $where[] = qsprintf($conn_r, '(%Q)', implode(') OR (', $clauses)); } return $this->formatWhereClause($where); } + protected function getPagingColumn() { + return 'f.id'; + } + } diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 888d49234c..732ede3de2 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -682,6 +682,89 @@ final class PhabricatorFile extends PhabricatorFileDAO } + /** + * Load (or build) the {@class:PhabricatorFile} objects for builtin file + * resources. The builtin mechanism allows files shipped with Phabricator + * to be treated like normal files so that APIs do not need to special case + * things like default images or deleted files. + * + * Builtins are located in `resources/builtin/` and identified by their + * name. + * + * @param PhabricatorUser Viewing user. + * @param list List of builtin file names. + * @return dict Dictionary of named builtins. + */ + public static function loadBuiltins(PhabricatorUser $user, array $names) { + $specs = array(); + foreach ($names as $name) { + $specs[] = array( + 'originalPHID' => PhabricatorPHIDConstants::PHID_VOID, + 'transform' => 'builtin:'.$name, + ); + } + + $files = id(new PhabricatorFileQuery()) + ->setViewer($user) + ->withTransforms($specs) + ->execute(); + + $files = mpull($files, null, 'getName'); + + $root = dirname(phutil_get_library_root('phabricator')); + $root = $root.'/resources/builtin/'; + + $build = array(); + foreach ($names as $name) { + if (isset($files[$name])) { + continue; + } + + // This is just a sanity check to prevent loading arbitrary files. + if (basename($name) != $name) { + throw new Exception("Invalid builtin name '{$name}'!"); + } + + $path = $root.$name; + + if (!Filesystem::pathExists($path)) { + throw new Exception("Builtin '{$path}' does not exist!"); + } + + $data = Filesystem::readFile($path); + $params = array( + 'name' => $name, + 'ttl' => time() + (60 * 60 * 24 * 7), + ); + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $file = PhabricatorFile::newFromFileData($data, $params); + $xform = id(new PhabricatorTransformedFile()) + ->setOriginalPHID(PhabricatorPHIDConstants::PHID_VOID) + ->setTransform('builtin:'.$name) + ->setTransformedPHID($file->getPHID()) + ->save(); + unset($unguarded); + + $files[$name] = $file; + } + + return $files; + } + + + /** + * Convenience wrapper for @{method:loadBuiltins}. + * + * @param PhabricatorUser Viewing user. + * @param string Single builtin name to load. + * @return PhabricatorFile Corresponding builtin file. + */ + public static function loadBuiltin(PhabricatorUser $user, $name) { + return idx(self::loadBuiltins($user, array($name)), $name); + } + + /* -( PhabricatorPolicyInterface Implementation )-------------------------- */ diff --git a/src/applications/phid/PhabricatorPHIDConstants.php b/src/applications/phid/PhabricatorPHIDConstants.php index 6ed089f91f..5860ca1c5b 100644 --- a/src/applications/phid/PhabricatorPHIDConstants.php +++ b/src/applications/phid/PhabricatorPHIDConstants.php @@ -44,4 +44,7 @@ final class PhabricatorPHIDConstants { const PHID_TYPE_XCMT = 'XCMT'; const PHID_TYPE_XUSR = 'XUSR'; + const PHID_TYPE_VOID = 'VOID'; + const PHID_VOID = 'PHID-VOID-00000000000000000000'; + } diff --git a/src/applications/pholio/query/PholioMockQuery.php b/src/applications/pholio/query/PholioMockQuery.php index d222fed29b..d5ce886eba 100644 --- a/src/applications/pholio/query/PholioMockQuery.php +++ b/src/applications/pholio/query/PholioMockQuery.php @@ -129,7 +129,11 @@ final class PholioMockQuery } foreach ($all_images as $image) { - $image->attachFile($all_files[$image->getFilePHID()]); + $file = idx($all_files, $image->getFilePHID()); + if (!$file) { + $file = PhabricatorFile::loadBuiltin($this->getViewer(), 'missing.png'); + } + $image->attachFile($file); if ($this->needInlineComments) { $inlines = idx($all_images, $image->getID(), array()); $image->attachInlineComments($inlines); @@ -151,7 +155,11 @@ final class PholioMockQuery $cover_file_phids), null, 'getPHID'); foreach ($mocks as $mock) { - $mock->attachCoverFile($cover_files[$mock->getCoverPHID()]); + $file = idx($cover_files, $mock->getCoverPHID()); + if (!$file) { + $file = PhabricatorFile::loadBuiltin($this->getViewer(), 'missing.png'); + } + $mock->attachCoverFile($file); } }