(stable) Promote 2018 Week 11
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.7 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 5 KiB After Width: | Height: | Size: 5 KiB |
BIN
resources/builtin/favicon/dot-pink-64x64.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
resources/builtin/favicon/dot-red-64x64.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
|
@ -10,13 +10,11 @@ return array(
|
|||
'conpherence.pkg.css' => 'e68cf1fa',
|
||||
'conpherence.pkg.js' => '15191c65',
|
||||
'core.pkg.css' => 'c218ed53',
|
||||
'core.pkg.js' => '8b7400e7',
|
||||
'darkconsole.pkg.js' => '1f9a31bc',
|
||||
'core.pkg.js' => '8581cd02',
|
||||
'differential.pkg.css' => '113e692c',
|
||||
'differential.pkg.js' => 'f6d809c0',
|
||||
'diffusion.pkg.css' => 'a2d17c7d',
|
||||
'diffusion.pkg.js' => '6134c5a1',
|
||||
'favicon.ico' => '30672e08',
|
||||
'maniphest.pkg.css' => '4845691a',
|
||||
'maniphest.pkg.js' => '4d7e79c8',
|
||||
'rsrc/audio/basic/alert.mp3' => '98461568',
|
||||
|
@ -25,7 +23,7 @@ return array(
|
|||
'rsrc/audio/basic/tap.mp3' => 'fc2fd796',
|
||||
'rsrc/audio/basic/ting.mp3' => '17660001',
|
||||
'rsrc/css/aphront/aphront-bars.css' => '231ac33c',
|
||||
'rsrc/css/aphront/dark-console.css' => 'f7b071f1',
|
||||
'rsrc/css/aphront/dark-console.css' => '0e14e8f6',
|
||||
'rsrc/css/aphront/dialog-view.css' => '6bfc244b',
|
||||
'rsrc/css/aphront/list-filter-view.css' => '5d6f0526',
|
||||
'rsrc/css/aphront/multi-column.css' => '84cc6640',
|
||||
|
@ -47,7 +45,7 @@ return array(
|
|||
'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4',
|
||||
'rsrc/css/application/config/config-options.css' => '4615667b',
|
||||
'rsrc/css/application/config/config-template.css' => '8f18fa41',
|
||||
'rsrc/css/application/config/setup-issue.css' => '7dae7f18',
|
||||
'rsrc/css/application/config/setup-issue.css' => '30ee0173',
|
||||
'rsrc/css/application/config/unhandled-exception.css' => '4c96257a',
|
||||
'rsrc/css/application/conpherence/color.css' => 'abb4c358',
|
||||
'rsrc/css/application/conpherence/durable-column.css' => '89ea6bef',
|
||||
|
@ -270,28 +268,8 @@ return array(
|
|||
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js' => '54f314a0',
|
||||
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js' => 'ab9e0a82',
|
||||
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadStaticSource.js' => '6c0e62fa',
|
||||
'rsrc/favicons/apple-touch-icon-114x114.png' => '12a24178',
|
||||
'rsrc/favicons/apple-touch-icon-120x120.png' => '0d1543c7',
|
||||
'rsrc/favicons/apple-touch-icon-144x144.png' => '8043b5a5',
|
||||
'rsrc/favicons/apple-touch-icon-152x152.png' => '65905ecd',
|
||||
'rsrc/favicons/apple-touch-icon-57x57.png' => '2bfc7b0a',
|
||||
'rsrc/favicons/apple-touch-icon-60x60.png' => '8ff52925',
|
||||
'rsrc/favicons/apple-touch-icon-72x72.png' => 'a2bb65d6',
|
||||
'rsrc/favicons/apple-touch-icon-76x76.png' => '2d061a11',
|
||||
'rsrc/favicons/favicon-128.png' => '72f7e812',
|
||||
'rsrc/favicons/favicon-16x16.png' => 'fc6275ba',
|
||||
'rsrc/favicons/favicon-196x196.png' => '95db275e',
|
||||
'rsrc/favicons/favicon-32x32.png' => '5bd18b6c',
|
||||
'rsrc/favicons/favicon-96x96.png' => '7242c8e9',
|
||||
'rsrc/favicons/favicon-mention.ico' => '1fdd0fa4',
|
||||
'rsrc/favicons/favicon-message.ico' => '115bc010',
|
||||
'rsrc/favicons/favicon.ico' => 'cdb11121',
|
||||
'rsrc/favicons/mask-icon.svg' => 'e132a80f',
|
||||
'rsrc/favicons/mstile-144x144.png' => '310c2ee5',
|
||||
'rsrc/favicons/mstile-150x150.png' => '74bf5133',
|
||||
'rsrc/favicons/mstile-310x150.png' => '4a49d3ee',
|
||||
'rsrc/favicons/mstile-310x310.png' => 'a52ab264',
|
||||
'rsrc/favicons/mstile-70x70.png' => '5edce7b8',
|
||||
'rsrc/image/BFCFDA.png' => 'd5ec91f4',
|
||||
'rsrc/image/actions/edit.png' => '2fc41442',
|
||||
'rsrc/image/avatar.png' => '17d346a4',
|
||||
|
@ -374,7 +352,7 @@ return array(
|
|||
'rsrc/image/texture/table_header_tall.png' => 'd56b434f',
|
||||
'rsrc/js/application/aphlict/Aphlict.js' => 'e1d4b11a',
|
||||
'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => 'caade6f2',
|
||||
'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => '4cc4f460',
|
||||
'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => '599a8f5f',
|
||||
'rsrc/js/application/aphlict/behavior-aphlict-status.js' => '5e2634b9',
|
||||
'rsrc/js/application/aphlict/behavior-desktop-notifications-control.js' => '27ca6289',
|
||||
'rsrc/js/application/calendar/behavior-day-view.js' => '4b3c4443',
|
||||
|
@ -412,7 +390,7 @@ return array(
|
|||
'rsrc/js/application/diffusion/behavior-load-blame.js' => '42126667',
|
||||
'rsrc/js/application/diffusion/behavior-locate-file.js' => '6d3e1947',
|
||||
'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'f01586dc',
|
||||
'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => 'e5822781',
|
||||
'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '1db13e70',
|
||||
'rsrc/js/application/drydock/drydock-live-operation-status.js' => '901935ef',
|
||||
'rsrc/js/application/files/behavior-icon-composer.js' => '8499b6ab',
|
||||
'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888',
|
||||
|
@ -467,7 +445,7 @@ return array(
|
|||
'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2',
|
||||
'rsrc/js/core/KeyboardShortcutManager.js' => 'c19dd9b9',
|
||||
'rsrc/js/core/MultirowRowManager.js' => 'b5d57730',
|
||||
'rsrc/js/core/Notification.js' => '008faf9c',
|
||||
'rsrc/js/core/Notification.js' => '4f774dac',
|
||||
'rsrc/js/core/Prefab.js' => '77b0ae28',
|
||||
'rsrc/js/core/ShapedRequest.js' => '7cbe244b',
|
||||
'rsrc/js/core/TextAreaUtils.js' => '320810c8',
|
||||
|
@ -483,7 +461,6 @@ return array(
|
|||
'rsrc/js/core/behavior-detect-timezone.js' => '4c193c96',
|
||||
'rsrc/js/core/behavior-device.js' => 'a3714c76',
|
||||
'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '484a6e22',
|
||||
'rsrc/js/core/behavior-error-log.js' => '6882e80a',
|
||||
'rsrc/js/core/behavior-fancy-datepicker.js' => 'ecf4e799',
|
||||
'rsrc/js/core/behavior-file-tree.js' => '88236f00',
|
||||
'rsrc/js/core/behavior-form.js' => '5c54cbf3',
|
||||
|
@ -522,7 +499,7 @@ return array(
|
|||
'rsrc/js/core/behavior-workflow.js' => '0a3f3021',
|
||||
'rsrc/js/core/darkconsole/DarkLog.js' => 'c8e1ffe3',
|
||||
'rsrc/js/core/darkconsole/DarkMessage.js' => 'c48cccdd',
|
||||
'rsrc/js/core/darkconsole/behavior-dark-console.js' => '17bb8539',
|
||||
'rsrc/js/core/darkconsole/behavior-dark-console.js' => '66888767',
|
||||
'rsrc/js/core/phtize.js' => 'd254d646',
|
||||
'rsrc/js/phui/behavior-phui-dropdown-menu.js' => 'b95d6f7d',
|
||||
'rsrc/js/phui/behavior-phui-file-upload.js' => 'b003d4fb',
|
||||
|
@ -541,7 +518,7 @@ return array(
|
|||
'symbols' => array(
|
||||
'almanac-css' => 'dbb9b3af',
|
||||
'aphront-bars' => '231ac33c',
|
||||
'aphront-dark-console-css' => 'f7b071f1',
|
||||
'aphront-dark-console-css' => '0e14e8f6',
|
||||
'aphront-dialog-view-css' => '6bfc244b',
|
||||
'aphront-list-filter-view-css' => '5d6f0526',
|
||||
'aphront-multi-column-view-css' => '84cc6640',
|
||||
|
@ -589,7 +566,7 @@ return array(
|
|||
'javelin-aphlict' => 'e1d4b11a',
|
||||
'javelin-behavior' => '61cbc29a',
|
||||
'javelin-behavior-aphlict-dropdown' => 'caade6f2',
|
||||
'javelin-behavior-aphlict-listen' => '4cc4f460',
|
||||
'javelin-behavior-aphlict-listen' => '599a8f5f',
|
||||
'javelin-behavior-aphlict-status' => '5e2634b9',
|
||||
'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884',
|
||||
'javelin-behavior-aphront-drag-and-drop-textarea' => '484a6e22',
|
||||
|
@ -609,7 +586,7 @@ return array(
|
|||
'javelin-behavior-conpherence-pontificate' => '55616e04',
|
||||
'javelin-behavior-conpherence-search' => '9bbf3762',
|
||||
'javelin-behavior-countdown-timer' => 'e4cc26b3',
|
||||
'javelin-behavior-dark-console' => '17bb8539',
|
||||
'javelin-behavior-dark-console' => '66888767',
|
||||
'javelin-behavior-dashboard-async-panel' => '469c0d9e',
|
||||
'javelin-behavior-dashboard-move-panels' => '408bf173',
|
||||
'javelin-behavior-dashboard-query-panel-select' => '453c5375',
|
||||
|
@ -629,12 +606,11 @@ return array(
|
|||
'javelin-behavior-diffusion-jump-to' => '73d09eef',
|
||||
'javelin-behavior-diffusion-locate-file' => '6d3e1947',
|
||||
'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc',
|
||||
'javelin-behavior-doorkeeper-tag' => 'e5822781',
|
||||
'javelin-behavior-doorkeeper-tag' => '1db13e70',
|
||||
'javelin-behavior-drydock-live-operation-status' => '901935ef',
|
||||
'javelin-behavior-durable-column' => '2ae077e1',
|
||||
'javelin-behavior-editengine-reorder-configs' => 'd7a74243',
|
||||
'javelin-behavior-editengine-reorder-fields' => 'b59e1e96',
|
||||
'javelin-behavior-error-log' => '6882e80a',
|
||||
'javelin-behavior-event-all-day' => 'b41537c9',
|
||||
'javelin-behavior-fancy-datepicker' => 'ecf4e799',
|
||||
'javelin-behavior-global-drag-and-drop' => '960f6a39',
|
||||
|
@ -796,7 +772,7 @@ return array(
|
|||
'phabricator-keyboard-shortcut-manager' => 'c19dd9b9',
|
||||
'phabricator-main-menu-view' => '1802a242',
|
||||
'phabricator-nav-view-css' => 'a9e3e6d5',
|
||||
'phabricator-notification' => '008faf9c',
|
||||
'phabricator-notification' => '4f774dac',
|
||||
'phabricator-notification-css' => '457861ec',
|
||||
'phabricator-notification-menu-css' => '10685bd4',
|
||||
'phabricator-object-selector-css' => '85ee8ce6',
|
||||
|
@ -902,7 +878,7 @@ return array(
|
|||
'releeph-preview-branch' => 'b7a6f4a5',
|
||||
'releeph-request-differential-create-dialog' => '8d8b92cd',
|
||||
'releeph-request-typeahead-css' => '667a48ae',
|
||||
'setup-issue-css' => '7dae7f18',
|
||||
'setup-issue-css' => '30ee0173',
|
||||
'sprite-login-css' => '396f3c3a',
|
||||
'sprite-tokens-css' => '9cdfd599',
|
||||
'syntax-default-css' => '9923583c',
|
||||
|
@ -918,13 +894,6 @@ return array(
|
|||
'javelin-typeahead-preloaded-source',
|
||||
'javelin-util',
|
||||
),
|
||||
'008faf9c' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
'javelin-stratcom',
|
||||
'javelin-util',
|
||||
'phabricator-notification-css',
|
||||
),
|
||||
'013ffff9' => array(
|
||||
'javelin-install',
|
||||
'javelin-util',
|
||||
|
@ -1008,16 +977,6 @@ return array(
|
|||
'aphront-typeahead-control-css',
|
||||
'phui-tag-view-css',
|
||||
),
|
||||
'17bb8539' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
'javelin-util',
|
||||
'javelin-dom',
|
||||
'javelin-request',
|
||||
'phabricator-keyboard-shortcut',
|
||||
'phabricator-darklog',
|
||||
'phabricator-darkmessage',
|
||||
),
|
||||
'1802a242' => array(
|
||||
'phui-theme-css',
|
||||
),
|
||||
|
@ -1045,6 +1004,13 @@ return array(
|
|||
'javelin-request',
|
||||
'javelin-uri',
|
||||
),
|
||||
'1db13e70' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
'javelin-json',
|
||||
'javelin-workflow',
|
||||
'javelin-magical-init',
|
||||
),
|
||||
'1f6794f6' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
@ -1286,20 +1252,6 @@ return array(
|
|||
'javelin-uri',
|
||||
'phabricator-notification',
|
||||
),
|
||||
'4cc4f460' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-aphlict',
|
||||
'javelin-stratcom',
|
||||
'javelin-request',
|
||||
'javelin-uri',
|
||||
'javelin-dom',
|
||||
'javelin-json',
|
||||
'javelin-router',
|
||||
'javelin-util',
|
||||
'javelin-leader',
|
||||
'javelin-sound',
|
||||
'phabricator-notification',
|
||||
),
|
||||
'4d863052' => array(
|
||||
'javelin-dom',
|
||||
'javelin-util',
|
||||
|
@ -1316,6 +1268,13 @@ return array(
|
|||
'javelin-stratcom',
|
||||
'javelin-dom',
|
||||
),
|
||||
'4f774dac' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
'javelin-stratcom',
|
||||
'javelin-util',
|
||||
'phabricator-notification-css',
|
||||
),
|
||||
'503e17fd' => array(
|
||||
'javelin-install',
|
||||
'javelin-typeahead-source',
|
||||
|
@ -1370,6 +1329,20 @@ return array(
|
|||
'javelin-uri',
|
||||
'phabricator-file-upload',
|
||||
),
|
||||
'599a8f5f' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-aphlict',
|
||||
'javelin-stratcom',
|
||||
'javelin-request',
|
||||
'javelin-uri',
|
||||
'javelin-dom',
|
||||
'javelin-json',
|
||||
'javelin-router',
|
||||
'javelin-util',
|
||||
'javelin-leader',
|
||||
'javelin-sound',
|
||||
'phabricator-notification',
|
||||
),
|
||||
'59a7976a' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
|
@ -1430,6 +1403,16 @@ return array(
|
|||
'javelin-typeahead-ondemand-source',
|
||||
'javelin-util',
|
||||
),
|
||||
66888767 => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
'javelin-util',
|
||||
'javelin-dom',
|
||||
'javelin-request',
|
||||
'phabricator-keyboard-shortcut',
|
||||
'phabricator-darklog',
|
||||
'phabricator-darkmessage',
|
||||
),
|
||||
'66a6def1' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
|
@ -1443,9 +1426,6 @@ return array(
|
|||
'javelin-dom',
|
||||
'phabricator-notification',
|
||||
),
|
||||
'6882e80a' => array(
|
||||
'javelin-dom',
|
||||
),
|
||||
'68af71ca' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
|
@ -2108,13 +2088,6 @@ return array(
|
|||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
),
|
||||
'e5822781' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
'javelin-json',
|
||||
'javelin-workflow',
|
||||
'javelin-magical-init',
|
||||
),
|
||||
'e74b7517' => array(
|
||||
'javelin-install',
|
||||
'phuix-button-view',
|
||||
|
@ -2364,10 +2337,6 @@ return array(
|
|||
'javelin-behavior-user-menu',
|
||||
'phabricator-favicon',
|
||||
),
|
||||
'darkconsole.pkg.js' => array(
|
||||
'javelin-behavior-dark-console',
|
||||
'javelin-behavior-error-log',
|
||||
),
|
||||
'differential.pkg.css' => array(
|
||||
'differential-core-view-css',
|
||||
'differential-changeset-view-css',
|
||||
|
|
|
@ -224,8 +224,4 @@ return array(
|
|||
'javelin-behavior-maniphest-subpriority-editor',
|
||||
'javelin-behavior-maniphest-list-editor',
|
||||
),
|
||||
'darkconsole.pkg.js' => array(
|
||||
'javelin-behavior-dark-console',
|
||||
'javelin-behavior-error-log',
|
||||
),
|
||||
);
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_differential.differential_reviewer
|
||||
ADD options LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT};
|
|
@ -0,0 +1,2 @@
|
|||
UPDATE {$NAMESPACE}_differential.differential_reviewer
|
||||
SET options = '{}' WHERE options = '';
|
|
@ -45,7 +45,7 @@ set +x
|
|||
sudo apt-get -qq update
|
||||
sudo apt-get install \
|
||||
$GIT mysql-server apache2 dpkg-dev \
|
||||
php5 php5-mysql php5-gd php5-dev php5-curl php-apc php5-cli php5-json
|
||||
php5 php5-mysqlnd php5-gd php5-dev php5-curl php-apc php5-cli php5-json
|
||||
|
||||
# Enable mod_rewrite
|
||||
sudo a2enmod rewrite
|
||||
|
|
|
@ -2055,6 +2055,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorApplicationTransactionValidationResponse' => 'applications/transactions/response/PhabricatorApplicationTransactionValidationResponse.php',
|
||||
'PhabricatorApplicationTransactionValueController' => 'applications/transactions/controller/PhabricatorApplicationTransactionValueController.php',
|
||||
'PhabricatorApplicationTransactionView' => 'applications/transactions/view/PhabricatorApplicationTransactionView.php',
|
||||
'PhabricatorApplicationTransactionWarningException' => 'applications/transactions/exception/PhabricatorApplicationTransactionWarningException.php',
|
||||
'PhabricatorApplicationTransactionWarningResponse' => 'applications/transactions/response/PhabricatorApplicationTransactionWarningResponse.php',
|
||||
'PhabricatorApplicationUninstallController' => 'applications/meta/controller/PhabricatorApplicationUninstallController.php',
|
||||
'PhabricatorApplicationsApplication' => 'applications/meta/application/PhabricatorApplicationsApplication.php',
|
||||
'PhabricatorApplicationsController' => 'applications/meta/controller/PhabricatorApplicationsController.php',
|
||||
|
@ -2953,6 +2955,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFactObjectDimension' => 'applications/fact/storage/PhabricatorFactObjectDimension.php',
|
||||
'PhabricatorFactRaw' => 'applications/fact/storage/PhabricatorFactRaw.php',
|
||||
'PhabricatorFactUpdateIterator' => 'applications/fact/extract/PhabricatorFactUpdateIterator.php',
|
||||
'PhabricatorFaviconRef' => 'applications/files/favicon/PhabricatorFaviconRef.php',
|
||||
'PhabricatorFaviconRefQuery' => 'applications/files/favicon/PhabricatorFaviconRefQuery.php',
|
||||
'PhabricatorFavoritesApplication' => 'applications/favorites/application/PhabricatorFavoritesApplication.php',
|
||||
'PhabricatorFavoritesController' => 'applications/favorites/controller/PhabricatorFavoritesController.php',
|
||||
'PhabricatorFavoritesMainMenuBarExtension' => 'applications/favorites/engineextension/PhabricatorFavoritesMainMenuBarExtension.php',
|
||||
|
@ -4331,7 +4335,6 @@ phutil_register_library_map(array(
|
|||
'PhabricatorSystemDAO' => 'applications/system/storage/PhabricatorSystemDAO.php',
|
||||
'PhabricatorSystemDestructionGarbageCollector' => 'applications/system/garbagecollector/PhabricatorSystemDestructionGarbageCollector.php',
|
||||
'PhabricatorSystemDestructionLog' => 'applications/system/storage/PhabricatorSystemDestructionLog.php',
|
||||
'PhabricatorSystemFaviconController' => 'applications/system/controller/PhabricatorSystemFaviconController.php',
|
||||
'PhabricatorSystemReadOnlyController' => 'applications/system/controller/PhabricatorSystemReadOnlyController.php',
|
||||
'PhabricatorSystemRemoveDestroyWorkflow' => 'applications/system/management/PhabricatorSystemRemoveDestroyWorkflow.php',
|
||||
'PhabricatorSystemRemoveLogWorkflow' => 'applications/system/management/PhabricatorSystemRemoveLogWorkflow.php',
|
||||
|
@ -7483,6 +7486,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorApplicationTransactionValidationResponse' => 'AphrontProxyResponse',
|
||||
'PhabricatorApplicationTransactionValueController' => 'PhabricatorApplicationTransactionController',
|
||||
'PhabricatorApplicationTransactionView' => 'AphrontView',
|
||||
'PhabricatorApplicationTransactionWarningException' => 'Exception',
|
||||
'PhabricatorApplicationTransactionWarningResponse' => 'AphrontProxyResponse',
|
||||
'PhabricatorApplicationUninstallController' => 'PhabricatorApplicationsController',
|
||||
'PhabricatorApplicationsApplication' => 'PhabricatorApplication',
|
||||
'PhabricatorApplicationsController' => 'PhabricatorController',
|
||||
|
@ -8512,6 +8517,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFactObjectDimension' => 'PhabricatorFactDimension',
|
||||
'PhabricatorFactRaw' => 'PhabricatorFactDAO',
|
||||
'PhabricatorFactUpdateIterator' => 'PhutilBufferedIterator',
|
||||
'PhabricatorFaviconRef' => 'Phobject',
|
||||
'PhabricatorFaviconRefQuery' => 'Phobject',
|
||||
'PhabricatorFavoritesApplication' => 'PhabricatorApplication',
|
||||
'PhabricatorFavoritesController' => 'PhabricatorController',
|
||||
'PhabricatorFavoritesMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension',
|
||||
|
@ -10142,7 +10149,6 @@ phutil_register_library_map(array(
|
|||
'PhabricatorSystemDAO' => 'PhabricatorLiskDAO',
|
||||
'PhabricatorSystemDestructionGarbageCollector' => 'PhabricatorGarbageCollector',
|
||||
'PhabricatorSystemDestructionLog' => 'PhabricatorSystemDAO',
|
||||
'PhabricatorSystemFaviconController' => 'PhabricatorController',
|
||||
'PhabricatorSystemReadOnlyController' => 'PhabricatorController',
|
||||
'PhabricatorSystemRemoveDestroyWorkflow' => 'PhabricatorSystemRemoveWorkflow',
|
||||
'PhabricatorSystemRemoveLogWorkflow' => 'PhabricatorSystemRemoveWorkflow',
|
||||
|
|
|
@ -67,6 +67,51 @@ final class PhabricatorPHPConfigSetupCheck extends PhabricatorSetupCheck {
|
|||
->addPHPConfig('always_populate_raw_post_data');
|
||||
}
|
||||
|
||||
if (!extension_loaded('mysqli')) {
|
||||
$summary = pht(
|
||||
'Install the MySQLi extension to improve database behavior.');
|
||||
|
||||
$message = pht(
|
||||
'PHP is currently using the very old "mysql" extension to interact '.
|
||||
'with the database. You should install the newer "mysqli" extension '.
|
||||
'to improve behaviors (like error handling and query timeouts).'.
|
||||
"\n\n".
|
||||
'Phabricator will work with the older extension, but upgrading to the '.
|
||||
'newer extension is recommended.'.
|
||||
"\n\n".
|
||||
'You may be able to install the extension with a command like: %s',
|
||||
|
||||
// NOTE: We're intentionally telling you to install "mysqlnd" here; on
|
||||
// Ubuntu, there's no separate "mysqli" package.
|
||||
phutil_tag('tt', array(), 'sudo apt-get install php5-mysqlnd'));
|
||||
|
||||
$this->newIssue('php.mysqli')
|
||||
->setName(pht('MySQLi Extension Not Available'))
|
||||
->setSummary($summary)
|
||||
->setMessage($message);
|
||||
} else if (!defined('MYSQLI_ASYNC')) {
|
||||
$summary = pht(
|
||||
'Configure the MySQL Native Driver to improve database behavior.');
|
||||
|
||||
$message = pht(
|
||||
'PHP is currently using the older MySQL external driver instead of '.
|
||||
'the newer MySQL native driver. The older driver lacks options and '.
|
||||
'features (like support for query timeouts) which allow Phabricator '.
|
||||
'to interact better with the database.'.
|
||||
"\n\n".
|
||||
'Phabricator will work with the older driver, but upgrading to the '.
|
||||
'native driver is recommended.'.
|
||||
"\n\n".
|
||||
'You may be able to install the native driver with a command like: %s',
|
||||
phutil_tag('tt', array(), 'sudo apt-get install php5-mysqlnd'));
|
||||
|
||||
|
||||
$this->newIssue('php.myqlnd')
|
||||
->setName(pht('MySQL Native Driver Not Available'))
|
||||
->setSummary($summary)
|
||||
->setMessage($message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -64,6 +64,10 @@ EOJSON;
|
|||
"Phabricator logo in the site header.\n\n".
|
||||
" - **Wordmark**: Choose new text to display next to the logo. ".
|
||||
"By default, the header displays //Phabricator//.\n\n")),
|
||||
$this->newOption('ui.favicons', 'wild', array())
|
||||
->setSummary(pht('Customize favicons.'))
|
||||
->setDescription(pht('Customize favicons.'))
|
||||
->setLocked(true),
|
||||
$this->newOption('ui.footer-items', $footer_type, array())
|
||||
->setSummary(
|
||||
pht(
|
||||
|
|
|
@ -39,10 +39,13 @@ final class DarkConsoleErrorLogPlugin extends DarkConsolePlugin {
|
|||
$file = $row['file'];
|
||||
$line = $row['line'];
|
||||
|
||||
$tag = phutil_tag(
|
||||
$tag = javelin_tag(
|
||||
'a',
|
||||
array(
|
||||
'onclick' => jsprintf('show_details(%d)', $index),
|
||||
'sigil' => 'darkconsole-expand',
|
||||
'meta' => array(
|
||||
'expandID' => 'row-details-'.$index,
|
||||
),
|
||||
),
|
||||
$row['str'].' at ['.basename($file).':'.$line.']');
|
||||
$rows[] = array($tag);
|
||||
|
|
|
@ -29,6 +29,18 @@ final class DarkConsoleServicesPlugin extends DarkConsolePlugin {
|
|||
return false;
|
||||
}
|
||||
|
||||
public function didStartup() {
|
||||
$should_analyze = self::isQueryAnalyzerRequested();
|
||||
|
||||
if ($should_analyze) {
|
||||
PhutilServiceProfiler::getInstance()
|
||||
->setCollectStackTraces(true);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @phutil-external-symbol class PhabricatorStartup
|
||||
*/
|
||||
|
@ -266,6 +278,16 @@ final class DarkConsoleServicesPlugin extends DarkConsolePlugin {
|
|||
$info,
|
||||
$analysis,
|
||||
);
|
||||
|
||||
if ($row['trace']) {
|
||||
$rows[] = array(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
$row['trace'],
|
||||
null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$table = new AphrontTableView($rows);
|
||||
|
@ -274,7 +296,7 @@ final class DarkConsoleServicesPlugin extends DarkConsolePlugin {
|
|||
null,
|
||||
'n',
|
||||
'n',
|
||||
'wide',
|
||||
'wide prewrap',
|
||||
'',
|
||||
));
|
||||
$table->setHeaders(
|
||||
|
|
|
@ -154,59 +154,41 @@ final class DifferentialTransactionEditor
|
|||
|
||||
$edge_ref_task = DifferentialRevisionHasTaskEdgeType::EDGECONST;
|
||||
|
||||
$is_sticky_accept = PhabricatorEnv::getEnvConfig(
|
||||
'differential.sticky-accept');
|
||||
|
||||
$downgrade_rejects = false;
|
||||
$downgrade_accepts = false;
|
||||
$want_downgrade = array();
|
||||
$must_downgrade = array();
|
||||
if ($this->getIsCloseByCommit()) {
|
||||
// Never downgrade reviewers when we're closing a revision after a
|
||||
// commit.
|
||||
} else {
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case DifferentialRevisionUpdateTransaction::TRANSACTIONTYPE:
|
||||
$downgrade_rejects = true;
|
||||
if (!$is_sticky_accept) {
|
||||
// If "sticky accept" is disabled, also downgrade the accepts.
|
||||
$downgrade_accepts = true;
|
||||
}
|
||||
$want_downgrade[] = DifferentialReviewerStatus::STATUS_ACCEPTED;
|
||||
$want_downgrade[] = DifferentialReviewerStatus::STATUS_REJECTED;
|
||||
break;
|
||||
case DifferentialRevisionRequestReviewTransaction::TRANSACTIONTYPE:
|
||||
$downgrade_rejects = true;
|
||||
if ((!$is_sticky_accept) ||
|
||||
(!$object->isChangePlanned())) {
|
||||
// If the old state isn't "changes planned", downgrade the accepts.
|
||||
// This exception allows an accepted revision to go through
|
||||
// "Plan Changes" -> "Request Review" and return to "accepted" if
|
||||
// the author didn't update the revision, essentially undoing the
|
||||
// "Plan Changes".
|
||||
$downgrade_accepts = true;
|
||||
if (!$object->isChangePlanned()) {
|
||||
// If the old state isn't "Changes Planned", downgrade the accepts
|
||||
// even if they're sticky.
|
||||
|
||||
// We don't downgrade for "Changes Planned" to allow an author to
|
||||
// undo a "Plan Changes" by immediately following it up with a
|
||||
// "Request Review".
|
||||
$want_downgrade[] = DifferentialReviewerStatus::STATUS_ACCEPTED;
|
||||
$must_downgrade[] = DifferentialReviewerStatus::STATUS_ACCEPTED;
|
||||
}
|
||||
$want_downgrade[] = DifferentialReviewerStatus::STATUS_REJECTED;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$new_accept = DifferentialReviewerStatus::STATUS_ACCEPTED;
|
||||
$new_reject = DifferentialReviewerStatus::STATUS_REJECTED;
|
||||
$old_accept = DifferentialReviewerStatus::STATUS_ACCEPTED_OLDER;
|
||||
$old_reject = DifferentialReviewerStatus::STATUS_REJECTED_OLDER;
|
||||
|
||||
$downgrade = array();
|
||||
if ($downgrade_accepts) {
|
||||
$downgrade[] = DifferentialReviewerStatus::STATUS_ACCEPTED;
|
||||
}
|
||||
|
||||
if ($downgrade_rejects) {
|
||||
$downgrade[] = DifferentialReviewerStatus::STATUS_REJECTED;
|
||||
}
|
||||
|
||||
if ($downgrade) {
|
||||
if ($want_downgrade) {
|
||||
$void_type = DifferentialRevisionVoidTransaction::TRANSACTIONTYPE;
|
||||
|
||||
$results[] = id(new DifferentialTransaction())
|
||||
->setTransactionType($void_type)
|
||||
->setIgnoreOnNoEffect(true)
|
||||
->setNewValue($downgrade);
|
||||
->setMetadataValue('void.force', $must_downgrade)
|
||||
->setNewValue($want_downgrade);
|
||||
}
|
||||
|
||||
$is_commandeer = false;
|
||||
|
|
|
@ -10,12 +10,16 @@ final class DifferentialReviewer
|
|||
protected $lastCommentDiffPHID;
|
||||
protected $lastActorPHID;
|
||||
protected $voidedPHID;
|
||||
protected $options = array();
|
||||
|
||||
private $authority = array();
|
||||
private $changesets = self::ATTACHABLE;
|
||||
|
||||
protected function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_SERIALIZATION => array(
|
||||
'options' => self::SERIALIZATION_JSON,
|
||||
),
|
||||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'reviewerStatus' => 'text64',
|
||||
'lastActionDiffPHID' => 'phid?',
|
||||
|
@ -64,6 +68,15 @@ final class DifferentialReviewer
|
|||
return $this->assertAttached($this->changesets);
|
||||
}
|
||||
|
||||
public function setOption($key, $value) {
|
||||
$this->options[$key] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOption($key, $default = null) {
|
||||
return idx($this->options, $key, $default);
|
||||
}
|
||||
|
||||
public function isResigned() {
|
||||
$status_resigned = DifferentialReviewerStatus::STATUS_RESIGNED;
|
||||
return ($this->getReviewerStatus() == $status_resigned);
|
||||
|
|
|
@ -66,11 +66,6 @@ final class DifferentialRevisionCommandeerTransaction
|
|||
'been closed. You can only commandeer open revisions.'));
|
||||
}
|
||||
|
||||
if ($object->isDraft()) {
|
||||
throw new Exception(
|
||||
pht('You can not commandeer a draft revision.'));
|
||||
}
|
||||
|
||||
if ($this->isViewerRevisionAuthor($object, $viewer)) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
|
|
|
@ -164,7 +164,14 @@ abstract class DifferentialRevisionReviewTransaction
|
|||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer,
|
||||
$value,
|
||||
$status) {
|
||||
$status,
|
||||
array $reviewer_options = array()) {
|
||||
|
||||
PhutilTypeSpec::checkMap(
|
||||
$reviewer_options,
|
||||
array(
|
||||
'sticky' => 'optional bool',
|
||||
));
|
||||
|
||||
$map = array();
|
||||
|
||||
|
@ -253,6 +260,8 @@ abstract class DifferentialRevisionReviewTransaction
|
|||
// accepted.
|
||||
$reviewer->setVoidedPHID(null);
|
||||
|
||||
$reviewer->setOption('sticky', idx($reviewer_options, 'sticky'));
|
||||
|
||||
try {
|
||||
$reviewer->save();
|
||||
} catch (AphrontDuplicateKeyQueryException $ex) {
|
||||
|
|
|
@ -16,21 +16,43 @@ final class DifferentialRevisionVoidTransaction
|
|||
}
|
||||
|
||||
public function generateNewValue($object, $value) {
|
||||
$table = new DifferentialReviewer();
|
||||
$table_name = $table->getTableName();
|
||||
$conn = $table->establishConnection('w');
|
||||
|
||||
$rows = queryfx_all(
|
||||
$conn,
|
||||
'SELECT reviewerPHID FROM %T
|
||||
WHERE revisionPHID = %s
|
||||
AND voidedPHID IS NULL
|
||||
AND reviewerStatus IN (%Ls)',
|
||||
$table_name,
|
||||
$reviewers = id(new DifferentialReviewer())->loadAllWhere(
|
||||
'revisionPHID = %s
|
||||
AND voidedPHID IS NULL
|
||||
AND reviewerStatus IN (%Ls)',
|
||||
$object->getPHID(),
|
||||
$value);
|
||||
|
||||
return ipull($rows, 'reviewerPHID');
|
||||
$must_downgrade = $this->getMetadataValue('void.force', array());
|
||||
$must_downgrade = array_fuse($must_downgrade);
|
||||
|
||||
$default = PhabricatorEnv::getEnvConfig('differential.sticky-accept');
|
||||
foreach ($reviewers as $key => $reviewer) {
|
||||
$status = $reviewer->getReviewerStatus();
|
||||
|
||||
// If this void is forced, always downgrade. For example, this happens
|
||||
// when an author chooses "Request Review": existing reviews are always
|
||||
// voided, even if they're sticky.
|
||||
if (isset($must_downgrade[$status])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, if this is a sticky accept, don't void it. Accepts may be
|
||||
// explicitly sticky or unsticky, or they'll use the default value if
|
||||
// no value is specified.
|
||||
$is_sticky = $reviewer->getOption('sticky');
|
||||
$is_sticky = coalesce($is_sticky, $default);
|
||||
|
||||
if ($status === DifferentialReviewerStatus::STATUS_ACCEPTED) {
|
||||
if ($is_sticky) {
|
||||
unset($reviewers[$key]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return mpull($reviewers, 'getReviewerPHID');
|
||||
}
|
||||
|
||||
public function getTransactionHasEffect($object, $old, $new) {
|
||||
|
|
|
@ -97,6 +97,7 @@ final class DoorkeeperBridgeJIRA extends DoorkeeperBridge {
|
|||
|
||||
$ref->setAttribute('title', idx($fields, 'summary'));
|
||||
$ref->setAttribute('description', idx($result, 'description'));
|
||||
$ref->setAttribute('shortname', $result['key']);
|
||||
|
||||
$obj = $ref->getExternalObject();
|
||||
if ($obj->getID()) {
|
||||
|
|
|
@ -13,17 +13,13 @@ final class DoorkeeperTagsController extends PhabricatorController {
|
|||
}
|
||||
|
||||
$refs = array();
|
||||
$id_map = array();
|
||||
foreach ($tags as $tag_spec) {
|
||||
foreach ($tags as $key => $tag_spec) {
|
||||
$tag = $tag_spec['ref'];
|
||||
$ref = id(new DoorkeeperObjectRef())
|
||||
->setApplicationType($tag[0])
|
||||
->setApplicationDomain($tag[1])
|
||||
->setObjectType($tag[2])
|
||||
->setObjectID($tag[3]);
|
||||
|
||||
$key = $ref->getObjectKey();
|
||||
$id_map[$key] = $tag_spec['id'];
|
||||
$refs[$key] = $ref;
|
||||
}
|
||||
|
||||
|
@ -43,19 +39,30 @@ final class DoorkeeperTagsController extends PhabricatorController {
|
|||
continue;
|
||||
}
|
||||
|
||||
$id = $id_map[$key];
|
||||
$tag_spec = $tags[$key];
|
||||
|
||||
$id = $tag_spec['id'];
|
||||
$view = idx($tag_spec, 'view');
|
||||
|
||||
$is_short = ($view == 'short');
|
||||
|
||||
if ($is_short) {
|
||||
$name = $ref->getShortName();
|
||||
} else {
|
||||
$name = $ref->getFullName();
|
||||
}
|
||||
|
||||
$tag = id(new PHUITagView())
|
||||
->setID($id)
|
||||
->setName($ref->getFullName())
|
||||
->setName($name)
|
||||
->setHref($uri)
|
||||
->setType(PHUITagView::TYPE_OBJECT)
|
||||
->setExternal(true)
|
||||
->render();
|
||||
|
||||
$results[] = array(
|
||||
'id' => $id,
|
||||
'markup' => $tag,
|
||||
'id' => $id,
|
||||
'markup' => $tag,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -107,6 +107,13 @@ final class DoorkeeperObjectRef extends Phobject {
|
|||
pht('External Object'));
|
||||
}
|
||||
|
||||
public function getShortName() {
|
||||
return coalesce(
|
||||
$this->getAttribute('shortname'),
|
||||
$this->getAttribute('name'),
|
||||
pht('External Object'));
|
||||
}
|
||||
|
||||
public function getObjectKey() {
|
||||
if (!$this->objectKey) {
|
||||
$this->objectKey = PhabricatorHash::digestForIndex(
|
||||
|
|
|
@ -4,11 +4,41 @@ abstract class DoorkeeperRemarkupRule extends PhutilRemarkupRule {
|
|||
|
||||
const KEY_TAGS = 'doorkeeper.tags';
|
||||
|
||||
const VIEW_FULL = 'full';
|
||||
const VIEW_SHORT = 'short';
|
||||
|
||||
public function getPriority() {
|
||||
return 350.0;
|
||||
}
|
||||
|
||||
protected function addDoorkeeperTag(array $spec) {
|
||||
PhutilTypeSpec::checkMap(
|
||||
$spec,
|
||||
array(
|
||||
'href' => 'string',
|
||||
'tag' => 'map<string, wild>',
|
||||
|
||||
'name' => 'optional string',
|
||||
'view' => 'optional string',
|
||||
));
|
||||
|
||||
$spec = $spec + array(
|
||||
'view' => self::VIEW_FULL,
|
||||
);
|
||||
|
||||
$views = array(
|
||||
self::VIEW_FULL,
|
||||
self::VIEW_SHORT,
|
||||
);
|
||||
$views = array_fuse($views);
|
||||
if (!isset($views[$spec['view']])) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Unsupported Doorkeeper tag view mode "%s". Supported modes are: %s.',
|
||||
$spec['view'],
|
||||
implode(', ', $views)));
|
||||
}
|
||||
|
||||
$key = self::KEY_TAGS;
|
||||
$engine = $this->getEngine();
|
||||
$token = $engine->storeText(get_class($this));
|
||||
|
@ -36,19 +66,26 @@ abstract class DoorkeeperRemarkupRule extends PhutilRemarkupRule {
|
|||
|
||||
$refs = array();
|
||||
foreach ($tags as $spec) {
|
||||
$tag_id = celerity_generate_unique_node_id();
|
||||
$href = $spec['href'];
|
||||
$name = idx($spec, 'name', $href);
|
||||
|
||||
$refs[] = array(
|
||||
'id' => $tag_id,
|
||||
) + $spec['tag'];
|
||||
$this->assertFlatText($href);
|
||||
$this->assertFlatText($name);
|
||||
|
||||
if ($this->getEngine()->isTextMode()) {
|
||||
$view = $spec['href'];
|
||||
$view = "{$name} <{$href}>";
|
||||
} else {
|
||||
$tag_id = celerity_generate_unique_node_id();
|
||||
|
||||
$refs[] = array(
|
||||
'id' => $tag_id,
|
||||
'view' => $spec['view'],
|
||||
) + $spec['tag'];
|
||||
|
||||
$view = id(new PHUITagView())
|
||||
->setID($tag_id)
|
||||
->setName($this->assertFlatText($spec['href']))
|
||||
->setHref($this->assertFlatText($spec['href']))
|
||||
->setName($name)
|
||||
->setHref($href)
|
||||
->setType(PHUITagView::TYPE_OBJECT)
|
||||
->setExternal(true);
|
||||
}
|
||||
|
@ -56,7 +93,9 @@ abstract class DoorkeeperRemarkupRule extends PhutilRemarkupRule {
|
|||
$engine->overwriteStoredText($spec['token'], $view);
|
||||
}
|
||||
|
||||
Javelin::initBehavior('doorkeeper-tag', array('tags' => $refs));
|
||||
if ($refs) {
|
||||
Javelin::initBehavior('doorkeeper-tag', array('tags' => $refs));
|
||||
}
|
||||
|
||||
$engine->setTextMetadata($key, array());
|
||||
}
|
||||
|
|
447
src/applications/files/favicon/PhabricatorFaviconRef.php
Normal file
|
@ -0,0 +1,447 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorFaviconRef extends Phobject {
|
||||
|
||||
private $viewer;
|
||||
private $width;
|
||||
private $height;
|
||||
private $emblems;
|
||||
private $uri;
|
||||
private $cacheKey;
|
||||
|
||||
public function __construct() {
|
||||
$this->emblems = array(null, null, null, null);
|
||||
}
|
||||
|
||||
public function setViewer(PhabricatorUser $viewer) {
|
||||
$this->viewer = $viewer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getViewer() {
|
||||
return $this->viewer;
|
||||
}
|
||||
|
||||
public function setWidth($width) {
|
||||
$this->width = $width;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getWidth() {
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
public function setHeight($height) {
|
||||
$this->height = $height;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getHeight() {
|
||||
return $this->height;
|
||||
}
|
||||
|
||||
public function setEmblems(array $emblems) {
|
||||
if (count($emblems) !== 4) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Expected four elements in icon emblem list. To omit an emblem, '.
|
||||
'pass "null".'));
|
||||
}
|
||||
|
||||
$this->emblems = $emblems;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getEmblems() {
|
||||
return $this->emblems;
|
||||
}
|
||||
|
||||
public function setURI($uri) {
|
||||
$this->uri = $uri;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
return $this->uri;
|
||||
}
|
||||
|
||||
public function setCacheKey($cache_key) {
|
||||
$this->cacheKey = $cache_key;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCacheKey() {
|
||||
return $this->cacheKey;
|
||||
}
|
||||
|
||||
public function newDigest() {
|
||||
return PhabricatorHash::digestForIndex(serialize($this->toDictionary()));
|
||||
}
|
||||
|
||||
public function toDictionary() {
|
||||
return array(
|
||||
'width' => $this->width,
|
||||
'height' => $this->height,
|
||||
'emblems' => $this->emblems,
|
||||
);
|
||||
}
|
||||
|
||||
public static function newConfigurationDigest() {
|
||||
$all_resources = self::getAllResources();
|
||||
|
||||
// Because we need to access this cache on every page, it's very sticky.
|
||||
// Try to dirty it automatically if any relevant configuration changes.
|
||||
$inputs = array(
|
||||
'resources' => $all_resources,
|
||||
'prod' => PhabricatorEnv::getProductionURI('/'),
|
||||
'cdn' => PhabricatorEnv::getEnvConfig('security.alternate-file-domain'),
|
||||
'havepng' => function_exists('imagepng'),
|
||||
);
|
||||
|
||||
return PhabricatorHash::digestForIndex(serialize($inputs));
|
||||
}
|
||||
|
||||
private static function getAllResources() {
|
||||
$custom_resources = PhabricatorEnv::getEnvConfig('ui.favicons');
|
||||
|
||||
foreach ($custom_resources as $key => $custom_resource) {
|
||||
$custom_resources[$key] = array(
|
||||
'source-type' => 'file',
|
||||
'default' => false,
|
||||
) + $custom_resource;
|
||||
}
|
||||
|
||||
$builtin_resources = self::getBuiltinResources();
|
||||
|
||||
return array_merge($builtin_resources, $custom_resources);
|
||||
}
|
||||
|
||||
private static function getBuiltinResources() {
|
||||
return array(
|
||||
array(
|
||||
'source-type' => 'builtin',
|
||||
'source' => 'favicon/default-76x76.png',
|
||||
'version' => 1,
|
||||
'width' => 76,
|
||||
'height' => 76,
|
||||
'default' => true,
|
||||
),
|
||||
array(
|
||||
'source-type' => 'builtin',
|
||||
'source' => 'favicon/default-120x120.png',
|
||||
'version' => 1,
|
||||
'width' => 120,
|
||||
'height' => 120,
|
||||
'default' => true,
|
||||
),
|
||||
array(
|
||||
'source-type' => 'builtin',
|
||||
'source' => 'favicon/default-128x128.png',
|
||||
'version' => 1,
|
||||
'width' => 128,
|
||||
'height' => 128,
|
||||
'default' => true,
|
||||
),
|
||||
array(
|
||||
'source-type' => 'builtin',
|
||||
'source' => 'favicon/default-152x152.png',
|
||||
'version' => 1,
|
||||
'width' => 152,
|
||||
'height' => 152,
|
||||
'default' => true,
|
||||
),
|
||||
array(
|
||||
'source-type' => 'builtin',
|
||||
'source' => 'favicon/dot-pink-64x64.png',
|
||||
'version' => 1,
|
||||
'width' => 64,
|
||||
'height' => 64,
|
||||
'emblem' => 'dot-pink',
|
||||
'default' => true,
|
||||
),
|
||||
array(
|
||||
'source-type' => 'builtin',
|
||||
'source' => 'favicon/dot-red-64x64.png',
|
||||
'version' => 1,
|
||||
'width' => 64,
|
||||
'height' => 64,
|
||||
'emblem' => 'dot-red',
|
||||
'default' => true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public function newURI() {
|
||||
$dst_w = $this->getWidth();
|
||||
$dst_h = $this->getHeight();
|
||||
|
||||
$template = $this->newTemplateFile(null, $dst_w, $dst_h);
|
||||
$template_file = $template['file'];
|
||||
|
||||
$cache = $this->loadCachedFile($template_file);
|
||||
if ($cache) {
|
||||
return $cache->getViewURI();
|
||||
}
|
||||
|
||||
$data = $this->newCompositedFavicon($template);
|
||||
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
|
||||
$caught = null;
|
||||
try {
|
||||
$favicon_file = $this->newFaviconFile($data);
|
||||
|
||||
$xform = id(new PhabricatorTransformedFile())
|
||||
->setOriginalPHID($template_file->getPHID())
|
||||
->setTransformedPHID($favicon_file->getPHID())
|
||||
->setTransform($this->getCacheKey());
|
||||
|
||||
try {
|
||||
$xform->save();
|
||||
} catch (AphrontDuplicateKeyQueryException $ex) {
|
||||
unset($unguarded);
|
||||
|
||||
$cache = $this->loadCachedFile($template_file);
|
||||
if (!$cache) {
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
id(new PhabricatorDestructionEngine())
|
||||
->destroyObject($favicon_file);
|
||||
|
||||
return $cache->getViewURI();
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
$caught = $ex;
|
||||
}
|
||||
|
||||
unset($unguarded);
|
||||
|
||||
if ($caught) {
|
||||
throw $caught;
|
||||
}
|
||||
|
||||
return $favicon_file->getViewURI();
|
||||
}
|
||||
|
||||
private function loadCachedFile(PhabricatorFile $template_file) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$xform = id(new PhabricatorTransformedFile())->loadOneWhere(
|
||||
'originalPHID = %s AND transform = %s',
|
||||
$template_file->getPHID(),
|
||||
$this->getCacheKey());
|
||||
if (!$xform) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return id(new PhabricatorFileQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($xform->getTransformedPHID()))
|
||||
->executeOne();
|
||||
}
|
||||
|
||||
private function newCompositedFavicon($template) {
|
||||
$dst_w = $this->getWidth();
|
||||
$dst_h = $this->getHeight();
|
||||
$src_w = $template['width'];
|
||||
$src_h = $template['height'];
|
||||
|
||||
$template_data = $template['file']->loadFileData();
|
||||
|
||||
if (!function_exists('imagecreatefromstring')) {
|
||||
return $template_data;
|
||||
}
|
||||
|
||||
$src = @imagecreatefromstring($template_data);
|
||||
if (!$src) {
|
||||
return $template_data;
|
||||
}
|
||||
|
||||
$dst = imagecreatetruecolor($dst_w, $dst_h);
|
||||
imagesavealpha($dst, true);
|
||||
|
||||
$transparent = imagecolorallocatealpha($dst, 0, 255, 0, 127);
|
||||
imagefill($dst, 0, 0, $transparent);
|
||||
|
||||
imagecopyresampled(
|
||||
$dst,
|
||||
$src,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
$dst_w,
|
||||
$dst_h,
|
||||
$src_w,
|
||||
$src_h);
|
||||
|
||||
// Now, copy any icon emblems on top of the image. These are dots or other
|
||||
// marks used to indicate status information.
|
||||
$emblem_w = (int)floor(min($dst_w, $dst_h) / 2);
|
||||
$emblem_h = $emblem_w;
|
||||
foreach ($this->emblems as $key => $emblem) {
|
||||
if ($emblem === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$emblem_template = $this->newTemplateFile(
|
||||
$emblem,
|
||||
$emblem_w,
|
||||
$emblem_h);
|
||||
|
||||
switch ($key) {
|
||||
case 0:
|
||||
$emblem_x = $dst_w - $emblem_w;
|
||||
$emblem_y = 0;
|
||||
break;
|
||||
case 1:
|
||||
$emblem_x = $dst_w - $emblem_w;
|
||||
$emblem_y = $dst_h - $emblem_h;
|
||||
break;
|
||||
case 2:
|
||||
$emblem_x = 0;
|
||||
$emblem_y = $dst_h - $emblem_h;
|
||||
break;
|
||||
case 3:
|
||||
$emblem_x = 0;
|
||||
$emblem_y = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
$emblem_data = $emblem_template['file']->loadFileData();
|
||||
|
||||
$src = @imagecreatefromstring($emblem_data);
|
||||
if (!$src) {
|
||||
continue;
|
||||
}
|
||||
|
||||
imagecopyresampled(
|
||||
$dst,
|
||||
$src,
|
||||
$emblem_x,
|
||||
$emblem_y,
|
||||
0,
|
||||
0,
|
||||
$emblem_w,
|
||||
$emblem_h,
|
||||
$emblem_template['width'],
|
||||
$emblem_template['height']);
|
||||
}
|
||||
|
||||
return PhabricatorImageTransformer::saveImageDataInAnyFormat(
|
||||
$dst,
|
||||
'image/png');
|
||||
}
|
||||
|
||||
private function newTemplateFile($emblem, $width, $height) {
|
||||
$all_resources = self::getAllResources();
|
||||
|
||||
$scores = array();
|
||||
$ratio = $width / $height;
|
||||
foreach ($all_resources as $key => $resource) {
|
||||
// We can't use an emblem resource for a different emblem, nor for an
|
||||
// icon base. We also can't use an icon base as an emblem. That is, if
|
||||
// we're looking for a picture of a red dot, we have to actually find
|
||||
// a red dot, not just any image which happens to have a similar size.
|
||||
if (idx($resource, 'emblem') !== $emblem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$resource_width = $resource['width'];
|
||||
$resource_height = $resource['height'];
|
||||
|
||||
// Never use a resource with a different aspect ratio.
|
||||
if (($resource_width / $resource_height) !== $ratio) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to use custom resources instead of default resources.
|
||||
if ($resource['default']) {
|
||||
$default_score = 1;
|
||||
} else {
|
||||
$default_score = 0;
|
||||
}
|
||||
|
||||
$width_diff = ($resource_width - $width);
|
||||
|
||||
// If we have to resize an image, we'd rather scale a larger image down
|
||||
// than scale a smaller image up.
|
||||
if ($width_diff < 0) {
|
||||
$scale_score = 1;
|
||||
} else {
|
||||
$scale_score = 0;
|
||||
}
|
||||
|
||||
// Otherwise, we'd rather scale an image a little bit (ideally, zero)
|
||||
// than scale an image a lot.
|
||||
$width_score = abs($width_diff);
|
||||
|
||||
$scores[$key] = id(new PhutilSortVector())
|
||||
->addInt($default_score)
|
||||
->addInt($scale_score)
|
||||
->addInt($width_score);
|
||||
}
|
||||
|
||||
if (!$scores) {
|
||||
if ($emblem === null) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Found no background template resource for dimensions %dx%d.',
|
||||
$width,
|
||||
$height));
|
||||
} else {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Found no template resource (for emblem "%s") with dimensions '.
|
||||
'%dx%d.',
|
||||
$emblem,
|
||||
$width,
|
||||
$height));
|
||||
}
|
||||
}
|
||||
|
||||
$scores = msortv($scores, 'getSelf');
|
||||
$best_score = head_key($scores);
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$resource = $all_resources[$best_score];
|
||||
if ($resource['source-type'] === 'builtin') {
|
||||
$file = PhabricatorFile::loadBuiltin($viewer, $resource['source']);
|
||||
if (!$file) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Failed to load favicon template builtin "%s".',
|
||||
$resource['source']));
|
||||
}
|
||||
} else {
|
||||
$file = id(new PhabricatorFileQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($resource['source']))
|
||||
->executeOne();
|
||||
if (!$file) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Failed to load favicon template with PHID "%s".',
|
||||
$resource['source']));
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'width' => $resource['width'],
|
||||
'height' => $resource['height'],
|
||||
'file' => $file,
|
||||
);
|
||||
}
|
||||
|
||||
private function newFaviconFile($data) {
|
||||
return PhabricatorFile::newFromFileData(
|
||||
$data,
|
||||
array(
|
||||
'name' => 'favicon',
|
||||
'canCDN' => true,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorFaviconRefQuery extends Phobject {
|
||||
|
||||
private $refs;
|
||||
|
||||
public function withRefs(array $refs) {
|
||||
assert_instances_of($refs, 'PhabricatorFaviconRef');
|
||||
$this->refs = $refs;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function execute() {
|
||||
$viewer = PhabricatorUser::getOmnipotentUser();
|
||||
|
||||
$refs = $this->refs;
|
||||
|
||||
$config_digest = PhabricatorFaviconRef::newConfigurationDigest();
|
||||
|
||||
$ref_map = array();
|
||||
foreach ($refs as $ref) {
|
||||
$ref_digest = $ref->newDigest();
|
||||
$ref_key = "favicon({$config_digest},{$ref_digest},8)";
|
||||
|
||||
$ref
|
||||
->setViewer($viewer)
|
||||
->setCacheKey($ref_key);
|
||||
|
||||
$ref_map[$ref_key] = $ref;
|
||||
}
|
||||
|
||||
$cache = PhabricatorCaches::getImmutableCache();
|
||||
$ref_hits = $cache->getKeys(array_keys($ref_map));
|
||||
|
||||
foreach ($ref_hits as $ref_key => $ref_uri) {
|
||||
$ref_map[$ref_key]->setURI($ref_uri);
|
||||
unset($ref_map[$ref_key]);
|
||||
}
|
||||
|
||||
if ($ref_map) {
|
||||
$new_map = array();
|
||||
foreach ($ref_map as $ref_key => $ref) {
|
||||
$ref_uri = $ref->newURI();
|
||||
$ref->setURI($ref_uri);
|
||||
$new_map[$ref_key] = $ref_uri;
|
||||
}
|
||||
|
||||
$cache->setKeys($new_map);
|
||||
}
|
||||
|
||||
return $refs;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -152,61 +152,96 @@ final class PhabricatorFileQuery
|
|||
}
|
||||
|
||||
protected function loadPage() {
|
||||
$files = $this->loadStandardPage(new PhabricatorFile());
|
||||
$files = $this->loadStandardPage($this->newResultObject());
|
||||
|
||||
if (!$files) {
|
||||
return $files;
|
||||
}
|
||||
|
||||
// Figure out which files we need to load attached objects for. In most
|
||||
// cases, we need to load attached objects to perform policy checks for
|
||||
// files.
|
||||
|
||||
// However, in some special cases where we know files will always be
|
||||
// visible, we skip this. See T8478 and T13106.
|
||||
$need_objects = array();
|
||||
$need_xforms = array();
|
||||
foreach ($files as $file) {
|
||||
$always_visible = false;
|
||||
|
||||
if ($file->getIsProfileImage()) {
|
||||
$always_visible = true;
|
||||
}
|
||||
|
||||
if ($file->isBuiltin()) {
|
||||
$always_visible = true;
|
||||
}
|
||||
|
||||
if ($always_visible) {
|
||||
// We just treat these files as though they aren't attached to
|
||||
// anything. This saves a query in common cases when we're loading
|
||||
// profile images or builtins. We could be slightly more nuanced
|
||||
// about this and distinguish between "not attached to anything" and
|
||||
// "might be attached but policy checks don't need to care".
|
||||
$file->attachObjectPHIDs(array());
|
||||
continue;
|
||||
}
|
||||
|
||||
$need_objects[] = $file;
|
||||
$need_xforms[] = $file;
|
||||
}
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
$is_omnipotent = $viewer->isOmnipotent();
|
||||
|
||||
// We need to load attached objects to perform policy checks for files.
|
||||
// First, load the edges.
|
||||
|
||||
$edge_type = PhabricatorFileHasObjectEdgeType::EDGECONST;
|
||||
$file_phids = mpull($files, 'getPHID');
|
||||
$edges = id(new PhabricatorEdgeQuery())
|
||||
->withSourcePHIDs($file_phids)
|
||||
->withEdgeTypes(array($edge_type))
|
||||
->execute();
|
||||
|
||||
// If we have any files left which do need objects, load the edges now.
|
||||
$object_phids = array();
|
||||
foreach ($files as $file) {
|
||||
$phids = array_keys($edges[$file->getPHID()][$edge_type]);
|
||||
$file->attachObjectPHIDs($phids);
|
||||
if ($need_objects) {
|
||||
$edge_type = PhabricatorFileHasObjectEdgeType::EDGECONST;
|
||||
$file_phids = mpull($need_objects, 'getPHID');
|
||||
|
||||
if ($file->getIsProfileImage()) {
|
||||
// If this is a profile image, don't bother loading related files.
|
||||
// It will always be visible, and we can get into trouble if we try
|
||||
// to load objects and end up stuck in a cycle. See T8478.
|
||||
continue;
|
||||
}
|
||||
$edges = id(new PhabricatorEdgeQuery())
|
||||
->withSourcePHIDs($file_phids)
|
||||
->withEdgeTypes(array($edge_type))
|
||||
->execute();
|
||||
|
||||
if ($is_omnipotent) {
|
||||
// If the viewer is omnipotent, we don't need to load the associated
|
||||
// objects either since they can certainly see the object. Skipping
|
||||
// this can improve performance and prevent cycles.
|
||||
continue;
|
||||
}
|
||||
foreach ($need_objects as $file) {
|
||||
$phids = array_keys($edges[$file->getPHID()][$edge_type]);
|
||||
$file->attachObjectPHIDs($phids);
|
||||
|
||||
foreach ($phids as $phid) {
|
||||
$object_phids[$phid] = true;
|
||||
if ($is_omnipotent) {
|
||||
// If the viewer is omnipotent, we don't need to load the associated
|
||||
// objects either since the viewer can certainly see the object.
|
||||
// Skipping this can improve performance and prevent cycles. This
|
||||
// could possibly become part of the profile/builtin code above which
|
||||
// short circuits attacment policy checks in cases where we know them
|
||||
// to be unnecessary.
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($phids as $phid) {
|
||||
$object_phids[$phid] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 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;
|
||||
if ($need_xforms) {
|
||||
$xforms = id(new PhabricatorTransformedFile())->loadAllWhere(
|
||||
'transformedPHID IN (%Ls)',
|
||||
mpull($need_xforms, 'getPHID'));
|
||||
$xform_phids = mpull($xforms, 'getOriginalPHID', 'getTransformedPHID');
|
||||
foreach ($xform_phids as $derived_phid => $original_phid) {
|
||||
$object_phids[$original_phid] = true;
|
||||
}
|
||||
} else {
|
||||
$xform_phids = array();
|
||||
}
|
||||
|
||||
$object_phids = array_keys($object_phids);
|
||||
|
|
|
@ -1152,7 +1152,6 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
|
||||
$params = array(
|
||||
'name' => $builtin->getBuiltinDisplayName(),
|
||||
'ttl.relative' => phutil_units('7 days in seconds'),
|
||||
'canCDN' => true,
|
||||
'builtin' => $key,
|
||||
);
|
||||
|
@ -1648,7 +1647,7 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
public function getFieldValuesForConduit() {
|
||||
return array(
|
||||
'name' => $this->getName(),
|
||||
'dataURI' => $this->getCDNURI(),
|
||||
'dataURI' => $this->getCDNURI('data'),
|
||||
'size' => (int)$this->getByteSize(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -71,7 +71,8 @@ final class PhabricatorHarbormasterApplication extends PhabricatorApplication {
|
|||
),
|
||||
'build/' => array(
|
||||
$this->getQueryRoutePattern() => 'HarbormasterBuildListController',
|
||||
'(?P<id>\d+)/' => 'HarbormasterBuildViewController',
|
||||
'(?P<id>\d+)/(?:(?P<generation>\d+)/)?'
|
||||
=> 'HarbormasterBuildViewController',
|
||||
'(?P<action>pause|resume|restart|abort)/'.
|
||||
'(?P<id>\d+)/(?:(?P<via>[^/]+)/)?'
|
||||
=> 'HarbormasterBuildActionController',
|
||||
|
|
|
@ -8,7 +8,6 @@ final class HarbormasterBuildViewController
|
|||
$viewer = $request->getUser();
|
||||
|
||||
$id = $request->getURIData('id');
|
||||
$generation = $request->getInt('g');
|
||||
|
||||
$build = id(new HarbormasterBuildQuery())
|
||||
->setViewer($viewer)
|
||||
|
@ -21,6 +20,7 @@ final class HarbormasterBuildViewController
|
|||
require_celerity_resource('harbormaster-css');
|
||||
|
||||
$title = pht('Build %d', $id);
|
||||
$warnings = array();
|
||||
|
||||
$page_header = id(new PHUIHeaderView())
|
||||
->setHeader($title)
|
||||
|
@ -28,7 +28,9 @@ final class HarbormasterBuildViewController
|
|||
->setPolicyObject($build)
|
||||
->setHeaderIcon('fa-cubes');
|
||||
|
||||
if ($build->isRestarting()) {
|
||||
$is_restarting = $build->isRestarting();
|
||||
|
||||
if ($is_restarting) {
|
||||
$page_header->setStatus(
|
||||
'fa-exclamation-triangle', 'red', pht('Restarting'));
|
||||
} else if ($build->isPausing()) {
|
||||
|
@ -42,19 +44,53 @@ final class HarbormasterBuildViewController
|
|||
'fa-exclamation-triangle', 'red', pht('Aborting'));
|
||||
}
|
||||
|
||||
$max_generation = (int)$build->getBuildGeneration();
|
||||
if ($max_generation === 0) {
|
||||
$min_generation = 0;
|
||||
} else {
|
||||
$min_generation = 1;
|
||||
}
|
||||
|
||||
if ($is_restarting) {
|
||||
$max_generation = $max_generation + 1;
|
||||
}
|
||||
|
||||
$generation = $request->getURIData('generation');
|
||||
if ($generation === null) {
|
||||
$generation = $max_generation;
|
||||
} else {
|
||||
$generation = (int)$generation;
|
||||
}
|
||||
|
||||
if ($generation < $min_generation || $generation > $max_generation) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
if ($generation < $max_generation) {
|
||||
$warnings[] = pht(
|
||||
'You are viewing an older run of this build. %s',
|
||||
phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $build->getURI(),
|
||||
),
|
||||
pht('View Current Build')));
|
||||
}
|
||||
|
||||
|
||||
$curtain = $this->buildCurtainView($build);
|
||||
$properties = $this->buildPropertyList($build);
|
||||
$history = $this->buildHistoryTable(
|
||||
$build,
|
||||
$generation,
|
||||
$min_generation,
|
||||
$max_generation);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$this->addBuildableCrumb($crumbs, $build->getBuildable());
|
||||
$crumbs->addTextCrumb($title);
|
||||
$crumbs->setBorder(true);
|
||||
|
||||
if ($generation === null || $generation > $build->getBuildGeneration() ||
|
||||
$generation < 0) {
|
||||
$generation = $build->getBuildGeneration();
|
||||
}
|
||||
|
||||
$build_targets = id(new HarbormasterBuildTargetQuery())
|
||||
->setViewer($viewer)
|
||||
->needBuildSteps(true)
|
||||
|
@ -264,14 +300,25 @@ final class HarbormasterBuildViewController
|
|||
new HarbormasterBuildTransactionQuery());
|
||||
$timeline->setShouldTerminate(true);
|
||||
|
||||
if ($warnings) {
|
||||
$warnings = id(new PHUIInfoView())
|
||||
->setErrors($warnings)
|
||||
->setSeverity(PHUIInfoView::SEVERITY_WARNING);
|
||||
} else {
|
||||
$warnings = null;
|
||||
}
|
||||
|
||||
$view = id(new PHUITwoColumnView())
|
||||
->setHeader($page_header)
|
||||
->setCurtain($curtain)
|
||||
->setMainColumn(array(
|
||||
$properties,
|
||||
$targets,
|
||||
$timeline,
|
||||
));
|
||||
->setMainColumn(
|
||||
array(
|
||||
$warnings,
|
||||
$properties,
|
||||
$history,
|
||||
$targets,
|
||||
$timeline,
|
||||
));
|
||||
|
||||
return $this->newPage()
|
||||
->setTitle($title)
|
||||
|
@ -561,10 +608,6 @@ final class HarbormasterBuildViewController
|
|||
pht('Build Plan'),
|
||||
$handles[$build->getBuildPlanPHID()]->renderLink());
|
||||
|
||||
$properties->addProperty(
|
||||
pht('Restarts'),
|
||||
$build->getBuildGeneration());
|
||||
|
||||
$properties->addProperty(
|
||||
pht('Status'),
|
||||
$this->getStatus($build));
|
||||
|
@ -576,6 +619,53 @@ final class HarbormasterBuildViewController
|
|||
|
||||
}
|
||||
|
||||
private function buildHistoryTable(
|
||||
HarbormasterBuild $build,
|
||||
$generation,
|
||||
$min_generation,
|
||||
$max_generation) {
|
||||
|
||||
if ($max_generation === $min_generation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$uri = $build->getURI();
|
||||
|
||||
$rows = array();
|
||||
$rowc = array();
|
||||
for ($ii = $max_generation; $ii >= $min_generation; $ii--) {
|
||||
if ($generation == $ii) {
|
||||
$rowc[] = 'highlighted';
|
||||
} else {
|
||||
$rowc[] = null;
|
||||
}
|
||||
|
||||
$rows[] = array(
|
||||
phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $uri.$ii.'/',
|
||||
),
|
||||
pht('Run %d', $ii)),
|
||||
);
|
||||
}
|
||||
|
||||
$table = id(new AphrontTableView($rows))
|
||||
->setColumnClasses(
|
||||
array(
|
||||
'pri wide',
|
||||
))
|
||||
->setRowClasses($rowc);
|
||||
|
||||
return id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('History'))
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->setTable($table);
|
||||
}
|
||||
|
||||
|
||||
private function getStatus(HarbormasterBuild $build) {
|
||||
$status_view = new PHUIStatusListView();
|
||||
|
||||
|
|
|
@ -147,7 +147,7 @@ final class HarbormasterBuildEngine extends Phobject {
|
|||
// If it is different, they will automatically stop what they're doing
|
||||
// and abort.
|
||||
|
||||
// Previously we used to delete targets, logs and artifacts here. Instead
|
||||
// Previously we used to delete targets, logs and artifacts here. Instead,
|
||||
// leave them around so users can view previous generations of this build.
|
||||
}
|
||||
|
||||
|
|
|
@ -51,13 +51,22 @@ final class HarbormasterUIEventListener
|
|||
return;
|
||||
}
|
||||
|
||||
$buildable = id(new HarbormasterBuildableQuery())
|
||||
->setViewer($viewer)
|
||||
->withManualBuildables(false)
|
||||
->withBuildablePHIDs(array($buildable_phid))
|
||||
->needBuilds(true)
|
||||
->needTargets(true)
|
||||
->executeOne();
|
||||
try {
|
||||
$buildable = id(new HarbormasterBuildableQuery())
|
||||
->setViewer($viewer)
|
||||
->withManualBuildables(false)
|
||||
->withBuildablePHIDs(array($buildable_phid))
|
||||
->needBuilds(true)
|
||||
->needTargets(true)
|
||||
->executeOne();
|
||||
} catch (PhabricatorPolicyException $ex) {
|
||||
// TODO: See PHI430. When this query raises a policy exception, it
|
||||
// fatals the whole page because it happens very late in execution,
|
||||
// during final page rendering. If the viewer can't see the buildable or
|
||||
// some of the builds, just hide this element for now.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$buildable) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -236,7 +236,10 @@ final class PhabricatorMemeEngine extends Phobject {
|
|||
Filesystem::writeFile($output_name, $memed_frame_data);
|
||||
}
|
||||
|
||||
$future = new ExecFuture('convert -loop 0 %Ls %s', $output_files, $output);
|
||||
$future = new ExecFuture(
|
||||
'convert -dispose background -loop 0 %Ls %s',
|
||||
$output_files,
|
||||
$output);
|
||||
$future->setTimeout(10)->resolvex();
|
||||
|
||||
return Filesystem::readFile($output);
|
||||
|
@ -297,6 +300,9 @@ final class PhabricatorMemeEngine extends Phobject {
|
|||
$font_max = 72;
|
||||
$font_min = 5;
|
||||
|
||||
$margin_x = 16;
|
||||
$margin_y = 16;
|
||||
|
||||
$last = null;
|
||||
$cursor = floor(($font_max + $font_min) / 2);
|
||||
$min = $font_min;
|
||||
|
@ -321,12 +327,12 @@ final class PhabricatorMemeEngine extends Phobject {
|
|||
// text extends, for example if it has a "y".
|
||||
$descend = $box[3];
|
||||
|
||||
if ($height > $dim_y) {
|
||||
if (($height + $margin_y) > $dim_y) {
|
||||
$all_fit = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($width > $dim_x) {
|
||||
if (($width + $margin_x) > $dim_x) {
|
||||
$all_fit = false;
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -284,9 +284,6 @@ final class ManiphestTaskDetailController extends ManiphestController {
|
|||
$edit_config = $edit_engine->loadDefaultEditConfiguration($task);
|
||||
$can_create = (bool)$edit_config;
|
||||
|
||||
$can_reassign = $edit_engine->hasEditAccessToTransaction(
|
||||
ManiphestTaskOwnerTransaction::TRANSACTIONTYPE);
|
||||
|
||||
if ($can_create) {
|
||||
$form_key = $edit_config->getIdentifier();
|
||||
$edit_uri = id(new PhutilURI("/task/edit/form/{$form_key}/"))
|
||||
|
|
|
@ -357,29 +357,69 @@ final class PhabricatorProjectQuery
|
|||
}
|
||||
|
||||
protected function didFilterPage(array $projects) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
if ($this->needImages) {
|
||||
$file_phids = mpull($projects, 'getProfileImagePHID');
|
||||
$file_phids = array_filter($file_phids);
|
||||
$need_images = $projects;
|
||||
|
||||
// First, try to load custom profile images for any projects with custom
|
||||
// images.
|
||||
$file_phids = array();
|
||||
foreach ($need_images as $key => $project) {
|
||||
$image_phid = $project->getProfileImagePHID();
|
||||
if ($image_phid) {
|
||||
$file_phids[$key] = $image_phid;
|
||||
}
|
||||
}
|
||||
|
||||
if ($file_phids) {
|
||||
$files = id(new PhabricatorFileQuery())
|
||||
->setParentQuery($this)
|
||||
->setViewer($this->getViewer())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs($file_phids)
|
||||
->execute();
|
||||
$files = mpull($files, null, 'getPHID');
|
||||
} else {
|
||||
$files = array();
|
||||
|
||||
foreach ($file_phids as $key => $image_phid) {
|
||||
$file = idx($files, $image_phid);
|
||||
if (!$file) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$need_images[$key]->attachProfileImageFile($file);
|
||||
unset($need_images[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($projects as $project) {
|
||||
$file = idx($files, $project->getProfileImagePHID());
|
||||
if (!$file) {
|
||||
$builtin = PhabricatorProjectIconSet::getIconImage(
|
||||
$project->getIcon());
|
||||
$file = PhabricatorFile::loadBuiltin($this->getViewer(),
|
||||
'projects/'.$builtin);
|
||||
// For projects with default images, or projects where the custom image
|
||||
// failed to load, load a builtin image.
|
||||
if ($need_images) {
|
||||
$builtin_map = array();
|
||||
$builtins = array();
|
||||
foreach ($need_images as $key => $project) {
|
||||
$icon = $project->getIcon();
|
||||
|
||||
$builtin_name = PhabricatorProjectIconSet::getIconImage($icon);
|
||||
$builtin_name = 'projects/'.$builtin_name;
|
||||
|
||||
$builtin = id(new PhabricatorFilesOnDiskBuiltinFile())
|
||||
->setName($builtin_name);
|
||||
|
||||
$builtin_key = $builtin->getBuiltinFileKey();
|
||||
|
||||
$builtins[] = $builtin;
|
||||
$builtin_map[$key] = $builtin_key;
|
||||
}
|
||||
|
||||
$builtin_files = PhabricatorFile::loadBuiltins(
|
||||
$viewer,
|
||||
$builtins);
|
||||
|
||||
foreach ($need_images as $key => $project) {
|
||||
$builtin_key = $builtin_map[$key];
|
||||
$builtin_file = $builtin_files[$builtin_key];
|
||||
$project->attachProfileImageFile($builtin_file);
|
||||
}
|
||||
$project->attachProfileImageFile($file);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,14 @@ final class PhabricatorProjectSearchEngine
|
|||
id(new PhabricatorSearchTextField())
|
||||
->setLabel(pht('Name'))
|
||||
->setKey('name'),
|
||||
id(new PhabricatorSearchStringListField())
|
||||
->setLabel(pht('Slugs'))
|
||||
->setIsHidden(true)
|
||||
->setKey('slugs')
|
||||
->setDescription(
|
||||
pht(
|
||||
'Search for projects with particular slugs. (Slugs are the same '.
|
||||
'as project hashtags.)')),
|
||||
id(new PhabricatorUsersSearchField())
|
||||
->setLabel(pht('Members'))
|
||||
->setKey('memberPHIDs')
|
||||
|
@ -81,6 +89,10 @@ final class PhabricatorProjectSearchEngine
|
|||
$query->withNameTokens($tokens);
|
||||
}
|
||||
|
||||
if ($map['slugs']) {
|
||||
$query->withSlugs($map['slugs']);
|
||||
}
|
||||
|
||||
if ($map['memberPHIDs']) {
|
||||
$query->withMemberPHIDs($map['memberPHIDs']);
|
||||
}
|
||||
|
|
|
@ -45,12 +45,19 @@ final class PhabricatorSubscriptionsSubscribersPolicyRule
|
|||
$this->subscribed[$viewer_phid] = array();
|
||||
}
|
||||
|
||||
// Load the project PHIDs the user is a member of.
|
||||
// Load the project PHIDs the user is a member of. We use the omnipotent
|
||||
// user here because projects may themselves have "Subscribers" visibility
|
||||
// policies and we don't want to get stuck in an infinite stack of
|
||||
// recursive policy checks. See T13106.
|
||||
if (!isset($this->sourcePHIDs[$viewer_phid])) {
|
||||
$source_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
|
||||
$viewer_phid,
|
||||
PhabricatorProjectMemberOfProjectEdgeType::EDGECONST);
|
||||
$projects = id(new PhabricatorProjectQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withMemberPHIDs(array($viewer_phid))
|
||||
->execute();
|
||||
|
||||
$source_phids = mpull($projects, 'getPHID');
|
||||
$source_phids[] = $viewer_phid;
|
||||
|
||||
$this->sourcePHIDs[$viewer_phid] = $source_phids;
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,6 @@ final class PhabricatorSystemApplication extends PhabricatorApplication {
|
|||
'/readonly/' => array(
|
||||
'(?P<reason>[^/]+)/' => 'PhabricatorSystemReadOnlyController',
|
||||
),
|
||||
'/favicon.ico' => 'PhabricatorSystemFaviconController',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorSystemFaviconController extends PhabricatorController {
|
||||
|
||||
public function shouldRequireLogin() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$webroot = dirname(phutil_get_library_root('phabricator')).'/webroot/';
|
||||
$content = Filesystem::readFile($webroot.'/rsrc/favicons/favicon.ico');
|
||||
|
||||
return id(new AphrontFileResponse())
|
||||
->setContent($content)
|
||||
->setMimeType('image/x-icon')
|
||||
->setCacheDurationInSeconds(phutil_units('24 hours in seconds'))
|
||||
->setCanCDN(true);
|
||||
}
|
||||
}
|
|
@ -1398,59 +1398,6 @@ abstract class PhabricatorEditEngine
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the viewer could apply a certain type of change by using the
|
||||
* normal "Edit" form.
|
||||
*
|
||||
* This method returns `true` if the user has access to an edit form and
|
||||
* that edit form has a field which applied the specified transaction type,
|
||||
* and that field is visible and editable for the user.
|
||||
*
|
||||
* For example, you can use it to test if a user is able to reassign tasks
|
||||
* or not, prior to rendering dedicated UI for task reassignment.
|
||||
*
|
||||
* Note that this method does NOT test if the user can actually edit the
|
||||
* current object, just if they have access to the related field.
|
||||
*
|
||||
* @param const Transaction type to test for.
|
||||
* @return bool True if the user could "Edit" to apply the transaction type.
|
||||
*/
|
||||
final public function hasEditAccessToTransaction($xaction_type) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$object = $this->getTargetObject();
|
||||
if (!$object) {
|
||||
$object = $this->newEditableObject();
|
||||
}
|
||||
|
||||
$config = $this->loadDefaultEditConfiguration($object);
|
||||
if (!$config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$fields = $this->buildEditFields($object);
|
||||
|
||||
$field = null;
|
||||
foreach ($fields as $form_field) {
|
||||
$field_xaction_type = $form_field->getTransactionType();
|
||||
if ($field_xaction_type === $xaction_type) {
|
||||
$field = $form_field;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$field) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$field->shouldReadValueFromSubmit()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function newNUXButton($text) {
|
||||
$specs = $this->newCreateActionSpecifications(array());
|
||||
$head = head($specs);
|
||||
|
@ -1968,6 +1915,7 @@ abstract class PhabricatorEditEngine
|
|||
->setContinueOnNoEffect($request->isContinueRequest())
|
||||
->setContinueOnMissingFields(true)
|
||||
->setContentSourceFromRequest($request)
|
||||
->setRaiseWarnings(!$request->getBool('editEngine.warnings'))
|
||||
->setIsPreview($is_preview);
|
||||
|
||||
try {
|
||||
|
@ -1980,6 +1928,10 @@ abstract class PhabricatorEditEngine
|
|||
return id(new PhabricatorApplicationTransactionNoEffectResponse())
|
||||
->setCancelURI($view_uri)
|
||||
->setException($ex);
|
||||
} catch (PhabricatorApplicationTransactionWarningException $ex) {
|
||||
return id(new PhabricatorApplicationTransactionWarningResponse())
|
||||
->setCancelURI($view_uri)
|
||||
->setException($ex);
|
||||
}
|
||||
|
||||
if (!$is_preview) {
|
||||
|
|
|
@ -48,6 +48,7 @@ abstract class PhabricatorApplicationTransactionEditor
|
|||
private $mentionedPHIDs;
|
||||
private $continueOnNoEffect;
|
||||
private $continueOnMissingFields;
|
||||
private $raiseWarnings;
|
||||
private $parentMessageID;
|
||||
private $heraldAdapter;
|
||||
private $heraldTranscript;
|
||||
|
@ -273,6 +274,15 @@ abstract class PhabricatorApplicationTransactionEditor
|
|||
return $this->applicationEmail;
|
||||
}
|
||||
|
||||
public function setRaiseWarnings($raise_warnings) {
|
||||
$this->raiseWarnings = $raise_warnings;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRaiseWarnings() {
|
||||
return $this->raiseWarnings;
|
||||
}
|
||||
|
||||
public function getTransactionTypesForObject($object) {
|
||||
$old = $this->object;
|
||||
try {
|
||||
|
@ -919,6 +929,19 @@ abstract class PhabricatorApplicationTransactionEditor
|
|||
throw new PhabricatorApplicationTransactionValidationException($errors);
|
||||
}
|
||||
|
||||
if ($this->raiseWarnings) {
|
||||
$warnings = array();
|
||||
foreach ($xactions as $xaction) {
|
||||
if ($this->hasWarnings($object, $xaction)) {
|
||||
$warnings[] = $xaction;
|
||||
}
|
||||
}
|
||||
if ($warnings) {
|
||||
throw new PhabricatorApplicationTransactionWarningException(
|
||||
$warnings);
|
||||
}
|
||||
}
|
||||
|
||||
$this->willApplyTransactions($object, $xactions);
|
||||
|
||||
if ($object->getID()) {
|
||||
|
@ -4277,4 +4300,28 @@ abstract class PhabricatorApplicationTransactionEditor
|
|||
}
|
||||
}
|
||||
|
||||
private function hasWarnings($object, $xaction) {
|
||||
// TODO: For the moment, this is a very un-modular hack to support
|
||||
// exactly one type of warning (mentioning users on a draft revision)
|
||||
// that we want to show. See PHI433.
|
||||
|
||||
if (!($object instanceof DifferentialRevision)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$object->isDraft()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$type = $xaction->getTransactionType();
|
||||
if ($type != PhabricatorTransactions::TYPE_SUBSCRIBERS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// NOTE: This will currently warn even if you're only removing
|
||||
// subscribers.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorApplicationTransactionWarningException
|
||||
extends Exception {
|
||||
|
||||
private $xactions;
|
||||
|
||||
public function __construct(array $xactions) {
|
||||
$this->xactions = $xactions;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorApplicationTransactionWarningResponse
|
||||
extends AphrontProxyResponse {
|
||||
|
||||
private $viewer;
|
||||
private $exception;
|
||||
private $cancelURI;
|
||||
|
||||
public function setCancelURI($cancel_uri) {
|
||||
$this->cancelURI = $cancel_uri;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setException(
|
||||
PhabricatorApplicationTransactionWarningException $exception) {
|
||||
$this->exception = $exception;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function buildProxy() {
|
||||
return new AphrontDialogResponse();
|
||||
}
|
||||
|
||||
public function reduceProxyResponse() {
|
||||
$request = $this->getRequest();
|
||||
|
||||
$title = pht('Warning: Unexpected Effects');
|
||||
|
||||
$head = pht(
|
||||
'This is a draft revision that will not publish any notifications '.
|
||||
'until the author requests review.');
|
||||
$tail = pht(
|
||||
'Mentioned or subscribed users will not be notified.');
|
||||
|
||||
$continue = pht('Tell No One');
|
||||
|
||||
$dialog = id(new AphrontDialogView())
|
||||
->setViewer($request->getViewer())
|
||||
->setTitle($title);
|
||||
|
||||
$dialog->appendParagraph($head);
|
||||
$dialog->appendParagraph($tail);
|
||||
|
||||
$passthrough = $request->getPassthroughRequestParameters();
|
||||
foreach ($passthrough as $key => $value) {
|
||||
$dialog->addHiddenInput($key, $value);
|
||||
}
|
||||
|
||||
$dialog
|
||||
->addHiddenInput('editEngine.warnings', 1)
|
||||
->addSubmitButton($continue)
|
||||
->addCancelButton($this->cancelURI);
|
||||
|
||||
return $this->getProxy()->setDialog($dialog);
|
||||
}
|
||||
|
||||
}
|
|
@ -123,7 +123,7 @@ Otherwise, here's a general description of what you need to install:
|
|||
- MySQL Server (usually "mysqld" or "mysql-server")
|
||||
- PHP (usually "php")
|
||||
- Required PHP extensions: mbstring, iconv, mysql (or mysqli), curl, pcntl
|
||||
(these might be something like "php-mysql" or "php5-mysql")
|
||||
(these might be something like "php-mysql" or "php5-mysqlnd")
|
||||
- Optional PHP extensions: gd, apc (special instructions for APC are available
|
||||
below if you have difficulty installing it), xhprof (instructions below,
|
||||
you only need this if you are developing Phabricator)
|
||||
|
|
|
@ -9,14 +9,23 @@ Overview
|
|||
|
||||
This document describes available support resources.
|
||||
|
||||
The upstream provides active, free support for a narrow range of problems
|
||||
(primarily, security issues and reproducible bugs).
|
||||
The upstream provides free support for a narrow range of problems (primarily,
|
||||
security issues and reproducible bugs) and paid support for virtually anything.
|
||||
|
||||
The upstream does not provide free support for general problems with installing
|
||||
or configuring Phabricator. You may be able to get some help with these
|
||||
kinds of issues from the community.
|
||||
|
||||
|
||||
Paid Support
|
||||
============
|
||||
|
||||
If you'd like upstream support, see ((pacts)).
|
||||
|
||||
This is the only way to request features and the only way to get guaranteed
|
||||
answers from experts quickly.
|
||||
|
||||
|
||||
Reporting Security Vulnerabilities
|
||||
==================================
|
||||
|
||||
|
@ -31,8 +40,7 @@ Reporting Bugs
|
|||
|
||||
The upstream will accept **reproducible** bug reports in modern, first-party
|
||||
production code running in reasonable environments. Before submitting a bug
|
||||
report you **must update** to the latest version of Phabricator. This reduces
|
||||
support costs on the upstream, please be mindful.
|
||||
report you **must update** to the latest version of Phabricator.
|
||||
|
||||
To report bugs, see @{article:Contributing Bug Reports}.
|
||||
|
||||
|
@ -52,14 +60,10 @@ If you'd like to contribute to Phabricator, start with
|
|||
Installation and Setup Help
|
||||
===========================
|
||||
|
||||
Installation and setup help is available from the upstream at consulting rates.
|
||||
See [[ https://secure.phabricator.com/w/consulting/ | Consulting ]] for details.
|
||||
|
||||
Helping individual installs navigate unique setup problems takes our time
|
||||
away from developing Phabricator, so we can not offer this service for free.
|
||||
|
||||
You may be able to get free help with these issues from the
|
||||
[[ https://phurl.io/u/discourse | community ]]. See below for details.
|
||||
[[ https://phurl.io/u/discourse | community ]].
|
||||
|
||||
You can also pay us for support. See ((pacts)).
|
||||
|
||||
|
||||
Hosting
|
||||
|
@ -67,8 +71,8 @@ Hosting
|
|||
|
||||
The upstream offers Phabricator as a hosted service at
|
||||
[[ https://phacility.com | Phacility ]]. This simplifies setting up and
|
||||
operating a Phabricator instance, and gives you access to a broader range
|
||||
of upstream support services.
|
||||
operating a Phabricator instance, and automatically gives you access to a
|
||||
broader range of upstream support services.
|
||||
|
||||
Running this service gives us a strong financial incentive to make installing
|
||||
and operating Phabricator as difficult as possible. Blinded by greed, we toil
|
||||
|
@ -79,9 +83,10 @@ ourselves can hope to navigate.
|
|||
Phabricator Community
|
||||
=====================
|
||||
|
||||
We provide hosting for a
|
||||
[[ https://phurl.io/u/discourse | Discussion Forum ]]
|
||||
where admins and users help and answer questions from other community members.
|
||||
Upstream developers may occasionally participate, but this is mostly
|
||||
a user to user community. If you run into general problems, but are not
|
||||
interested in paid support, this is the main place to find help.
|
||||
We provide hosting for a [[ https://phurl.io/u/discourse | Discussion Forum ]]
|
||||
where admininstrators and users help and answer questions from other community
|
||||
members.
|
||||
|
||||
Upstream developers occasionally participate, but this is mostly a user to user
|
||||
community. If you run into general problems, but are not interested in paid
|
||||
support, this is the main place to find help.
|
||||
|
|
|
@ -43,6 +43,27 @@ The most useful header for routing is generally `X-Phabricator-Stamps`. This
|
|||
is a list of attributes which describe the object the mail is about and the
|
||||
actions which the mail informs you about.
|
||||
|
||||
Stamps and Gmail
|
||||
================
|
||||
|
||||
If you use a client which can not perform header matching (like Gmail), you can
|
||||
change the {nav Settings > Email Format > Send Stamps} setting to include the
|
||||
stamps in the mail body and then match them with body rules.
|
||||
|
||||
When writing filter rules against mail stamps in Gmail, you should quote any
|
||||
filters you want to apply. For example, specify rules like this, with quotes:
|
||||
|
||||
> "author(@alice)"
|
||||
|
||||
Note that Gmail will ignore some symbols when matching mail against filtering
|
||||
rules, so you can get false positives if the body of the message includes text
|
||||
like `author alice` (the same words in the same order, without the special
|
||||
symbols).
|
||||
|
||||
You'll also get false positives if the message body includes the text of a
|
||||
mail stamp explicitly in a normal text field like a summary, description, or
|
||||
comment.
|
||||
|
||||
There's no way to avoid these false positives other than using a different
|
||||
client with support for more powerful filtering rules, but these false
|
||||
positives should normally be uncommon.
|
||||
|
|
|
@ -279,6 +279,15 @@ abstract class PhabricatorObjectRemarkupRule extends PhutilRemarkupRule {
|
|||
return $matches[0];
|
||||
}
|
||||
|
||||
// If we're rendering a table of contents, just render the raw input.
|
||||
// This could perhaps be handled more gracefully but it seems unusual to
|
||||
// put something like "{P123}" in a header and it's not obvious what users
|
||||
// expect? See T8845.
|
||||
$engine = $this->getEngine();
|
||||
if ($engine->getState('toc')) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
return $this->markupObject(array(
|
||||
'type' => 'embed',
|
||||
'id' => $matches[1],
|
||||
|
@ -292,6 +301,12 @@ abstract class PhabricatorObjectRemarkupRule extends PhutilRemarkupRule {
|
|||
return $matches[0];
|
||||
}
|
||||
|
||||
// If we're rendering a table of contents, just render the monogram.
|
||||
$engine = $this->getEngine();
|
||||
if ($engine->getState('toc')) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
return $this->markupObject(array(
|
||||
'type' => 'ref',
|
||||
'id' => $matches[1],
|
||||
|
|
|
@ -71,6 +71,14 @@ class PhabricatorBarePageView extends AphrontPageView {
|
|||
));
|
||||
}
|
||||
|
||||
$referrer_tag = phutil_tag(
|
||||
'meta',
|
||||
array(
|
||||
'name' => 'referrer',
|
||||
'content' => 'no-referrer',
|
||||
));
|
||||
|
||||
|
||||
$mask_icon = phutil_tag(
|
||||
'link',
|
||||
array(
|
||||
|
@ -80,47 +88,7 @@ class PhabricatorBarePageView extends AphrontPageView {
|
|||
'/rsrc/favicons/mask-icon.svg'),
|
||||
));
|
||||
|
||||
$icon_tag_76 = phutil_tag(
|
||||
'link',
|
||||
array(
|
||||
'rel' => 'apple-touch-icon',
|
||||
'href' => celerity_get_resource_uri(
|
||||
'/rsrc/favicons/apple-touch-icon-76x76.png'),
|
||||
));
|
||||
|
||||
$icon_tag_120 = phutil_tag(
|
||||
'link',
|
||||
array(
|
||||
'rel' => 'apple-touch-icon',
|
||||
'sizes' => '120x120',
|
||||
'href' => celerity_get_resource_uri(
|
||||
'/rsrc/favicons/apple-touch-icon-120x120.png'),
|
||||
));
|
||||
|
||||
$icon_tag_152 = phutil_tag(
|
||||
'link',
|
||||
array(
|
||||
'rel' => 'apple-touch-icon',
|
||||
'sizes' => '152x152',
|
||||
'href' => celerity_get_resource_uri(
|
||||
'/rsrc/favicons/apple-touch-icon-152x152.png'),
|
||||
));
|
||||
|
||||
$favicon_tag = phutil_tag(
|
||||
'link',
|
||||
array(
|
||||
'id' => 'favicon',
|
||||
'rel' => 'shortcut icon',
|
||||
'href' => celerity_get_resource_uri(
|
||||
'/rsrc/favicons/favicon.ico'),
|
||||
));
|
||||
|
||||
$referrer_tag = phutil_tag(
|
||||
'meta',
|
||||
array(
|
||||
'name' => 'referrer',
|
||||
'content' => 'no-referrer',
|
||||
));
|
||||
$favicon_links = $this->newFavicons();
|
||||
|
||||
$response = CelerityAPI::getStaticResourceResponse();
|
||||
|
||||
|
@ -136,13 +104,10 @@ class PhabricatorBarePageView extends AphrontPageView {
|
|||
}
|
||||
|
||||
return hsprintf(
|
||||
'%s%s%s%s%s%s%s%s',
|
||||
'%s%s%s%s%s',
|
||||
$viewport_tag,
|
||||
$mask_icon,
|
||||
$icon_tag_76,
|
||||
$icon_tag_120,
|
||||
$icon_tag_152,
|
||||
$favicon_tag,
|
||||
$favicon_links,
|
||||
$referrer_tag,
|
||||
$response->renderResourcesOfType('css'));
|
||||
}
|
||||
|
@ -156,4 +121,61 @@ class PhabricatorBarePageView extends AphrontPageView {
|
|||
return $response->renderResourcesOfType('js');
|
||||
}
|
||||
|
||||
private function newFavicons() {
|
||||
$favicon_refs = array(
|
||||
array(
|
||||
'rel' => 'apple-touch-icon',
|
||||
'sizes' => '76x76',
|
||||
'width' => 76,
|
||||
'height' => 76,
|
||||
),
|
||||
array(
|
||||
'rel' => 'apple-touch-icon',
|
||||
'sizes' => '120x120',
|
||||
'width' => 120,
|
||||
'height' => 120,
|
||||
),
|
||||
array(
|
||||
'rel' => 'apple-touch-icon',
|
||||
'sizes' => '152x152',
|
||||
'width' => 152,
|
||||
'height' => 152,
|
||||
),
|
||||
array(
|
||||
'rel' => 'icon',
|
||||
'id' => 'favicon',
|
||||
'width' => 64,
|
||||
'height' => 64,
|
||||
),
|
||||
);
|
||||
|
||||
$fetch_refs = array();
|
||||
foreach ($favicon_refs as $key => $spec) {
|
||||
$ref = id(new PhabricatorFaviconRef())
|
||||
->setWidth($spec['width'])
|
||||
->setHeight($spec['height']);
|
||||
|
||||
$favicon_refs[$key]['ref'] = $ref;
|
||||
$fetch_refs[] = $ref;
|
||||
}
|
||||
|
||||
id(new PhabricatorFaviconRefQuery())
|
||||
->withRefs($fetch_refs)
|
||||
->execute();
|
||||
|
||||
$favicon_links = array();
|
||||
foreach ($favicon_refs as $spec) {
|
||||
$favicon_links[] = phutil_tag(
|
||||
'link',
|
||||
array(
|
||||
'rel' => $spec['rel'],
|
||||
'sizes' => idx($spec, 'sizes'),
|
||||
'id' => idx($spec, 'id'),
|
||||
'href' => $spec['ref']->getURI(),
|
||||
));
|
||||
}
|
||||
|
||||
return $favicon_links;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -330,9 +330,6 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView
|
|||
Javelin::initBehavior(
|
||||
'dark-console',
|
||||
$this->getConsoleConfig());
|
||||
|
||||
// Change this to initBehavior when there is some behavior to initialize
|
||||
require_celerity_resource('javelin-behavior-error-log');
|
||||
}
|
||||
|
||||
if ($user) {
|
||||
|
|
|
@ -23,15 +23,29 @@ final class PhabricatorMainMenuView extends AphrontView {
|
|||
return $this->controller;
|
||||
}
|
||||
|
||||
private function getFaviconURI($type = null) {
|
||||
switch ($type) {
|
||||
case 'message':
|
||||
return celerity_get_resource_uri('/rsrc/favicons/favicon-message.ico');
|
||||
case 'mention':
|
||||
return celerity_get_resource_uri('/rsrc/favicons/favicon-mention.ico');
|
||||
default:
|
||||
return celerity_get_resource_uri('/rsrc/favicons/favicon.ico');
|
||||
}
|
||||
private static function getFavicons() {
|
||||
$refs = array();
|
||||
|
||||
$refs['favicon'] = id(new PhabricatorFaviconRef())
|
||||
->setWidth(64)
|
||||
->setHeight(64);
|
||||
|
||||
$refs['message_favicon'] = id(new PhabricatorFaviconRef())
|
||||
->setWidth(64)
|
||||
->setHeight(64)
|
||||
->setEmblems(
|
||||
array(
|
||||
'dot-pink',
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
));
|
||||
|
||||
id(new PhabricatorFaviconRefQuery())
|
||||
->withRefs($refs)
|
||||
->execute();
|
||||
|
||||
return mpull($refs, 'getURI');
|
||||
}
|
||||
|
||||
public function render() {
|
||||
|
@ -428,10 +442,7 @@ final class PhabricatorMainMenuView extends AphrontView {
|
|||
'countType' => $conpherence_data['countType'],
|
||||
'countNumber' => $message_count_number,
|
||||
'unreadClass' => 'message-unread',
|
||||
'favicon' => $this->getFaviconURI('default'),
|
||||
'message_favicon' => $this->getFaviconURI('message'),
|
||||
'mention_favicon' => $this->getFaviconURI('mention'),
|
||||
));
|
||||
) + self::getFavicons());
|
||||
|
||||
$message_notification_dropdown = javelin_tag(
|
||||
'div',
|
||||
|
@ -509,10 +520,7 @@ final class PhabricatorMainMenuView extends AphrontView {
|
|||
'countType' => $notification_data['countType'],
|
||||
'countNumber' => $count_number,
|
||||
'unreadClass' => 'alert-unread',
|
||||
'favicon' => $this->getFaviconURI('default'),
|
||||
'message_favicon' => $this->getFaviconURI('message'),
|
||||
'mention_favicon' => $this->getFaviconURI('mention'),
|
||||
));
|
||||
) + self::getFavicons());
|
||||
|
||||
$notification_dropdown = javelin_tag(
|
||||
'div',
|
||||
|
@ -594,10 +602,7 @@ final class PhabricatorMainMenuView extends AphrontView {
|
|||
'countType' => null,
|
||||
'countNumber' => null,
|
||||
'unreadClass' => 'setup-unread',
|
||||
'favicon' => $this->getFaviconURI('default'),
|
||||
'message_favicon' => $this->getFaviconURI('message'),
|
||||
'mention_favicon' => $this->getFaviconURI('mention'),
|
||||
));
|
||||
) + self::getFavicons());
|
||||
|
||||
$setup_notification_dropdown = javelin_tag(
|
||||
'div',
|
||||
|
|
|
@ -117,8 +117,8 @@ final class PHUITagView extends AphrontTagView {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setSlimShady($mm) {
|
||||
$this->slimShady = $mm;
|
||||
public function setSlimShady($is_eminem) {
|
||||
$this->slimShady = $is_eminem;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
Before Width: | Height: | Size: 34 KiB |
|
@ -104,6 +104,7 @@
|
|||
font-size: 11px;
|
||||
background: #333333;
|
||||
color: #ffffff;
|
||||
border-bottom: 0px;
|
||||
}
|
||||
|
||||
.dark-console .aphront-table-view td {
|
||||
|
@ -210,10 +211,10 @@
|
|||
}
|
||||
|
||||
.dark-console-panel-request-log-separator {
|
||||
background-color: #e8e8e8;
|
||||
border-bottom: 1px solid #b7b7b7;
|
||||
border-top: 1px solid #b7b7b7;
|
||||
height: 2px;
|
||||
background-color: #e8e8e8;
|
||||
border-bottom: 1px solid #b7b7b7;
|
||||
border-top: 1px solid #b7b7b7;
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
.dark-console-panel-ErrorLog {
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
}
|
||||
|
||||
.setup-issue-body {
|
||||
padding: 16px 16px 0 16px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.setup-issue-status {
|
||||
|
|
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 87 KiB |
Before Width: | Height: | Size: 175 KiB |
Before Width: | Height: | Size: 6.4 KiB |
|
@ -78,7 +78,7 @@ JX.behavior('aphlict-listen', function(config) {
|
|||
JX.Stratcom.invoke('notification-panel-update', null, {});
|
||||
var response = e.getData();
|
||||
|
||||
if (!response.showAnyNotification) {
|
||||
if (!response.showAnyNotification && !response.showDesktopNotification) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -86,6 +86,7 @@ JX.behavior('aphlict-listen', function(config) {
|
|||
new JX.Notification()
|
||||
.setContent(JX.$H(response.content))
|
||||
.setKey(response.primaryObjectPHID)
|
||||
.setShowAsWebNotification(response.showAnyNotification)
|
||||
.setShowAsDesktopNotification(response.showDesktopNotification)
|
||||
.setTitle(response.title)
|
||||
.setBody(response.body)
|
||||
|
|
|
@ -40,9 +40,19 @@ JX.behavior('doorkeeper-tag', function(config, statics) {
|
|||
};
|
||||
|
||||
for (var ii = 0; ii < tags.length; ii++) {
|
||||
var tag_key = tags[ii].ref.join('@');
|
||||
var key_parts = [];
|
||||
|
||||
key_parts = key_parts.concat(tags[ii].ref);
|
||||
key_parts.push(tags[ii].view);
|
||||
|
||||
var tag_key = key_parts.join(' ');
|
||||
|
||||
if (tag_key in statics.cache) {
|
||||
have.push({id: tags[ii].id, markup: statics.cache[tag_key]});
|
||||
have.push(
|
||||
{
|
||||
id: tags[ii].id,
|
||||
markup: statics.cache[tag_key]
|
||||
});
|
||||
} else {
|
||||
need.push(tags[ii]);
|
||||
keys[tags[ii].id] = tag_key;
|
||||
|
@ -54,7 +64,11 @@ JX.behavior('doorkeeper-tag', function(config, statics) {
|
|||
}
|
||||
|
||||
if (need.length) {
|
||||
new JX.Workflow('/doorkeeper/tags/', {tags: JX.JSON.stringify(need)})
|
||||
var data = {
|
||||
tags: JX.JSON.stringify(need)
|
||||
};
|
||||
|
||||
new JX.Workflow('/doorkeeper/tags/', data)
|
||||
.setHandler(function(r) { draw(r.tags); })
|
||||
.start();
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ JX.install('Notification', {
|
|||
_hideTimer : null,
|
||||
_duration : 12000,
|
||||
_asDesktop : false,
|
||||
_asWeb : true,
|
||||
_key : null,
|
||||
_title : null,
|
||||
_body : null,
|
||||
|
@ -88,6 +89,11 @@ JX.install('Notification', {
|
|||
return this;
|
||||
},
|
||||
|
||||
setShowAsWebNotification: function(mode) {
|
||||
this._asWeb = mode;
|
||||
return this;
|
||||
},
|
||||
|
||||
setShowAsDesktopNotification : function(mode) {
|
||||
this._asDesktop = mode;
|
||||
return this;
|
||||
|
@ -242,6 +248,13 @@ JX.install('Notification', {
|
|||
|
||||
var notifications = [];
|
||||
for (var ii = 0; ii < self._active.length; ii++) {
|
||||
|
||||
// Don't render this notification if it's not configured to show as
|
||||
// a web notification.
|
||||
if (!self._active[ii]._asWeb) {
|
||||
continue;
|
||||
}
|
||||
|
||||
notifications.push(self._active[ii]._getContainer());
|
||||
if (!(--limit)) {
|
||||
break;
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
/**
|
||||
* @provides javelin-behavior-error-log
|
||||
* @requires javelin-dom
|
||||
*/
|
||||
|
||||
/* exported show_details */
|
||||
|
||||
var current_details = null;
|
||||
|
||||
function show_details(row) {
|
||||
var node = JX.$('row-details-' + row);
|
||||
|
||||
if (current_details !== null) {
|
||||
JX.$('row-details-' + current_details).style.display = 'none';
|
||||
}
|
||||
|
||||
node.style.display = 'block';
|
||||
current_details = row;
|
||||
}
|
|
@ -404,4 +404,24 @@ JX.behavior('dark-console', function(config, statics) {
|
|||
|
||||
}
|
||||
|
||||
if (!statics.expand) {
|
||||
statics.expand = true;
|
||||
|
||||
var current_details = null;
|
||||
JX.Stratcom.listen('click', 'darkconsole-expand', function(e) {
|
||||
e.kill();
|
||||
|
||||
if (current_details) {
|
||||
current_details.style.display = 'none';
|
||||
current_details = null;
|
||||
}
|
||||
|
||||
var id = e.getNodeData('darkconsole-expand').expandID;
|
||||
var node = JX.$(id);
|
||||
|
||||
node.style.display = 'block';
|
||||
current_details = node;
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
|