@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
phorge phabricator

Dynamically composite favicons from customizable sources

Summary: Ref T13103. Make favicons customizable, and perform dynamic compositing to add marker to indicate things like "unread messages".

Test Plan: Viewed favicons in Safari, Firefox and Chrome. With unread messages, saw pink dot composited into icon.

Maniphest Tasks: T13103

Differential Revision: https://secure.phabricator.com/D19209

+605 -112
resources/builtin/favicon/dot-pink-64x64.png

This is a binary file and will not be displayed.

resources/builtin/favicon/dot-red-64x64.png

This is a binary file and will not be displayed.

-21
resources/celerity/map.php
··· 16 16 'differential.pkg.js' => 'f6d809c0', 17 17 'diffusion.pkg.css' => 'a2d17c7d', 18 18 'diffusion.pkg.js' => '6134c5a1', 19 - 'favicon.ico' => '30672e08', 20 19 'maniphest.pkg.css' => '4845691a', 21 20 'maniphest.pkg.js' => '4d7e79c8', 22 21 'rsrc/audio/basic/alert.mp3' => '98461568', ··· 270 269 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js' => '54f314a0', 271 270 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js' => 'ab9e0a82', 272 271 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadStaticSource.js' => '6c0e62fa', 273 - 'rsrc/favicons/apple-touch-icon-114x114.png' => '12a24178', 274 - 'rsrc/favicons/apple-touch-icon-120x120.png' => '0d1543c7', 275 - 'rsrc/favicons/apple-touch-icon-144x144.png' => '8043b5a5', 276 - 'rsrc/favicons/apple-touch-icon-152x152.png' => '65905ecd', 277 - 'rsrc/favicons/apple-touch-icon-57x57.png' => '2bfc7b0a', 278 - 'rsrc/favicons/apple-touch-icon-60x60.png' => '8ff52925', 279 - 'rsrc/favicons/apple-touch-icon-72x72.png' => 'a2bb65d6', 280 - 'rsrc/favicons/apple-touch-icon-76x76.png' => '2d061a11', 281 - 'rsrc/favicons/favicon-128.png' => '72f7e812', 282 272 'rsrc/favicons/favicon-16x16.png' => 'fc6275ba', 283 - 'rsrc/favicons/favicon-196x196.png' => '95db275e', 284 - 'rsrc/favicons/favicon-32x32.png' => '5bd18b6c', 285 - 'rsrc/favicons/favicon-96x96.png' => '7242c8e9', 286 - 'rsrc/favicons/favicon-mention.ico' => '1fdd0fa4', 287 - 'rsrc/favicons/favicon-message.ico' => '115bc010', 288 - 'rsrc/favicons/favicon.ico' => 'cdb11121', 289 273 'rsrc/favicons/mask-icon.svg' => 'e132a80f', 290 - 'rsrc/favicons/mstile-144x144.png' => '310c2ee5', 291 - 'rsrc/favicons/mstile-150x150.png' => '74bf5133', 292 - 'rsrc/favicons/mstile-310x150.png' => '4a49d3ee', 293 - 'rsrc/favicons/mstile-310x310.png' => 'a52ab264', 294 - 'rsrc/favicons/mstile-70x70.png' => '5edce7b8', 295 274 'rsrc/image/BFCFDA.png' => 'd5ec91f4', 296 275 'rsrc/image/actions/edit.png' => '2fc41442', 297 276 'rsrc/image/avatar.png' => '17d346a4',
+4 -2
src/__phutil_library_map__.php
··· 2953 2953 'PhabricatorFactObjectDimension' => 'applications/fact/storage/PhabricatorFactObjectDimension.php', 2954 2954 'PhabricatorFactRaw' => 'applications/fact/storage/PhabricatorFactRaw.php', 2955 2955 'PhabricatorFactUpdateIterator' => 'applications/fact/extract/PhabricatorFactUpdateIterator.php', 2956 + 'PhabricatorFaviconRef' => 'applications/files/favicon/PhabricatorFaviconRef.php', 2957 + 'PhabricatorFaviconRefQuery' => 'applications/files/favicon/PhabricatorFaviconRefQuery.php', 2956 2958 'PhabricatorFavoritesApplication' => 'applications/favorites/application/PhabricatorFavoritesApplication.php', 2957 2959 'PhabricatorFavoritesController' => 'applications/favorites/controller/PhabricatorFavoritesController.php', 2958 2960 'PhabricatorFavoritesMainMenuBarExtension' => 'applications/favorites/engineextension/PhabricatorFavoritesMainMenuBarExtension.php', ··· 4331 4333 'PhabricatorSystemDAO' => 'applications/system/storage/PhabricatorSystemDAO.php', 4332 4334 'PhabricatorSystemDestructionGarbageCollector' => 'applications/system/garbagecollector/PhabricatorSystemDestructionGarbageCollector.php', 4333 4335 'PhabricatorSystemDestructionLog' => 'applications/system/storage/PhabricatorSystemDestructionLog.php', 4334 - 'PhabricatorSystemFaviconController' => 'applications/system/controller/PhabricatorSystemFaviconController.php', 4335 4336 'PhabricatorSystemReadOnlyController' => 'applications/system/controller/PhabricatorSystemReadOnlyController.php', 4336 4337 'PhabricatorSystemRemoveDestroyWorkflow' => 'applications/system/management/PhabricatorSystemRemoveDestroyWorkflow.php', 4337 4338 'PhabricatorSystemRemoveLogWorkflow' => 'applications/system/management/PhabricatorSystemRemoveLogWorkflow.php', ··· 8512 8513 'PhabricatorFactObjectDimension' => 'PhabricatorFactDimension', 8513 8514 'PhabricatorFactRaw' => 'PhabricatorFactDAO', 8514 8515 'PhabricatorFactUpdateIterator' => 'PhutilBufferedIterator', 8516 + 'PhabricatorFaviconRef' => 'Phobject', 8517 + 'PhabricatorFaviconRefQuery' => 'Phobject', 8515 8518 'PhabricatorFavoritesApplication' => 'PhabricatorApplication', 8516 8519 'PhabricatorFavoritesController' => 'PhabricatorController', 8517 8520 'PhabricatorFavoritesMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension', ··· 10142 10145 'PhabricatorSystemDAO' => 'PhabricatorLiskDAO', 10143 10146 'PhabricatorSystemDestructionGarbageCollector' => 'PhabricatorGarbageCollector', 10144 10147 'PhabricatorSystemDestructionLog' => 'PhabricatorSystemDAO', 10145 - 'PhabricatorSystemFaviconController' => 'PhabricatorController', 10146 10148 'PhabricatorSystemReadOnlyController' => 'PhabricatorController', 10147 10149 'PhabricatorSystemRemoveDestroyWorkflow' => 'PhabricatorSystemRemoveWorkflow', 10148 10150 'PhabricatorSystemRemoveLogWorkflow' => 'PhabricatorSystemRemoveWorkflow',
+4
src/applications/config/option/PhabricatorUIConfigOptions.php
··· 64 64 "Phabricator logo in the site header.\n\n". 65 65 " - **Wordmark**: Choose new text to display next to the logo. ". 66 66 "By default, the header displays //Phabricator//.\n\n")), 67 + $this->newOption('ui.favicons', 'wild', array()) 68 + ->setSummary(pht('Customize favicons.')) 69 + ->setDescription(pht('Customize favicons.')) 70 + ->setLocked(true), 67 71 $this->newOption('ui.footer-items', $footer_type, array()) 68 72 ->setSummary( 69 73 pht(
+447
src/applications/files/favicon/PhabricatorFaviconRef.php
··· 1 + <?php 2 + 3 + final class PhabricatorFaviconRef extends Phobject { 4 + 5 + private $viewer; 6 + private $width; 7 + private $height; 8 + private $emblems; 9 + private $uri; 10 + private $cacheKey; 11 + 12 + public function __construct() { 13 + $this->emblems = array(null, null, null, null); 14 + } 15 + 16 + public function setViewer(PhabricatorUser $viewer) { 17 + $this->viewer = $viewer; 18 + return $this; 19 + } 20 + 21 + public function getViewer() { 22 + return $this->viewer; 23 + } 24 + 25 + public function setWidth($width) { 26 + $this->width = $width; 27 + return $this; 28 + } 29 + 30 + public function getWidth() { 31 + return $this->width; 32 + } 33 + 34 + public function setHeight($height) { 35 + $this->height = $height; 36 + return $this; 37 + } 38 + 39 + public function getHeight() { 40 + return $this->height; 41 + } 42 + 43 + public function setEmblems(array $emblems) { 44 + if (count($emblems) !== 4) { 45 + throw new Exception( 46 + pht( 47 + 'Expected four elements in icon emblem list. To omit an emblem, '. 48 + 'pass "null".')); 49 + } 50 + 51 + $this->emblems = $emblems; 52 + return $this; 53 + } 54 + 55 + public function getEmblems() { 56 + return $this->emblems; 57 + } 58 + 59 + public function setURI($uri) { 60 + $this->uri = $uri; 61 + return $this; 62 + } 63 + 64 + public function getURI() { 65 + return $this->uri; 66 + } 67 + 68 + public function setCacheKey($cache_key) { 69 + $this->cacheKey = $cache_key; 70 + return $this; 71 + } 72 + 73 + public function getCacheKey() { 74 + return $this->cacheKey; 75 + } 76 + 77 + public function newDigest() { 78 + return PhabricatorHash::digestForIndex(serialize($this->toDictionary())); 79 + } 80 + 81 + public function toDictionary() { 82 + return array( 83 + 'width' => $this->width, 84 + 'height' => $this->height, 85 + 'emblems' => $this->emblems, 86 + ); 87 + } 88 + 89 + public static function newConfigurationDigest() { 90 + $all_resources = self::getAllResources(); 91 + 92 + // Because we need to access this cache on every page, it's very sticky. 93 + // Try to dirty it automatically if any relevant configuration changes. 94 + $inputs = array( 95 + 'resources' => $all_resources, 96 + 'prod' => PhabricatorEnv::getProductionURI('/'), 97 + 'cdn' => PhabricatorEnv::getEnvConfig('security.alternate-file-domain'), 98 + 'havepng' => function_exists('imagepng'), 99 + ); 100 + 101 + return PhabricatorHash::digestForIndex(serialize($inputs)); 102 + } 103 + 104 + private static function getAllResources() { 105 + $custom_resources = PhabricatorEnv::getEnvConfig('ui.favicons'); 106 + 107 + foreach ($custom_resources as $key => $custom_resource) { 108 + $custom_resources[$key] = array( 109 + 'source-type' => 'file', 110 + 'default' => false, 111 + ) + $custom_resource; 112 + } 113 + 114 + $builtin_resources = self::getBuiltinResources(); 115 + 116 + return array_merge($builtin_resources, $custom_resources); 117 + } 118 + 119 + private static function getBuiltinResources() { 120 + return array( 121 + array( 122 + 'source-type' => 'builtin', 123 + 'source' => 'favicon/default-76x76.png', 124 + 'version' => 1, 125 + 'width' => 76, 126 + 'height' => 76, 127 + 'default' => true, 128 + ), 129 + array( 130 + 'source-type' => 'builtin', 131 + 'source' => 'favicon/default-120x120.png', 132 + 'version' => 1, 133 + 'width' => 120, 134 + 'height' => 120, 135 + 'default' => true, 136 + ), 137 + array( 138 + 'source-type' => 'builtin', 139 + 'source' => 'favicon/default-128x128.png', 140 + 'version' => 1, 141 + 'width' => 128, 142 + 'height' => 128, 143 + 'default' => true, 144 + ), 145 + array( 146 + 'source-type' => 'builtin', 147 + 'source' => 'favicon/default-152x152.png', 148 + 'version' => 1, 149 + 'width' => 152, 150 + 'height' => 152, 151 + 'default' => true, 152 + ), 153 + array( 154 + 'source-type' => 'builtin', 155 + 'source' => 'favicon/dot-pink-64x64.png', 156 + 'version' => 1, 157 + 'width' => 64, 158 + 'height' => 64, 159 + 'emblem' => 'dot-pink', 160 + 'default' => true, 161 + ), 162 + array( 163 + 'source-type' => 'builtin', 164 + 'source' => 'favicon/dot-red-64x64.png', 165 + 'version' => 1, 166 + 'width' => 64, 167 + 'height' => 64, 168 + 'emblem' => 'dot-red', 169 + 'default' => true, 170 + ), 171 + ); 172 + } 173 + 174 + public function newURI() { 175 + $dst_w = $this->getWidth(); 176 + $dst_h = $this->getHeight(); 177 + 178 + $template = $this->newTemplateFile(null, $dst_w, $dst_h); 179 + $template_file = $template['file']; 180 + 181 + $cache = $this->loadCachedFile($template_file); 182 + if ($cache) { 183 + return $cache->getViewURI(); 184 + } 185 + 186 + $data = $this->newCompositedFavicon($template); 187 + 188 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 189 + 190 + $caught = null; 191 + try { 192 + $favicon_file = $this->newFaviconFile($data); 193 + 194 + $xform = id(new PhabricatorTransformedFile()) 195 + ->setOriginalPHID($template_file->getPHID()) 196 + ->setTransformedPHID($favicon_file->getPHID()) 197 + ->setTransform($this->getCacheKey()); 198 + 199 + try { 200 + $xform->save(); 201 + } catch (AphrontDuplicateKeyQueryException $ex) { 202 + unset($unguarded); 203 + 204 + $cache = $this->loadCachedFile($template_file); 205 + if (!$cache) { 206 + throw $ex; 207 + } 208 + 209 + id(new PhabricatorDestructionEngine()) 210 + ->destroyObject($favicon_file); 211 + 212 + return $cache->getViewURI(); 213 + } 214 + } catch (Exception $ex) { 215 + $caught = $ex; 216 + } 217 + 218 + unset($unguarded); 219 + 220 + if ($caught) { 221 + throw $caught; 222 + } 223 + 224 + return $favicon_file->getViewURI(); 225 + } 226 + 227 + private function loadCachedFile(PhabricatorFile $template_file) { 228 + $viewer = $this->getViewer(); 229 + 230 + $xform = id(new PhabricatorTransformedFile())->loadOneWhere( 231 + 'originalPHID = %s AND transform = %s', 232 + $template_file->getPHID(), 233 + $this->getCacheKey()); 234 + if (!$xform) { 235 + return null; 236 + } 237 + 238 + return id(new PhabricatorFileQuery()) 239 + ->setViewer($viewer) 240 + ->withPHIDs(array($xform->getTransformedPHID())) 241 + ->executeOne(); 242 + } 243 + 244 + private function newCompositedFavicon($template) { 245 + $dst_w = $this->getWidth(); 246 + $dst_h = $this->getHeight(); 247 + $src_w = $template['width']; 248 + $src_h = $template['height']; 249 + 250 + $template_data = $template['file']->loadFileData(); 251 + 252 + if (!function_exists('imagecreatefromstring')) { 253 + return $template_data; 254 + } 255 + 256 + $src = @imagecreatefromstring($template_data); 257 + if (!$src) { 258 + return $template_data; 259 + } 260 + 261 + $dst = imagecreatetruecolor($dst_w, $dst_h); 262 + imagesavealpha($dst, true); 263 + 264 + $transparent = imagecolorallocatealpha($dst, 0, 255, 0, 127); 265 + imagefill($dst, 0, 0, $transparent); 266 + 267 + imagecopyresampled( 268 + $dst, 269 + $src, 270 + 0, 271 + 0, 272 + 0, 273 + 0, 274 + $dst_w, 275 + $dst_h, 276 + $src_w, 277 + $src_h); 278 + 279 + // Now, copy any icon emblems on top of the image. These are dots or other 280 + // marks used to indicate status information. 281 + $emblem_w = (int)floor(min($dst_w, $dst_h) / 2); 282 + $emblem_h = $emblem_w; 283 + foreach ($this->emblems as $key => $emblem) { 284 + if ($emblem === null) { 285 + continue; 286 + } 287 + 288 + $emblem_template = $this->newTemplateFile( 289 + $emblem, 290 + $emblem_w, 291 + $emblem_h); 292 + 293 + switch ($key) { 294 + case 0: 295 + $emblem_x = $dst_w - $emblem_w; 296 + $emblem_y = 0; 297 + break; 298 + case 1: 299 + $emblem_x = $dst_w - $emblem_w; 300 + $emblem_y = $dst_h - $emblem_h; 301 + break; 302 + case 2: 303 + $emblem_x = 0; 304 + $emblem_y = $dst_h - $emblem_h; 305 + break; 306 + case 3: 307 + $emblem_x = 0; 308 + $emblem_y = 0; 309 + break; 310 + } 311 + 312 + $emblem_data = $emblem_template['file']->loadFileData(); 313 + 314 + $src = @imagecreatefromstring($emblem_data); 315 + if (!$src) { 316 + continue; 317 + } 318 + 319 + imagecopyresampled( 320 + $dst, 321 + $src, 322 + $emblem_x, 323 + $emblem_y, 324 + 0, 325 + 0, 326 + $emblem_w, 327 + $emblem_h, 328 + $emblem_template['width'], 329 + $emblem_template['height']); 330 + } 331 + 332 + return PhabricatorImageTransformer::saveImageDataInAnyFormat( 333 + $dst, 334 + 'image/png'); 335 + } 336 + 337 + private function newTemplateFile($emblem, $width, $height) { 338 + $all_resources = self::getAllResources(); 339 + 340 + $scores = array(); 341 + $ratio = $width / $height; 342 + foreach ($all_resources as $key => $resource) { 343 + // We can't use an emblem resource for a different emblem, nor for an 344 + // icon base. We also can't use an icon base as an emblem. That is, if 345 + // we're looking for a picture of a red dot, we have to actually find 346 + // a red dot, not just any image which happens to have a similar size. 347 + if (idx($resource, 'emblem') !== $emblem) { 348 + continue; 349 + } 350 + 351 + $resource_width = $resource['width']; 352 + $resource_height = $resource['height']; 353 + 354 + // Never use a resource with a different aspect ratio. 355 + if (($resource_width / $resource_height) !== $ratio) { 356 + continue; 357 + } 358 + 359 + // Try to use custom resources instead of default resources. 360 + if ($resource['default']) { 361 + $default_score = 1; 362 + } else { 363 + $default_score = 0; 364 + } 365 + 366 + $width_diff = ($resource_width - $width); 367 + 368 + // If we have to resize an image, we'd rather scale a larger image down 369 + // than scale a smaller image up. 370 + if ($width_diff < 0) { 371 + $scale_score = 1; 372 + } else { 373 + $scale_score = 0; 374 + } 375 + 376 + // Otherwise, we'd rather scale an image a little bit (ideally, zero) 377 + // than scale an image a lot. 378 + $width_score = abs($width_diff); 379 + 380 + $scores[$key] = id(new PhutilSortVector()) 381 + ->addInt($default_score) 382 + ->addInt($scale_score) 383 + ->addInt($width_score); 384 + } 385 + 386 + if (!$scores) { 387 + if ($emblem === null) { 388 + throw new Exception( 389 + pht( 390 + 'Found no background template resource for dimensions %dx%d.', 391 + $width, 392 + $height)); 393 + } else { 394 + throw new Exception( 395 + pht( 396 + 'Found no template resource (for emblem "%s") with dimensions '. 397 + '%dx%d.', 398 + $emblem, 399 + $width, 400 + $height)); 401 + } 402 + } 403 + 404 + $scores = msortv($scores, 'getSelf'); 405 + $best_score = head_key($scores); 406 + 407 + $viewer = $this->getViewer(); 408 + 409 + $resource = $all_resources[$best_score]; 410 + if ($resource['source-type'] === 'builtin') { 411 + $file = PhabricatorFile::loadBuiltin($viewer, $resource['source']); 412 + if (!$file) { 413 + throw new Exception( 414 + pht( 415 + 'Failed to load favicon template builtin "%s".', 416 + $resource['source'])); 417 + } 418 + } else { 419 + $file = id(new PhabricatorFileQuery()) 420 + ->setViewer($viewer) 421 + ->withPHIDs(array($resource['source'])) 422 + ->executeOne(); 423 + if (!$file) { 424 + throw new Exception( 425 + pht( 426 + 'Failed to load favicon template with PHID "%s".', 427 + $resource['source'])); 428 + } 429 + } 430 + 431 + return array( 432 + 'width' => $resource['width'], 433 + 'height' => $resource['height'], 434 + 'file' => $file, 435 + ); 436 + } 437 + 438 + private function newFaviconFile($data) { 439 + return PhabricatorFile::newFromFileData( 440 + $data, 441 + array( 442 + 'name' => 'favicon', 443 + 'canCDN' => true, 444 + )); 445 + } 446 + 447 + }
+55
src/applications/files/favicon/PhabricatorFaviconRefQuery.php
··· 1 + <?php 2 + 3 + final class PhabricatorFaviconRefQuery extends Phobject { 4 + 5 + private $refs; 6 + 7 + public function withRefs(array $refs) { 8 + assert_instances_of($refs, 'PhabricatorFaviconRef'); 9 + $this->refs = $refs; 10 + return $this; 11 + } 12 + 13 + public function execute() { 14 + $viewer = PhabricatorUser::getOmnipotentUser(); 15 + 16 + $refs = $this->refs; 17 + 18 + $config_digest = PhabricatorFaviconRef::newConfigurationDigest(); 19 + 20 + $ref_map = array(); 21 + foreach ($refs as $ref) { 22 + $ref_digest = $ref->newDigest(); 23 + $ref_key = "favicon({$config_digest},{$ref_digest},8)"; 24 + 25 + $ref 26 + ->setViewer($viewer) 27 + ->setCacheKey($ref_key); 28 + 29 + $ref_map[$ref_key] = $ref; 30 + } 31 + 32 + $cache = PhabricatorCaches::getImmutableCache(); 33 + $ref_hits = $cache->getKeys(array_keys($ref_map)); 34 + 35 + foreach ($ref_hits as $ref_key => $ref_uri) { 36 + $ref_map[$ref_key]->setURI($ref_uri); 37 + unset($ref_map[$ref_key]); 38 + } 39 + 40 + if ($ref_map) { 41 + $new_map = array(); 42 + foreach ($ref_map as $ref_key => $ref) { 43 + $ref_uri = $ref->newURI(); 44 + $ref->setURI($ref_uri); 45 + $new_map[$ref_key] = $ref_uri; 46 + } 47 + 48 + $cache->setKeys($new_map); 49 + } 50 + 51 + return $refs; 52 + } 53 + 54 + 55 + }
+1 -2
src/applications/files/storage/PhabricatorFile.php
··· 1152 1152 1153 1153 $params = array( 1154 1154 'name' => $builtin->getBuiltinDisplayName(), 1155 - 'ttl.relative' => phutil_units('7 days in seconds'), 1156 1155 'canCDN' => true, 1157 1156 'builtin' => $key, 1158 1157 ); ··· 1648 1647 public function getFieldValuesForConduit() { 1649 1648 return array( 1650 1649 'name' => $this->getName(), 1651 - 'dataURI' => $this->getCDNURI(), 1650 + 'dataURI' => $this->getCDNURI('data'), 1652 1651 'size' => (int)$this->getByteSize(), 1653 1652 ); 1654 1653 }
-1
src/applications/system/application/PhabricatorSystemApplication.php
··· 26 26 '/readonly/' => array( 27 27 '(?P<reason>[^/]+)/' => 'PhabricatorSystemReadOnlyController', 28 28 ), 29 - '/favicon.ico' => 'PhabricatorSystemFaviconController', 30 29 ); 31 30 } 32 31
-19
src/applications/system/controller/PhabricatorSystemFaviconController.php
··· 1 - <?php 2 - 3 - final class PhabricatorSystemFaviconController extends PhabricatorController { 4 - 5 - public function shouldRequireLogin() { 6 - return false; 7 - } 8 - 9 - public function processRequest() { 10 - $webroot = dirname(phutil_get_library_root('phabricator')).'/webroot/'; 11 - $content = Filesystem::readFile($webroot.'/rsrc/favicons/favicon.ico'); 12 - 13 - return id(new AphrontFileResponse()) 14 - ->setContent($content) 15 - ->setMimeType('image/x-icon') 16 - ->setCacheDurationInSeconds(phutil_units('24 hours in seconds')) 17 - ->setCanCDN(true); 18 - } 19 - }
+68 -46
src/view/page/PhabricatorBarePageView.php
··· 71 71 )); 72 72 } 73 73 74 + $referrer_tag = phutil_tag( 75 + 'meta', 76 + array( 77 + 'name' => 'referrer', 78 + 'content' => 'no-referrer', 79 + )); 80 + 81 + 74 82 $mask_icon = phutil_tag( 75 83 'link', 76 84 array( ··· 80 88 '/rsrc/favicons/mask-icon.svg'), 81 89 )); 82 90 83 - $icon_tag_76 = phutil_tag( 84 - 'link', 85 - array( 86 - 'rel' => 'apple-touch-icon', 87 - 'href' => celerity_get_resource_uri( 88 - '/rsrc/favicons/apple-touch-icon-76x76.png'), 89 - )); 90 - 91 - $icon_tag_120 = phutil_tag( 92 - 'link', 93 - array( 94 - 'rel' => 'apple-touch-icon', 95 - 'sizes' => '120x120', 96 - 'href' => celerity_get_resource_uri( 97 - '/rsrc/favicons/apple-touch-icon-120x120.png'), 98 - )); 99 - 100 - $icon_tag_152 = phutil_tag( 101 - 'link', 102 - array( 103 - 'rel' => 'apple-touch-icon', 104 - 'sizes' => '152x152', 105 - 'href' => celerity_get_resource_uri( 106 - '/rsrc/favicons/apple-touch-icon-152x152.png'), 107 - )); 108 - 109 - $favicon_tag = phutil_tag( 110 - 'link', 111 - array( 112 - 'id' => 'favicon', 113 - 'rel' => 'shortcut icon', 114 - 'href' => celerity_get_resource_uri( 115 - '/rsrc/favicons/favicon.ico'), 116 - )); 117 - 118 - $referrer_tag = phutil_tag( 119 - 'meta', 120 - array( 121 - 'name' => 'referrer', 122 - 'content' => 'no-referrer', 123 - )); 91 + $favicon_links = $this->newFavicons(); 124 92 125 93 $response = CelerityAPI::getStaticResourceResponse(); 126 94 ··· 136 104 } 137 105 138 106 return hsprintf( 139 - '%s%s%s%s%s%s%s%s', 107 + '%s%s%s%s%s', 140 108 $viewport_tag, 141 109 $mask_icon, 142 - $icon_tag_76, 143 - $icon_tag_120, 144 - $icon_tag_152, 145 - $favicon_tag, 110 + $favicon_links, 146 111 $referrer_tag, 147 112 $response->renderResourcesOfType('css')); 148 113 } ··· 154 119 protected function getTail() { 155 120 $response = CelerityAPI::getStaticResourceResponse(); 156 121 return $response->renderResourcesOfType('js'); 122 + } 123 + 124 + private function newFavicons() { 125 + $favicon_refs = array( 126 + array( 127 + 'rel' => 'apple-touch-icon', 128 + 'sizes' => '76x76', 129 + 'width' => 76, 130 + 'height' => 76, 131 + ), 132 + array( 133 + 'rel' => 'apple-touch-icon', 134 + 'sizes' => '120x120', 135 + 'width' => 120, 136 + 'height' => 120, 137 + ), 138 + array( 139 + 'rel' => 'apple-touch-icon', 140 + 'sizes' => '152x152', 141 + 'width' => 152, 142 + 'height' => 152, 143 + ), 144 + array( 145 + 'rel' => 'icon', 146 + 'id' => 'favicon', 147 + 'width' => 64, 148 + 'height' => 64, 149 + ), 150 + ); 151 + 152 + $fetch_refs = array(); 153 + foreach ($favicon_refs as $key => $spec) { 154 + $ref = id(new PhabricatorFaviconRef()) 155 + ->setWidth($spec['width']) 156 + ->setHeight($spec['height']); 157 + 158 + $favicon_refs[$key]['ref'] = $ref; 159 + $fetch_refs[] = $ref; 160 + } 161 + 162 + id(new PhabricatorFaviconRefQuery()) 163 + ->withRefs($fetch_refs) 164 + ->execute(); 165 + 166 + $favicon_links = array(); 167 + foreach ($favicon_refs as $spec) { 168 + $favicon_links[] = phutil_tag( 169 + 'link', 170 + array( 171 + 'rel' => $spec['rel'], 172 + 'sizes' => idx($spec, 'sizes'), 173 + 'id' => idx($spec, 'id'), 174 + 'href' => $spec['ref']->getURI(), 175 + )); 176 + } 177 + 178 + return $favicon_links; 157 179 } 158 180 159 181 }
+26 -21
src/view/page/menu/PhabricatorMainMenuView.php
··· 23 23 return $this->controller; 24 24 } 25 25 26 - private function getFaviconURI($type = null) { 27 - switch ($type) { 28 - case 'message': 29 - return celerity_get_resource_uri('/rsrc/favicons/favicon-message.ico'); 30 - case 'mention': 31 - return celerity_get_resource_uri('/rsrc/favicons/favicon-mention.ico'); 32 - default: 33 - return celerity_get_resource_uri('/rsrc/favicons/favicon.ico'); 34 - } 26 + private static function getFavicons() { 27 + $refs = array(); 28 + 29 + $refs['favicon'] = id(new PhabricatorFaviconRef()) 30 + ->setWidth(64) 31 + ->setHeight(64); 32 + 33 + $refs['message_favicon'] = id(new PhabricatorFaviconRef()) 34 + ->setWidth(64) 35 + ->setHeight(64) 36 + ->setEmblems( 37 + array( 38 + 'dot-pink', 39 + null, 40 + null, 41 + null, 42 + )); 43 + 44 + id(new PhabricatorFaviconRefQuery()) 45 + ->withRefs($refs) 46 + ->execute(); 47 + 48 + return mpull($refs, 'getURI'); 35 49 } 36 50 37 51 public function render() { ··· 428 442 'countType' => $conpherence_data['countType'], 429 443 'countNumber' => $message_count_number, 430 444 'unreadClass' => 'message-unread', 431 - 'favicon' => $this->getFaviconURI('default'), 432 - 'message_favicon' => $this->getFaviconURI('message'), 433 - 'mention_favicon' => $this->getFaviconURI('mention'), 434 - )); 445 + ) + self::getFavicons()); 435 446 436 447 $message_notification_dropdown = javelin_tag( 437 448 'div', ··· 509 520 'countType' => $notification_data['countType'], 510 521 'countNumber' => $count_number, 511 522 'unreadClass' => 'alert-unread', 512 - 'favicon' => $this->getFaviconURI('default'), 513 - 'message_favicon' => $this->getFaviconURI('message'), 514 - 'mention_favicon' => $this->getFaviconURI('mention'), 515 - )); 523 + ) + self::getFavicons()); 516 524 517 525 $notification_dropdown = javelin_tag( 518 526 'div', ··· 594 602 'countType' => null, 595 603 'countNumber' => null, 596 604 'unreadClass' => 'setup-unread', 597 - 'favicon' => $this->getFaviconURI('default'), 598 - 'message_favicon' => $this->getFaviconURI('message'), 599 - 'mention_favicon' => $this->getFaviconURI('mention'), 600 - )); 605 + ) + self::getFavicons()); 601 606 602 607 $setup_notification_dropdown = javelin_tag( 603 608 'div',
webroot/favicon.ico

This is a binary file and will not be displayed.

webroot/rsrc/favicons/apple-touch-icon-114x114.png

This is a binary file and will not be displayed.

webroot/rsrc/favicons/apple-touch-icon-120x120.png resources/builtin/favicon/default-120x120.png
webroot/rsrc/favicons/apple-touch-icon-144x144.png

This is a binary file and will not be displayed.

webroot/rsrc/favicons/apple-touch-icon-152x152.png resources/builtin/favicon/default-152x152.png
webroot/rsrc/favicons/apple-touch-icon-57x57.png

This is a binary file and will not be displayed.

webroot/rsrc/favicons/apple-touch-icon-60x60.png

This is a binary file and will not be displayed.

webroot/rsrc/favicons/apple-touch-icon-72x72.png

This is a binary file and will not be displayed.

webroot/rsrc/favicons/apple-touch-icon-76x76.png resources/builtin/favicon/default-76x76.png
webroot/rsrc/favicons/favicon-128.png resources/builtin/favicon/default-128x128.png
webroot/rsrc/favicons/favicon-196x196.png

This is a binary file and will not be displayed.

webroot/rsrc/favicons/favicon-32x32.png

This is a binary file and will not be displayed.

webroot/rsrc/favicons/favicon-96x96.png

This is a binary file and will not be displayed.

webroot/rsrc/favicons/favicon-mention.ico

This is a binary file and will not be displayed.

webroot/rsrc/favicons/favicon-message.ico

This is a binary file and will not be displayed.

webroot/rsrc/favicons/favicon.ico

This is a binary file and will not be displayed.

webroot/rsrc/favicons/mstile-144x144.png

This is a binary file and will not be displayed.

webroot/rsrc/favicons/mstile-150x150.png

This is a binary file and will not be displayed.

webroot/rsrc/favicons/mstile-310x150.png

This is a binary file and will not be displayed.

webroot/rsrc/favicons/mstile-310x310.png

This is a binary file and will not be displayed.

webroot/rsrc/favicons/mstile-70x70.png

This is a binary file and will not be displayed.