mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-26 08:42:41 +01:00
Merge branch 'master' into phutil_tag
(Sync.)
This commit is contained in:
commit
11bb8db970
80 changed files with 2727 additions and 1188 deletions
|
@ -1015,17 +1015,6 @@ return array(
|
|||
// interact with the revisions.
|
||||
'differential.anonymous-access' => false,
|
||||
|
||||
// If you set this to true, revision author email address information will
|
||||
// be exposed in Conduit. This is useful for Arcanist.
|
||||
//
|
||||
// For example, consider the "arc patch DX" workflow which needs to ask
|
||||
// Differential for the revision DX. This data often should contain
|
||||
// the author's email address, eg "George Washington
|
||||
// <gwashinton@example.com>" when DX is a git or mercurial revision. If this
|
||||
// option is false, Differential defaults to the best it can, something like
|
||||
// "George Washington" or "gwashington".
|
||||
'differential.expose-emails-prudently' => false,
|
||||
|
||||
// List of file regexps that should be treated as if they are generated by
|
||||
// an automatic process, and thus get hidden by default in differential.
|
||||
'differential.generated-paths' => array(
|
||||
|
|
|
@ -7,12 +7,12 @@
|
|||
],
|
||||
"handlers" : [
|
||||
"PhabricatorIRCProtocolHandler",
|
||||
"PhabricatorIRCObjectNameHandler",
|
||||
"PhabricatorIRCSymbolHandler",
|
||||
"PhabricatorIRCLogHandler",
|
||||
"PhabricatorIRCWhatsNewHandler",
|
||||
"PhabricatorIRCDifferentialNotificationHandler",
|
||||
"PhabricatorIRCMacroHandler"
|
||||
"PhabricatorBotObjectNameHandler",
|
||||
"PhabricatorBotSymbolHandler",
|
||||
"PhabricatorBotLogHandler",
|
||||
"PhabricatorBotWhatsNewHandler",
|
||||
"PhabricatorBotDifferentialNotificationHandler",
|
||||
"PhabricatorBotMacroHandler"
|
||||
],
|
||||
|
||||
"conduit.uri" : null,
|
||||
|
|
6
resources/sql/patches/20130131.conpherencepics.sql
Normal file
6
resources/sql/patches/20130131.conpherencepics.sql
Normal file
|
@ -0,0 +1,6 @@
|
|||
ALTER TABLE {$NAMESPACE}_conpherence.conpherence_thread
|
||||
DROP imagePHID,
|
||||
ADD imagePHIDs LONGTEXT COLLATE utf8_bin NOT NULL AFTER title;
|
||||
|
||||
UPDATE {$NAMESPACE}_conpherence.conpherence_thread
|
||||
SET imagePHIDs = '{}' WHERE imagePHIDs = '';
|
|
@ -646,7 +646,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'aphront-form-view-css' =>
|
||||
array(
|
||||
'uri' => '/res/1e191b83/rsrc/css/aphront/form-view.css',
|
||||
'uri' => '/res/ec323d34/rsrc/css/aphront/form-view.css',
|
||||
'type' => 'css',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -737,7 +737,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'conpherence-header-pane-css' =>
|
||||
array(
|
||||
'uri' => '/res/4b8aebd2/rsrc/css/application/conpherence/header-pane.css',
|
||||
'uri' => '/res/11c32adc/rsrc/css/application/conpherence/header-pane.css',
|
||||
'type' => 'css',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -764,7 +764,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'conpherence-update-css' =>
|
||||
array(
|
||||
'uri' => '/res/8e4757b5/rsrc/css/application/conpherence/update.css',
|
||||
'uri' => '/res/92094ed7/rsrc/css/application/conpherence/update.css',
|
||||
'type' => 'css',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -773,7 +773,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'conpherence-widget-pane-css' =>
|
||||
array(
|
||||
'uri' => '/res/b3e6a558/rsrc/css/application/conpherence/widget-pane.css',
|
||||
'uri' => '/res/6e5755bb/rsrc/css/application/conpherence/widget-pane.css',
|
||||
'type' => 'css',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -1031,6 +1031,19 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'disk' => '/rsrc/js/application/core/behavior-tokenizer.js',
|
||||
),
|
||||
'javelin-behavior-aphront-crop' =>
|
||||
array(
|
||||
'uri' => '/res/cda1eace/rsrc/js/application/core/behavior-crop.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
0 => 'javelin-behavior',
|
||||
1 => 'javelin-dom',
|
||||
2 => 'javelin-vector',
|
||||
3 => 'javelin-magical-init',
|
||||
),
|
||||
'disk' => '/rsrc/js/application/core/behavior-crop.js',
|
||||
),
|
||||
'javelin-behavior-aphront-drag-and-drop' =>
|
||||
array(
|
||||
'uri' => '/res/3d809b40/rsrc/js/application/core/behavior-drag-and-drop.js',
|
||||
|
@ -1095,6 +1108,19 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'disk' => '/rsrc/js/application/diffusion/behavior-audit-preview.js',
|
||||
),
|
||||
'javelin-behavior-conpherence-drag-and-drop-photo' =>
|
||||
array(
|
||||
'uri' => '/res/9e3eb1cd/rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
0 => 'javelin-behavior',
|
||||
1 => 'javelin-dom',
|
||||
2 => 'javelin-workflow',
|
||||
3 => 'phabricator-drag-and-drop-file-upload',
|
||||
),
|
||||
'disk' => '/rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js',
|
||||
),
|
||||
'javelin-behavior-conpherence-init' =>
|
||||
array(
|
||||
'uri' => '/res/bd911b43/rsrc/js/application/conpherence/behavior-init.js',
|
||||
|
@ -1109,7 +1135,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'javelin-behavior-conpherence-menu' =>
|
||||
array(
|
||||
'uri' => '/res/9d21fb86/rsrc/js/application/conpherence/behavior-menu.js',
|
||||
'uri' => '/res/986774a0/rsrc/js/application/conpherence/behavior-menu.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -1124,7 +1150,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'javelin-behavior-conpherence-widget-pane' =>
|
||||
array(
|
||||
'uri' => '/res/f3e0dbba/rsrc/js/application/conpherence/behavior-widget-pane.js',
|
||||
'uri' => '/res/4fb51b46/rsrc/js/application/conpherence/behavior-widget-pane.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -1148,7 +1174,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'javelin-behavior-dark-console' =>
|
||||
array(
|
||||
'uri' => '/res/52444d4e/rsrc/js/application/core/behavior-dark-console.js',
|
||||
'uri' => '/res/ae7f15ce/rsrc/js/application/core/behavior-dark-console.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -1269,7 +1295,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'javelin-behavior-differential-keyboard-navigation' =>
|
||||
array(
|
||||
'uri' => '/res/a7798465/rsrc/js/application/differential/behavior-keyboard-nav.js',
|
||||
'uri' => '/res/89e93cc9/rsrc/js/application/differential/behavior-keyboard-nav.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -1282,7 +1308,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'javelin-behavior-differential-populate' =>
|
||||
array(
|
||||
'uri' => '/res/71effec4/rsrc/js/application/differential/behavior-populate.js',
|
||||
'uri' => '/res/526c2615/rsrc/js/application/differential/behavior-populate.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -1292,7 +1318,8 @@ celerity_register_resource_map(array(
|
|||
3 => 'javelin-dom',
|
||||
4 => 'javelin-stratcom',
|
||||
5 => 'javelin-behavior-device',
|
||||
6 => 'phabricator-tooltip',
|
||||
6 => 'javelin-vector',
|
||||
7 => 'phabricator-tooltip',
|
||||
),
|
||||
'disk' => '/rsrc/js/application/differential/behavior-populate.js',
|
||||
),
|
||||
|
@ -1640,9 +1667,21 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'disk' => '/rsrc/js/application/core/behavior-autofocus.js',
|
||||
),
|
||||
'javelin-behavior-phabricator-file-tree' =>
|
||||
array(
|
||||
'uri' => '/res/e9c96597/rsrc/js/application/core/behavior-file-tree.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
0 => 'javelin-behavior',
|
||||
1 => 'phabricator-keyboard-shortcut',
|
||||
2 => 'javelin-stratcom',
|
||||
),
|
||||
'disk' => '/rsrc/js/application/core/behavior-file-tree.js',
|
||||
),
|
||||
'javelin-behavior-phabricator-home-reveal-tiles' =>
|
||||
array(
|
||||
'uri' => '/res/7230ca0c/rsrc/js/application/core/behavior-home-reveal-tiles.js',
|
||||
'uri' => '/res/b3c5cea9/rsrc/js/application/core/behavior-home-reveal-tiles.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -1680,7 +1719,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'javelin-behavior-phabricator-nav' =>
|
||||
array(
|
||||
'uri' => '/res/cec31f3f/rsrc/js/application/core/behavior-phabricator-nav.js',
|
||||
'uri' => '/res/222b9329/rsrc/js/application/core/behavior-phabricator-nav.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -1827,12 +1866,16 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'javelin-behavior-pholio-mock-view' =>
|
||||
array(
|
||||
'uri' => '/res/10fbdca1/rsrc/js/application/pholio/behavior-pholio-mock-view.js',
|
||||
'uri' => '/res/518a169e/rsrc/js/application/pholio/behavior-pholio-mock-view.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
0 => 'javelin-behavior',
|
||||
1 => 'javelin-stratcom',
|
||||
2 => 'javelin-dom',
|
||||
3 => 'javelin-vector',
|
||||
4 => 'javelin-magical-init',
|
||||
5 => 'javelin-request',
|
||||
),
|
||||
'disk' => '/rsrc/js/application/pholio/behavior-pholio-mock-view.js',
|
||||
),
|
||||
|
@ -2015,7 +2058,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'javelin-event' =>
|
||||
array(
|
||||
'uri' => '/res/3815b473/rsrc/js/javelin/core/Event.js',
|
||||
'uri' => '/res/e6582051/rsrc/js/javelin/core/Event.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -2671,7 +2714,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'phabricator-header-view-css' =>
|
||||
array(
|
||||
'uri' => '/res/88ef478c/rsrc/css/layout/phabricator-header-view.css',
|
||||
'uri' => '/res/585b771c/rsrc/css/layout/phabricator-header-view.css',
|
||||
'type' => 'css',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -3167,7 +3210,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'pholio-css' =>
|
||||
array(
|
||||
'uri' => '/res/f101ad7c/rsrc/css/application/pholio/pholio.css',
|
||||
'uri' => '/res/9de6e0b2/rsrc/css/application/pholio/pholio.css',
|
||||
'type' => 'css',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -3293,7 +3336,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'sprite-conpher-css' =>
|
||||
array(
|
||||
'uri' => '/res/f640f0c5/rsrc/css/sprite-conph.css',
|
||||
'uri' => '/res/89821322/rsrc/css/sprite-conph.css',
|
||||
'type' => 'css',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -3357,7 +3400,7 @@ celerity_register_resource_map(array(
|
|||
), array(
|
||||
'packages' =>
|
||||
array(
|
||||
'0e6165d0' =>
|
||||
'4b569463' =>
|
||||
array(
|
||||
'name' => 'core.pkg.css',
|
||||
'symbols' =>
|
||||
|
@ -3400,10 +3443,10 @@ celerity_register_resource_map(array(
|
|||
35 => 'phabricator-object-item-list-view-css',
|
||||
36 => 'global-drag-and-drop-css',
|
||||
),
|
||||
'uri' => '/res/pkg/0e6165d0/core.pkg.css',
|
||||
'uri' => '/res/pkg/4b569463/core.pkg.css',
|
||||
'type' => 'css',
|
||||
),
|
||||
'ff199687' =>
|
||||
'bc0774e5' =>
|
||||
array(
|
||||
'name' => 'core.pkg.js',
|
||||
'symbols' =>
|
||||
|
@ -3442,10 +3485,10 @@ celerity_register_resource_map(array(
|
|||
31 => 'javelin-behavior-global-drag-and-drop',
|
||||
32 => 'javelin-behavior-phabricator-home-reveal-tiles',
|
||||
),
|
||||
'uri' => '/res/pkg/ff199687/core.pkg.js',
|
||||
'uri' => '/res/pkg/bc0774e5/core.pkg.js',
|
||||
'type' => 'js',
|
||||
),
|
||||
'74593df4' =>
|
||||
'3e0098ea' =>
|
||||
array(
|
||||
'name' => 'darkconsole.pkg.js',
|
||||
'symbols' =>
|
||||
|
@ -3453,7 +3496,7 @@ celerity_register_resource_map(array(
|
|||
0 => 'javelin-behavior-dark-console',
|
||||
1 => 'javelin-behavior-error-log',
|
||||
),
|
||||
'uri' => '/res/pkg/74593df4/darkconsole.pkg.js',
|
||||
'uri' => '/res/pkg/3e0098ea/darkconsole.pkg.js',
|
||||
'type' => 'js',
|
||||
),
|
||||
'8aaacd1b' =>
|
||||
|
@ -3478,7 +3521,7 @@ celerity_register_resource_map(array(
|
|||
'uri' => '/res/pkg/8aaacd1b/differential.pkg.css',
|
||||
'type' => 'css',
|
||||
),
|
||||
'9dae5f20' =>
|
||||
'95d0d865' =>
|
||||
array(
|
||||
'name' => 'differential.pkg.js',
|
||||
'symbols' =>
|
||||
|
@ -3503,7 +3546,7 @@ celerity_register_resource_map(array(
|
|||
17 => 'javelin-behavior-differential-toggle-files',
|
||||
18 => 'javelin-behavior-differential-user-select',
|
||||
),
|
||||
'uri' => '/res/pkg/9dae5f20/differential.pkg.js',
|
||||
'uri' => '/res/pkg/95d0d865/differential.pkg.js',
|
||||
'type' => 'js',
|
||||
),
|
||||
'c8ce2d88' =>
|
||||
|
@ -3529,7 +3572,7 @@ celerity_register_resource_map(array(
|
|||
'uri' => '/res/pkg/f96657b8/diffusion.pkg.js',
|
||||
'type' => 'js',
|
||||
),
|
||||
'1c6f020b' =>
|
||||
'd466c034' =>
|
||||
array(
|
||||
'name' => 'javelin.pkg.js',
|
||||
'symbols' =>
|
||||
|
@ -3554,7 +3597,7 @@ celerity_register_resource_map(array(
|
|||
17 => 'javelin-typeahead-ondemand-source',
|
||||
18 => 'javelin-tokenizer',
|
||||
),
|
||||
'uri' => '/res/pkg/1c6f020b/javelin.pkg.js',
|
||||
'uri' => '/res/pkg/d466c034/javelin.pkg.js',
|
||||
'type' => 'js',
|
||||
),
|
||||
'e30a3fa8' =>
|
||||
|
@ -3588,20 +3631,20 @@ celerity_register_resource_map(array(
|
|||
'reverse' =>
|
||||
array(
|
||||
'aphront-attached-file-view-css' => 'e30a3fa8',
|
||||
'aphront-crumbs-view-css' => '0e6165d0',
|
||||
'aphront-dialog-view-css' => '0e6165d0',
|
||||
'aphront-error-view-css' => '0e6165d0',
|
||||
'aphront-form-view-css' => '0e6165d0',
|
||||
'aphront-list-filter-view-css' => '0e6165d0',
|
||||
'aphront-pager-view-css' => '0e6165d0',
|
||||
'aphront-panel-view-css' => '0e6165d0',
|
||||
'aphront-table-view-css' => '0e6165d0',
|
||||
'aphront-tokenizer-control-css' => '0e6165d0',
|
||||
'aphront-tooltip-css' => '0e6165d0',
|
||||
'aphront-typeahead-control-css' => '0e6165d0',
|
||||
'aphront-crumbs-view-css' => '4b569463',
|
||||
'aphront-dialog-view-css' => '4b569463',
|
||||
'aphront-error-view-css' => '4b569463',
|
||||
'aphront-form-view-css' => '4b569463',
|
||||
'aphront-list-filter-view-css' => '4b569463',
|
||||
'aphront-pager-view-css' => '4b569463',
|
||||
'aphront-panel-view-css' => '4b569463',
|
||||
'aphront-table-view-css' => '4b569463',
|
||||
'aphront-tokenizer-control-css' => '4b569463',
|
||||
'aphront-tooltip-css' => '4b569463',
|
||||
'aphront-typeahead-control-css' => '4b569463',
|
||||
'differential-changeset-view-css' => '8aaacd1b',
|
||||
'differential-core-view-css' => '8aaacd1b',
|
||||
'differential-inline-comment-editor' => '9dae5f20',
|
||||
'differential-inline-comment-editor' => '95d0d865',
|
||||
'differential-local-commits-view-css' => '8aaacd1b',
|
||||
'differential-results-table-css' => '8aaacd1b',
|
||||
'differential-revision-add-comment-css' => '8aaacd1b',
|
||||
|
@ -3612,117 +3655,117 @@ celerity_register_resource_map(array(
|
|||
'differential-table-of-contents-css' => '8aaacd1b',
|
||||
'diffusion-commit-view-css' => 'c8ce2d88',
|
||||
'diffusion-icons-css' => 'c8ce2d88',
|
||||
'global-drag-and-drop-css' => '0e6165d0',
|
||||
'global-drag-and-drop-css' => '4b569463',
|
||||
'inline-comment-summary-css' => '8aaacd1b',
|
||||
'javelin-aphlict' => 'ff199687',
|
||||
'javelin-behavior' => '1c6f020b',
|
||||
'javelin-behavior-aphlict-dropdown' => 'ff199687',
|
||||
'javelin-behavior-aphlict-listen' => 'ff199687',
|
||||
'javelin-behavior-aphront-basic-tokenizer' => 'ff199687',
|
||||
'javelin-behavior-aphront-drag-and-drop' => '9dae5f20',
|
||||
'javelin-behavior-aphront-drag-and-drop-textarea' => '9dae5f20',
|
||||
'javelin-behavior-aphront-form-disable-on-submit' => 'ff199687',
|
||||
'javelin-aphlict' => 'bc0774e5',
|
||||
'javelin-behavior' => 'd466c034',
|
||||
'javelin-behavior-aphlict-dropdown' => 'bc0774e5',
|
||||
'javelin-behavior-aphlict-listen' => 'bc0774e5',
|
||||
'javelin-behavior-aphront-basic-tokenizer' => 'bc0774e5',
|
||||
'javelin-behavior-aphront-drag-and-drop' => '95d0d865',
|
||||
'javelin-behavior-aphront-drag-and-drop-textarea' => '95d0d865',
|
||||
'javelin-behavior-aphront-form-disable-on-submit' => 'bc0774e5',
|
||||
'javelin-behavior-audit-preview' => 'f96657b8',
|
||||
'javelin-behavior-dark-console' => '74593df4',
|
||||
'javelin-behavior-device' => 'ff199687',
|
||||
'javelin-behavior-differential-accept-with-errors' => '9dae5f20',
|
||||
'javelin-behavior-differential-add-reviewers-and-ccs' => '9dae5f20',
|
||||
'javelin-behavior-differential-comment-jump' => '9dae5f20',
|
||||
'javelin-behavior-differential-diff-radios' => '9dae5f20',
|
||||
'javelin-behavior-differential-dropdown-menus' => '9dae5f20',
|
||||
'javelin-behavior-differential-edit-inline-comments' => '9dae5f20',
|
||||
'javelin-behavior-differential-feedback-preview' => '9dae5f20',
|
||||
'javelin-behavior-differential-keyboard-navigation' => '9dae5f20',
|
||||
'javelin-behavior-differential-populate' => '9dae5f20',
|
||||
'javelin-behavior-differential-show-more' => '9dae5f20',
|
||||
'javelin-behavior-differential-toggle-files' => '9dae5f20',
|
||||
'javelin-behavior-differential-user-select' => '9dae5f20',
|
||||
'javelin-behavior-dark-console' => '3e0098ea',
|
||||
'javelin-behavior-device' => 'bc0774e5',
|
||||
'javelin-behavior-differential-accept-with-errors' => '95d0d865',
|
||||
'javelin-behavior-differential-add-reviewers-and-ccs' => '95d0d865',
|
||||
'javelin-behavior-differential-comment-jump' => '95d0d865',
|
||||
'javelin-behavior-differential-diff-radios' => '95d0d865',
|
||||
'javelin-behavior-differential-dropdown-menus' => '95d0d865',
|
||||
'javelin-behavior-differential-edit-inline-comments' => '95d0d865',
|
||||
'javelin-behavior-differential-feedback-preview' => '95d0d865',
|
||||
'javelin-behavior-differential-keyboard-navigation' => '95d0d865',
|
||||
'javelin-behavior-differential-populate' => '95d0d865',
|
||||
'javelin-behavior-differential-show-more' => '95d0d865',
|
||||
'javelin-behavior-differential-toggle-files' => '95d0d865',
|
||||
'javelin-behavior-differential-user-select' => '95d0d865',
|
||||
'javelin-behavior-diffusion-commit-graph' => 'f96657b8',
|
||||
'javelin-behavior-diffusion-pull-lastmodified' => 'f96657b8',
|
||||
'javelin-behavior-error-log' => '74593df4',
|
||||
'javelin-behavior-global-drag-and-drop' => 'ff199687',
|
||||
'javelin-behavior-konami' => 'ff199687',
|
||||
'javelin-behavior-lightbox-attachments' => 'ff199687',
|
||||
'javelin-behavior-error-log' => '3e0098ea',
|
||||
'javelin-behavior-global-drag-and-drop' => 'bc0774e5',
|
||||
'javelin-behavior-konami' => 'bc0774e5',
|
||||
'javelin-behavior-lightbox-attachments' => 'bc0774e5',
|
||||
'javelin-behavior-maniphest-batch-selector' => '7707de41',
|
||||
'javelin-behavior-maniphest-subpriority-editor' => '7707de41',
|
||||
'javelin-behavior-maniphest-transaction-controls' => '7707de41',
|
||||
'javelin-behavior-maniphest-transaction-expand' => '7707de41',
|
||||
'javelin-behavior-maniphest-transaction-preview' => '7707de41',
|
||||
'javelin-behavior-phabricator-active-nav' => 'ff199687',
|
||||
'javelin-behavior-phabricator-autofocus' => 'ff199687',
|
||||
'javelin-behavior-phabricator-home-reveal-tiles' => 'ff199687',
|
||||
'javelin-behavior-phabricator-keyboard-shortcuts' => 'ff199687',
|
||||
'javelin-behavior-phabricator-nav' => 'ff199687',
|
||||
'javelin-behavior-phabricator-object-selector' => '9dae5f20',
|
||||
'javelin-behavior-phabricator-oncopy' => 'ff199687',
|
||||
'javelin-behavior-phabricator-remarkup-assist' => 'ff199687',
|
||||
'javelin-behavior-phabricator-search-typeahead' => 'ff199687',
|
||||
'javelin-behavior-phabricator-tooltips' => 'ff199687',
|
||||
'javelin-behavior-phabricator-watch-anchor' => 'ff199687',
|
||||
'javelin-behavior-refresh-csrf' => 'ff199687',
|
||||
'javelin-behavior-repository-crossreference' => '9dae5f20',
|
||||
'javelin-behavior-toggle-class' => 'ff199687',
|
||||
'javelin-behavior-workflow' => 'ff199687',
|
||||
'javelin-dom' => '1c6f020b',
|
||||
'javelin-event' => '1c6f020b',
|
||||
'javelin-install' => '1c6f020b',
|
||||
'javelin-json' => '1c6f020b',
|
||||
'javelin-mask' => '1c6f020b',
|
||||
'javelin-request' => '1c6f020b',
|
||||
'javelin-resource' => '1c6f020b',
|
||||
'javelin-stratcom' => '1c6f020b',
|
||||
'javelin-tokenizer' => '1c6f020b',
|
||||
'javelin-typeahead' => '1c6f020b',
|
||||
'javelin-typeahead-normalizer' => '1c6f020b',
|
||||
'javelin-typeahead-ondemand-source' => '1c6f020b',
|
||||
'javelin-typeahead-preloaded-source' => '1c6f020b',
|
||||
'javelin-typeahead-source' => '1c6f020b',
|
||||
'javelin-uri' => '1c6f020b',
|
||||
'javelin-util' => '1c6f020b',
|
||||
'javelin-vector' => '1c6f020b',
|
||||
'javelin-workflow' => '1c6f020b',
|
||||
'lightbox-attachment-css' => '0e6165d0',
|
||||
'javelin-behavior-phabricator-active-nav' => 'bc0774e5',
|
||||
'javelin-behavior-phabricator-autofocus' => 'bc0774e5',
|
||||
'javelin-behavior-phabricator-home-reveal-tiles' => 'bc0774e5',
|
||||
'javelin-behavior-phabricator-keyboard-shortcuts' => 'bc0774e5',
|
||||
'javelin-behavior-phabricator-nav' => 'bc0774e5',
|
||||
'javelin-behavior-phabricator-object-selector' => '95d0d865',
|
||||
'javelin-behavior-phabricator-oncopy' => 'bc0774e5',
|
||||
'javelin-behavior-phabricator-remarkup-assist' => 'bc0774e5',
|
||||
'javelin-behavior-phabricator-search-typeahead' => 'bc0774e5',
|
||||
'javelin-behavior-phabricator-tooltips' => 'bc0774e5',
|
||||
'javelin-behavior-phabricator-watch-anchor' => 'bc0774e5',
|
||||
'javelin-behavior-refresh-csrf' => 'bc0774e5',
|
||||
'javelin-behavior-repository-crossreference' => '95d0d865',
|
||||
'javelin-behavior-toggle-class' => 'bc0774e5',
|
||||
'javelin-behavior-workflow' => 'bc0774e5',
|
||||
'javelin-dom' => 'd466c034',
|
||||
'javelin-event' => 'd466c034',
|
||||
'javelin-install' => 'd466c034',
|
||||
'javelin-json' => 'd466c034',
|
||||
'javelin-mask' => 'd466c034',
|
||||
'javelin-request' => 'd466c034',
|
||||
'javelin-resource' => 'd466c034',
|
||||
'javelin-stratcom' => 'd466c034',
|
||||
'javelin-tokenizer' => 'd466c034',
|
||||
'javelin-typeahead' => 'd466c034',
|
||||
'javelin-typeahead-normalizer' => 'd466c034',
|
||||
'javelin-typeahead-ondemand-source' => 'd466c034',
|
||||
'javelin-typeahead-preloaded-source' => 'd466c034',
|
||||
'javelin-typeahead-source' => 'd466c034',
|
||||
'javelin-uri' => 'd466c034',
|
||||
'javelin-util' => 'd466c034',
|
||||
'javelin-vector' => 'd466c034',
|
||||
'javelin-workflow' => 'd466c034',
|
||||
'lightbox-attachment-css' => '4b569463',
|
||||
'maniphest-task-summary-css' => 'e30a3fa8',
|
||||
'maniphest-transaction-detail-css' => 'e30a3fa8',
|
||||
'phabricator-busy' => 'ff199687',
|
||||
'phabricator-busy' => 'bc0774e5',
|
||||
'phabricator-content-source-view-css' => '8aaacd1b',
|
||||
'phabricator-core-buttons-css' => '0e6165d0',
|
||||
'phabricator-core-css' => '0e6165d0',
|
||||
'phabricator-crumbs-view-css' => '0e6165d0',
|
||||
'phabricator-directory-css' => '0e6165d0',
|
||||
'phabricator-drag-and-drop-file-upload' => '9dae5f20',
|
||||
'phabricator-dropdown-menu' => 'ff199687',
|
||||
'phabricator-file-upload' => 'ff199687',
|
||||
'phabricator-filetree-view-css' => '0e6165d0',
|
||||
'phabricator-flag-css' => '0e6165d0',
|
||||
'phabricator-form-view-css' => '0e6165d0',
|
||||
'phabricator-header-view-css' => '0e6165d0',
|
||||
'phabricator-jump-nav' => '0e6165d0',
|
||||
'phabricator-keyboard-shortcut' => 'ff199687',
|
||||
'phabricator-keyboard-shortcut-manager' => 'ff199687',
|
||||
'phabricator-main-menu-view' => '0e6165d0',
|
||||
'phabricator-menu-item' => 'ff199687',
|
||||
'phabricator-nav-view-css' => '0e6165d0',
|
||||
'phabricator-notification' => 'ff199687',
|
||||
'phabricator-notification-css' => '0e6165d0',
|
||||
'phabricator-notification-menu-css' => '0e6165d0',
|
||||
'phabricator-object-item-list-view-css' => '0e6165d0',
|
||||
'phabricator-core-buttons-css' => '4b569463',
|
||||
'phabricator-core-css' => '4b569463',
|
||||
'phabricator-crumbs-view-css' => '4b569463',
|
||||
'phabricator-directory-css' => '4b569463',
|
||||
'phabricator-drag-and-drop-file-upload' => '95d0d865',
|
||||
'phabricator-dropdown-menu' => 'bc0774e5',
|
||||
'phabricator-file-upload' => 'bc0774e5',
|
||||
'phabricator-filetree-view-css' => '4b569463',
|
||||
'phabricator-flag-css' => '4b569463',
|
||||
'phabricator-form-view-css' => '4b569463',
|
||||
'phabricator-header-view-css' => '4b569463',
|
||||
'phabricator-jump-nav' => '4b569463',
|
||||
'phabricator-keyboard-shortcut' => 'bc0774e5',
|
||||
'phabricator-keyboard-shortcut-manager' => 'bc0774e5',
|
||||
'phabricator-main-menu-view' => '4b569463',
|
||||
'phabricator-menu-item' => 'bc0774e5',
|
||||
'phabricator-nav-view-css' => '4b569463',
|
||||
'phabricator-notification' => 'bc0774e5',
|
||||
'phabricator-notification-css' => '4b569463',
|
||||
'phabricator-notification-menu-css' => '4b569463',
|
||||
'phabricator-object-item-list-view-css' => '4b569463',
|
||||
'phabricator-object-selector-css' => '8aaacd1b',
|
||||
'phabricator-paste-file-upload' => 'ff199687',
|
||||
'phabricator-prefab' => 'ff199687',
|
||||
'phabricator-paste-file-upload' => 'bc0774e5',
|
||||
'phabricator-prefab' => 'bc0774e5',
|
||||
'phabricator-project-tag-css' => 'e30a3fa8',
|
||||
'phabricator-remarkup-css' => '0e6165d0',
|
||||
'phabricator-shaped-request' => '9dae5f20',
|
||||
'phabricator-side-menu-view-css' => '0e6165d0',
|
||||
'phabricator-standard-page-view' => '0e6165d0',
|
||||
'phabricator-textareautils' => 'ff199687',
|
||||
'phabricator-tooltip' => 'ff199687',
|
||||
'phabricator-transaction-view-css' => '0e6165d0',
|
||||
'phabricator-zindex-css' => '0e6165d0',
|
||||
'sprite-apps-large-css' => '0e6165d0',
|
||||
'sprite-gradient-css' => '0e6165d0',
|
||||
'sprite-icon-css' => '0e6165d0',
|
||||
'sprite-menu-css' => '0e6165d0',
|
||||
'syntax-highlighting-css' => '0e6165d0',
|
||||
'phabricator-remarkup-css' => '4b569463',
|
||||
'phabricator-shaped-request' => '95d0d865',
|
||||
'phabricator-side-menu-view-css' => '4b569463',
|
||||
'phabricator-standard-page-view' => '4b569463',
|
||||
'phabricator-textareautils' => 'bc0774e5',
|
||||
'phabricator-tooltip' => 'bc0774e5',
|
||||
'phabricator-transaction-view-css' => '4b569463',
|
||||
'phabricator-zindex-css' => '4b569463',
|
||||
'sprite-apps-large-css' => '4b569463',
|
||||
'sprite-gradient-css' => '4b569463',
|
||||
'sprite-icon-css' => '4b569463',
|
||||
'sprite-menu-css' => '4b569463',
|
||||
'syntax-highlighting-css' => '4b569463',
|
||||
),
|
||||
));
|
||||
|
|
|
@ -32,6 +32,7 @@ phutil_register_library_map(array(
|
|||
'AphrontFileResponse' => 'aphront/response/AphrontFileResponse.php',
|
||||
'AphrontFormCheckboxControl' => 'view/form/control/AphrontFormCheckboxControl.php',
|
||||
'AphrontFormControl' => 'view/form/control/AphrontFormControl.php',
|
||||
'AphrontFormCropControl' => 'view/form/control/AphrontFormCropControl.php',
|
||||
'AphrontFormDateControl' => 'view/form/control/AphrontFormDateControl.php',
|
||||
'AphrontFormDividerControl' => 'view/form/control/AphrontFormDividerControl.php',
|
||||
'AphrontFormDragAndDropUploadControl' => 'view/form/control/AphrontFormDragAndDropUploadControl.php',
|
||||
|
@ -87,6 +88,7 @@ phutil_register_library_map(array(
|
|||
'AphrontUsageException' => 'aphront/exception/AphrontUsageException.php',
|
||||
'AphrontView' => 'view/AphrontView.php',
|
||||
'AphrontWebpageResponse' => 'aphront/response/AphrontWebpageResponse.php',
|
||||
'AuditPeopleMenuEventListener' => 'applications/audit/events/AuditPeopleMenuEventListener.php',
|
||||
'CelerityAPI' => 'infrastructure/celerity/CelerityAPI.php',
|
||||
'CelerityPhabricatorResourceController' => 'infrastructure/celerity/CelerityPhabricatorResourceController.php',
|
||||
'CelerityResourceController' => 'infrastructure/celerity/CelerityResourceController.php',
|
||||
|
@ -197,12 +199,15 @@ phutil_register_library_map(array(
|
|||
'ConpherenceController' => 'applications/conpherence/controller/ConpherenceController.php',
|
||||
'ConpherenceDAO' => 'applications/conpherence/storage/ConpherenceDAO.php',
|
||||
'ConpherenceEditor' => 'applications/conpherence/editor/ConpherenceEditor.php',
|
||||
'ConpherenceFormDragAndDropUploadControl' => 'applications/conpherence/view/ConpherenceFormDragAndDropUploadControl.php',
|
||||
'ConpherenceImageData' => 'applications/conpherence/constants/ConpherenceImageData.php',
|
||||
'ConpherenceListController' => 'applications/conpherence/controller/ConpherenceListController.php',
|
||||
'ConpherenceMenuItemView' => 'applications/conpherence/view/ConpherenceMenuItemView.php',
|
||||
'ConpherenceNewController' => 'applications/conpherence/controller/ConpherenceNewController.php',
|
||||
'ConpherenceParticipant' => 'applications/conpherence/storage/ConpherenceParticipant.php',
|
||||
'ConpherenceParticipantQuery' => 'applications/conpherence/query/ConpherenceParticipantQuery.php',
|
||||
'ConpherenceParticipationStatus' => 'applications/conpherence/constants/ConpherenceParticipationStatus.php',
|
||||
'ConpherencePeopleMenuEventListener' => 'applications/conpherence/events/ConpherencePeopleMenuEventListener.php',
|
||||
'ConpherenceReplyHandler' => 'applications/conpherence/mail/ConpherenceReplyHandler.php',
|
||||
'ConpherenceThread' => 'applications/conpherence/storage/ConpherenceThread.php',
|
||||
'ConpherenceThreadQuery' => 'applications/conpherence/query/ConpherenceThreadQuery.php',
|
||||
|
@ -307,6 +312,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialNewDiffMail' => 'applications/differential/mail/DifferentialNewDiffMail.php',
|
||||
'DifferentialParseRenderTestCase' => 'applications/differential/__tests__/DifferentialParseRenderTestCase.php',
|
||||
'DifferentialPathFieldSpecification' => 'applications/differential/field/specification/DifferentialPathFieldSpecification.php',
|
||||
'DifferentialPeopleMenuEventListener' => 'applications/differential/events/DifferentialPeopleMenuEventListener.php',
|
||||
'DifferentialPrimaryPaneView' => 'applications/differential/view/DifferentialPrimaryPaneView.php',
|
||||
'DifferentialReplyHandler' => 'applications/differential/DifferentialReplyHandler.php',
|
||||
'DifferentialResultsTableView' => 'applications/differential/view/DifferentialResultsTableView.php',
|
||||
|
@ -420,6 +426,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionPathQuery' => 'applications/diffusion/query/DiffusionPathQuery.php',
|
||||
'DiffusionPathQueryTestCase' => 'applications/diffusion/query/pathid/__tests__/DiffusionPathQueryTestCase.php',
|
||||
'DiffusionPathValidateController' => 'applications/diffusion/controller/DiffusionPathValidateController.php',
|
||||
'DiffusionPeopleMenuEventListener' => 'applications/diffusion/events/DiffusionPeopleMenuEventListener.php',
|
||||
'DiffusionQuery' => 'applications/diffusion/query/DiffusionQuery.php',
|
||||
'DiffusionRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionRawDiffQuery.php',
|
||||
'DiffusionRenameHistoryQuery' => 'applications/diffusion/query/DiffusionRenameHistoryQuery.php',
|
||||
|
@ -563,6 +570,7 @@ phutil_register_library_map(array(
|
|||
'ManiphestDefaultTaskExtensions' => 'applications/maniphest/extensions/ManiphestDefaultTaskExtensions.php',
|
||||
'ManiphestEdgeEventListener' => 'applications/maniphest/event/ManiphestEdgeEventListener.php',
|
||||
'ManiphestExportController' => 'applications/maniphest/controller/ManiphestExportController.php',
|
||||
'ManiphestPeopleMenuEventListener' => 'applications/maniphest/event/ManiphestPeopleMenuEventListener.php',
|
||||
'ManiphestReplyHandler' => 'applications/maniphest/ManiphestReplyHandler.php',
|
||||
'ManiphestReportController' => 'applications/maniphest/controller/ManiphestReportController.php',
|
||||
'ManiphestSavedQuery' => 'applications/maniphest/storage/ManiphestSavedQuery.php',
|
||||
|
@ -695,6 +703,18 @@ phutil_register_library_map(array(
|
|||
'PhabricatorBarePageExample' => 'applications/uiexample/examples/PhabricatorBarePageExample.php',
|
||||
'PhabricatorBarePageView' => 'view/page/PhabricatorBarePageView.php',
|
||||
'PhabricatorBaseEnglishTranslation' => 'infrastructure/internationalization/PhabricatorBaseEnglishTranslation.php',
|
||||
'PhabricatorBaseProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorBaseProtocolAdapter.php',
|
||||
'PhabricatorBot' => 'infrastructure/daemon/bot/PhabricatorBot.php',
|
||||
'PhabricatorBotDebugLogHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotDebugLogHandler.php',
|
||||
'PhabricatorBotDifferentialNotificationHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotDifferentialNotificationHandler.php',
|
||||
'PhabricatorBotFeedNotificationHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotFeedNotificationHandler.php',
|
||||
'PhabricatorBotHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotHandler.php',
|
||||
'PhabricatorBotLogHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotLogHandler.php',
|
||||
'PhabricatorBotMacroHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotMacroHandler.php',
|
||||
'PhabricatorBotMessage' => 'infrastructure/daemon/bot/PhabricatorBotMessage.php',
|
||||
'PhabricatorBotObjectNameHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php',
|
||||
'PhabricatorBotSymbolHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotSymbolHandler.php',
|
||||
'PhabricatorBotWhatsNewHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotWhatsNewHandler.php',
|
||||
'PhabricatorBuiltinPatchList' => 'infrastructure/storage/patch/PhabricatorBuiltinPatchList.php',
|
||||
'PhabricatorButtonsExample' => 'applications/uiexample/examples/PhabricatorButtonsExample.php',
|
||||
'PhabricatorCacheDAO' => 'applications/cache/storage/PhabricatorCacheDAO.php',
|
||||
|
@ -707,6 +727,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorCalendarHoliday' => 'applications/calendar/storage/PhabricatorCalendarHoliday.php',
|
||||
'PhabricatorCalendarHolidayTestCase' => 'applications/calendar/storage/__tests__/PhabricatorCalendarHolidayTestCase.php',
|
||||
'PhabricatorCalendarViewStatusController' => 'applications/calendar/controller/PhabricatorCalendarViewStatusController.php',
|
||||
'PhabricatorCampfireProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorCampfireProtocolAdapter.php',
|
||||
'PhabricatorChangesetResponse' => 'infrastructure/diff/PhabricatorChangesetResponse.php',
|
||||
'PhabricatorChatLogChannelListController' => 'applications/chatlog/controller/PhabricatorChatLogChannelListController.php',
|
||||
'PhabricatorChatLogChannelLogController' => 'applications/chatlog/controller/PhabricatorChatLogChannelLogController.php',
|
||||
|
@ -877,6 +898,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFileStorageConfigurationException' => 'applications/files/exception/PhabricatorFileStorageConfigurationException.php',
|
||||
'PhabricatorFileStorageEngine' => 'applications/files/engine/PhabricatorFileStorageEngine.php',
|
||||
'PhabricatorFileStorageEngineSelector' => 'applications/files/engineselector/PhabricatorFileStorageEngineSelector.php',
|
||||
'PhabricatorFileTestCase' => 'applications/files/storage/__tests__/PhabricatorFileTestCase.php',
|
||||
'PhabricatorFileTransformController' => 'applications/files/controller/PhabricatorFileTransformController.php',
|
||||
'PhabricatorFileUploadController' => 'applications/files/controller/PhabricatorFileUploadController.php',
|
||||
'PhabricatorFileUploadException' => 'applications/files/exception/PhabricatorFileUploadException.php',
|
||||
|
@ -910,17 +932,9 @@ phutil_register_library_map(array(
|
|||
'PhabricatorHeaderView' => 'view/layout/PhabricatorHeaderView.php',
|
||||
'PhabricatorHelpController' => 'applications/help/controller/PhabricatorHelpController.php',
|
||||
'PhabricatorHelpKeyboardShortcutController' => 'applications/help/controller/PhabricatorHelpKeyboardShortcutController.php',
|
||||
'PhabricatorIRCBot' => 'infrastructure/daemon/irc/PhabricatorIRCBot.php',
|
||||
'PhabricatorIRCDifferentialNotificationHandler' => 'infrastructure/daemon/irc/handler/PhabricatorIRCDifferentialNotificationHandler.php',
|
||||
'PhabricatorIRCFeedNotificationHandler' => 'infrastructure/daemon/irc/handler/PhabricatorIRCFeedNotificationHandler.php',
|
||||
'PhabricatorIRCHandler' => 'infrastructure/daemon/irc/handler/PhabricatorIRCHandler.php',
|
||||
'PhabricatorIRCLogHandler' => 'infrastructure/daemon/irc/handler/PhabricatorIRCLogHandler.php',
|
||||
'PhabricatorIRCMacroHandler' => 'infrastructure/daemon/irc/handler/PhabricatorIRCMacroHandler.php',
|
||||
'PhabricatorIRCMessage' => 'infrastructure/daemon/irc/PhabricatorIRCMessage.php',
|
||||
'PhabricatorIRCObjectNameHandler' => 'infrastructure/daemon/irc/handler/PhabricatorIRCObjectNameHandler.php',
|
||||
'PhabricatorIRCProtocolHandler' => 'infrastructure/daemon/irc/handler/PhabricatorIRCProtocolHandler.php',
|
||||
'PhabricatorIRCSymbolHandler' => 'infrastructure/daemon/irc/handler/PhabricatorIRCSymbolHandler.php',
|
||||
'PhabricatorIRCWhatsNewHandler' => 'infrastructure/daemon/irc/handler/PhabricatorIRCWhatsNewHandler.php',
|
||||
'PhabricatorIRCBot' => 'infrastructure/daemon/bot/PhabricatorIRCBot.php',
|
||||
'PhabricatorIRCProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorIRCProtocolAdapter.php',
|
||||
'PhabricatorIRCProtocolHandler' => 'infrastructure/daemon/bot/handler/PhabricatorIRCProtocolHandler.php',
|
||||
'PhabricatorImageTransformer' => 'applications/files/PhabricatorImageTransformer.php',
|
||||
'PhabricatorInfrastructureTestCase' => 'infrastructure/__tests__/PhabricatorInfrastructureTestCase.php',
|
||||
'PhabricatorInlineCommentController' => 'infrastructure/diff/PhabricatorInlineCommentController.php',
|
||||
|
@ -1291,6 +1305,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorTagView' => 'view/layout/PhabricatorTagView.php',
|
||||
'PhabricatorTaskmasterDaemon' => 'infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php',
|
||||
'PhabricatorTestCase' => 'infrastructure/testing/PhabricatorTestCase.php',
|
||||
'PhabricatorTestStorageEngine' => 'applications/files/engine/PhabricatorTestStorageEngine.php',
|
||||
'PhabricatorTestWorker' => 'infrastructure/daemon/workers/__tests__/PhabricatorTestWorker.php',
|
||||
'PhabricatorTimelineCursor' => 'infrastructure/daemon/timeline/storage/PhabricatorTimelineCursor.php',
|
||||
'PhabricatorTimelineDAO' => 'infrastructure/daemon/timeline/storage/PhabricatorTimelineDAO.php',
|
||||
|
@ -1394,6 +1409,7 @@ phutil_register_library_map(array(
|
|||
'PholioController' => 'applications/pholio/controller/PholioController.php',
|
||||
'PholioDAO' => 'applications/pholio/storage/PholioDAO.php',
|
||||
'PholioImage' => 'applications/pholio/storage/PholioImage.php',
|
||||
'PholioInlineSaveController' => 'applications/pholio/controller/PholioInlineSaveController.php',
|
||||
'PholioMock' => 'applications/pholio/storage/PholioMock.php',
|
||||
'PholioMockCommentController' => 'applications/pholio/controller/PholioMockCommentController.php',
|
||||
'PholioMockEditController' => 'applications/pholio/controller/PholioMockEditController.php',
|
||||
|
@ -1521,6 +1537,7 @@ phutil_register_library_map(array(
|
|||
'AphrontFileResponse' => 'AphrontResponse',
|
||||
'AphrontFormCheckboxControl' => 'AphrontFormControl',
|
||||
'AphrontFormControl' => 'AphrontView',
|
||||
'AphrontFormCropControl' => 'AphrontFormControl',
|
||||
'AphrontFormDateControl' => 'AphrontFormControl',
|
||||
'AphrontFormDividerControl' => 'AphrontFormControl',
|
||||
'AphrontFormDragAndDropUploadControl' => 'AphrontFormControl',
|
||||
|
@ -1572,6 +1589,7 @@ phutil_register_library_map(array(
|
|||
'AphrontUsageException' => 'AphrontException',
|
||||
'AphrontView' => 'Phobject',
|
||||
'AphrontWebpageResponse' => 'AphrontHTMLResponse',
|
||||
'AuditPeopleMenuEventListener' => 'PhutilEventListener',
|
||||
'CelerityPhabricatorResourceController' => 'CelerityResourceController',
|
||||
'CelerityResourceController' => 'PhabricatorController',
|
||||
'CelerityResourceGraph' => 'AbstractDirectedGraph',
|
||||
|
@ -1672,12 +1690,15 @@ phutil_register_library_map(array(
|
|||
'ConpherenceController' => 'PhabricatorController',
|
||||
'ConpherenceDAO' => 'PhabricatorLiskDAO',
|
||||
'ConpherenceEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'ConpherenceFormDragAndDropUploadControl' => 'AphrontFormControl',
|
||||
'ConpherenceImageData' => 'ConpherenceConstants',
|
||||
'ConpherenceListController' => 'ConpherenceController',
|
||||
'ConpherenceMenuItemView' => 'AphrontTagView',
|
||||
'ConpherenceNewController' => 'ConpherenceController',
|
||||
'ConpherenceParticipant' => 'ConpherenceDAO',
|
||||
'ConpherenceParticipantQuery' => 'PhabricatorOffsetPagedQuery',
|
||||
'ConpherenceParticipationStatus' => 'ConpherenceConstants',
|
||||
'ConpherencePeopleMenuEventListener' => 'PhutilEventListener',
|
||||
'ConpherenceReplyHandler' => 'PhabricatorMailReplyHandler',
|
||||
'ConpherenceThread' =>
|
||||
array(
|
||||
|
@ -1778,6 +1799,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialNewDiffMail' => 'DifferentialReviewRequestMail',
|
||||
'DifferentialParseRenderTestCase' => 'PhabricatorTestCase',
|
||||
'DifferentialPathFieldSpecification' => 'DifferentialFieldSpecification',
|
||||
'DifferentialPeopleMenuEventListener' => 'PhutilEventListener',
|
||||
'DifferentialPrimaryPaneView' => 'AphrontView',
|
||||
'DifferentialReplyHandler' => 'PhabricatorMailReplyHandler',
|
||||
'DifferentialResultsTableView' => 'AphrontView',
|
||||
|
@ -1873,6 +1895,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionPathCompleteController' => 'DiffusionController',
|
||||
'DiffusionPathQueryTestCase' => 'PhabricatorTestCase',
|
||||
'DiffusionPathValidateController' => 'DiffusionController',
|
||||
'DiffusionPeopleMenuEventListener' => 'PhutilEventListener',
|
||||
'DiffusionRawDiffQuery' => 'DiffusionQuery',
|
||||
'DiffusionRepositoryController' => 'DiffusionController',
|
||||
'DiffusionSetupException' => 'AphrontUsageException',
|
||||
|
@ -1985,6 +2008,7 @@ phutil_register_library_map(array(
|
|||
'ManiphestDefaultTaskExtensions' => 'ManiphestTaskExtensions',
|
||||
'ManiphestEdgeEventListener' => 'PhutilEventListener',
|
||||
'ManiphestExportController' => 'ManiphestController',
|
||||
'ManiphestPeopleMenuEventListener' => 'PhutilEventListener',
|
||||
'ManiphestReplyHandler' => 'PhabricatorMailReplyHandler',
|
||||
'ManiphestReportController' => 'ManiphestController',
|
||||
'ManiphestSavedQuery' => 'ManiphestDAO',
|
||||
|
@ -2131,6 +2155,15 @@ phutil_register_library_map(array(
|
|||
'PhabricatorBarePageExample' => 'PhabricatorUIExample',
|
||||
'PhabricatorBarePageView' => 'AphrontPageView',
|
||||
'PhabricatorBaseEnglishTranslation' => 'PhabricatorTranslation',
|
||||
'PhabricatorBot' => 'PhabricatorDaemon',
|
||||
'PhabricatorBotDebugLogHandler' => 'PhabricatorBotHandler',
|
||||
'PhabricatorBotDifferentialNotificationHandler' => 'PhabricatorBotHandler',
|
||||
'PhabricatorBotFeedNotificationHandler' => 'PhabricatorBotHandler',
|
||||
'PhabricatorBotLogHandler' => 'PhabricatorBotHandler',
|
||||
'PhabricatorBotMacroHandler' => 'PhabricatorBotHandler',
|
||||
'PhabricatorBotObjectNameHandler' => 'PhabricatorBotHandler',
|
||||
'PhabricatorBotSymbolHandler' => 'PhabricatorBotHandler',
|
||||
'PhabricatorBotWhatsNewHandler' => 'PhabricatorBotHandler',
|
||||
'PhabricatorBuiltinPatchList' => 'PhabricatorSQLPatchList',
|
||||
'PhabricatorButtonsExample' => 'PhabricatorUIExample',
|
||||
'PhabricatorCacheDAO' => 'PhabricatorLiskDAO',
|
||||
|
@ -2142,6 +2175,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorCalendarHoliday' => 'PhabricatorCalendarDAO',
|
||||
'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorCalendarViewStatusController' => 'PhabricatorCalendarController',
|
||||
'PhabricatorCampfireProtocolAdapter' => 'PhabricatorBaseProtocolAdapter',
|
||||
'PhabricatorChangesetResponse' => 'AphrontProxyResponse',
|
||||
'PhabricatorChatLogChannelListController' => 'PhabricatorChatLogController',
|
||||
'PhabricatorChatLogChannelLogController' => 'PhabricatorChatLogController',
|
||||
|
@ -2311,6 +2345,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFileShortcutController' => 'PhabricatorFileController',
|
||||
'PhabricatorFileStorageBlob' => 'PhabricatorFileDAO',
|
||||
'PhabricatorFileStorageConfigurationException' => 'Exception',
|
||||
'PhabricatorFileTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorFileTransformController' => 'PhabricatorFileController',
|
||||
'PhabricatorFileUploadController' => 'PhabricatorFileController',
|
||||
'PhabricatorFileUploadException' => 'Exception',
|
||||
|
@ -2340,14 +2375,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorHelpController' => 'PhabricatorController',
|
||||
'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController',
|
||||
'PhabricatorIRCBot' => 'PhabricatorDaemon',
|
||||
'PhabricatorIRCDifferentialNotificationHandler' => 'PhabricatorIRCHandler',
|
||||
'PhabricatorIRCFeedNotificationHandler' => 'PhabricatorIRCHandler',
|
||||
'PhabricatorIRCLogHandler' => 'PhabricatorIRCHandler',
|
||||
'PhabricatorIRCMacroHandler' => 'PhabricatorIRCHandler',
|
||||
'PhabricatorIRCObjectNameHandler' => 'PhabricatorIRCHandler',
|
||||
'PhabricatorIRCProtocolHandler' => 'PhabricatorIRCHandler',
|
||||
'PhabricatorIRCSymbolHandler' => 'PhabricatorIRCHandler',
|
||||
'PhabricatorIRCWhatsNewHandler' => 'PhabricatorIRCHandler',
|
||||
'PhabricatorIRCProtocolAdapter' => 'PhabricatorBaseProtocolAdapter',
|
||||
'PhabricatorIRCProtocolHandler' => 'PhabricatorBotHandler',
|
||||
'PhabricatorInfrastructureTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorInlineCommentController' => 'PhabricatorController',
|
||||
'PhabricatorInlineCommentInterface' => 'PhabricatorMarkupInterface',
|
||||
|
@ -2683,6 +2712,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorTagView' => 'AphrontView',
|
||||
'PhabricatorTaskmasterDaemon' => 'PhabricatorDaemon',
|
||||
'PhabricatorTestCase' => 'ArcanistPhutilTestCase',
|
||||
'PhabricatorTestStorageEngine' => 'PhabricatorFileStorageEngine',
|
||||
'PhabricatorTestWorker' => 'PhabricatorWorker',
|
||||
'PhabricatorTimelineCursor' => 'PhabricatorTimelineDAO',
|
||||
'PhabricatorTimelineDAO' => 'PhabricatorLiskDAO',
|
||||
|
@ -2797,6 +2827,7 @@ phutil_register_library_map(array(
|
|||
0 => 'PholioDAO',
|
||||
1 => 'PhabricatorMarkupInterface',
|
||||
),
|
||||
'PholioInlineSaveController' => 'PholioController',
|
||||
'PholioMock' =>
|
||||
array(
|
||||
0 => 'PholioDAO',
|
||||
|
|
|
@ -18,6 +18,12 @@ final class PhabricatorApplicationAudit extends PhabricatorApplication {
|
|||
return PhabricatorEnv::getDoclink('article/Audit_User_Guide.html');
|
||||
}
|
||||
|
||||
public function getEventListeners() {
|
||||
return array(
|
||||
new AuditPeopleMenuEventListener()
|
||||
);
|
||||
}
|
||||
|
||||
public function getRoutes() {
|
||||
return array(
|
||||
'/audit/' => array(
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
final class AuditPeopleMenuEventListener extends PhutilEventListener {
|
||||
|
||||
public function register() {
|
||||
$this->listen(PhabricatorEventType::TYPE_PEOPLE_DIDRENDERMENU);
|
||||
}
|
||||
|
||||
public function handleEvent(PhutilEvent $event) {
|
||||
switch ($event->getType()) {
|
||||
case PhabricatorEventType::TYPE_PEOPLE_DIDRENDERMENU:
|
||||
$this->handleMenuEvent($event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function handleMenuEvent($event) {
|
||||
$viewer = $event->getUser();
|
||||
$menu = $event->getValue('menu');
|
||||
$person = $event->getValue('person');
|
||||
$username = phutil_escape_uri($person->getUsername());
|
||||
|
||||
$href = '/audit/view/author/'.$username.'/';
|
||||
$name = pht('Commits');
|
||||
|
||||
$menu->addMenuItemToLabel('activity',
|
||||
id(new PhabricatorMenuItemView())
|
||||
->setIsExternal(true)
|
||||
->setName($name)
|
||||
->setHref($href)
|
||||
->setKey($name)
|
||||
);
|
||||
|
||||
$event->setValue('menu', $menu);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -65,20 +65,6 @@ final class PhabricatorSetupCheckDatabase extends PhabricatorSetupCheck {
|
|||
return;
|
||||
}
|
||||
|
||||
if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) {
|
||||
$mode_string = queryfx_one($conn_raw, "SELECT @@sql_mode");
|
||||
$modes = explode(',', $mode_string['@@sql_mode']);
|
||||
if (!in_array('STRICT_ALL_TABLES', $modes)) {
|
||||
$message = pht(
|
||||
"The global sql_mode is not set to 'STRICT_ALL_TABLES'. It is ".
|
||||
"recommended that you set this mode while developing Phabricator.");
|
||||
|
||||
$this->newIssue('mysql.mode')
|
||||
->setName(pht('MySQL STRICT_ALL_TABLES mode not set.'))
|
||||
->setMessage($message);
|
||||
}
|
||||
}
|
||||
|
||||
$namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace');
|
||||
|
||||
$databases = queryfx_all($conn_raw, 'SHOW DATABASES');
|
||||
|
|
|
@ -24,6 +24,31 @@ final class PhabricatorSetupCheckMySQL extends PhabricatorSetupCheck {
|
|||
->setName(pht('Small MySQL "max_allowed_packet"'))
|
||||
->setMessage($message);
|
||||
}
|
||||
|
||||
if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) {
|
||||
$mode_string = queryfx_one($conn_raw, "SELECT @@sql_mode");
|
||||
$modes = explode(',', $mode_string['@@sql_mode']);
|
||||
if (!in_array('STRICT_ALL_TABLES', $modes)) {
|
||||
$summary = pht(
|
||||
"MySQL is not in strict mode, but should be for Phabricator ".
|
||||
"development.");
|
||||
|
||||
$message = pht(
|
||||
"This install is in developer mode, but the global sql_mode is not ".
|
||||
"set to 'STRICT_ALL_TABLES'. It is recommended that you set this ".
|
||||
"mode while developing Phabricator. Strict mode will promote some ".
|
||||
"query warnings to errors, and ensure you don't miss them during ".
|
||||
"development. You can find more information about this mode (and ".
|
||||
"how to configure it) in the MySQL manual.");
|
||||
|
||||
$this->newIssue('mysql.mode')
|
||||
->setName(pht('MySQL STRICT_ALL_TABLES Mode Not Set'))
|
||||
->addPhabricatorConfig('phabricator.developer-mode')
|
||||
->setSummary($summary)
|
||||
->setMessage($message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -33,6 +33,12 @@ final class PhabricatorApplicationConpherence extends PhabricatorApplication {
|
|||
return self::GROUP_COMMUNICATION;
|
||||
}
|
||||
|
||||
public function getEventListeners() {
|
||||
return array(
|
||||
new ConpherencePeopleMenuEventListener(),
|
||||
);
|
||||
}
|
||||
|
||||
public function getRoutes() {
|
||||
return array(
|
||||
'/conpherence/' => array(
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
final class ConpherenceImageData extends ConpherenceConstants {
|
||||
|
||||
const SIZE_ORIG = 'original';
|
||||
const SIZE_HEAD = 'header';
|
||||
|
||||
const HEAD_WIDTH = 120;
|
||||
const HEAD_HEIGHT = 80;
|
||||
|
||||
}
|
|
@ -8,6 +8,7 @@ final class ConpherenceTransactionType extends ConpherenceConstants {
|
|||
const TYPE_FILES = 'files';
|
||||
const TYPE_TITLE = 'title';
|
||||
const TYPE_PICTURE = 'picture';
|
||||
const TYPE_PICTURE_CROP = 'picture-crop';
|
||||
const TYPE_PARTICIPANTS = 'participants';
|
||||
|
||||
}
|
||||
|
|
|
@ -131,7 +131,10 @@ abstract class ConpherenceController extends PhabricatorController {
|
|||
$user = $this->getRequest()->getUser();
|
||||
foreach ($conpherences as $conpherence) {
|
||||
$uri = $this->getApplicationURI('view/'.$conpherence->getID().'/');
|
||||
$data = $conpherence->getDisplayData($user);
|
||||
$data = $conpherence->getDisplayData(
|
||||
$user,
|
||||
null
|
||||
);
|
||||
$title = $data['title'];
|
||||
$subtitle = $data['subtitle'];
|
||||
$unread_count = $data['unread_count'];
|
||||
|
@ -206,6 +209,7 @@ abstract class ConpherenceController extends PhabricatorController {
|
|||
'messages' => 'conpherence-messages',
|
||||
'widgets_pane' => 'conpherence-widget-pane',
|
||||
'form_pane' => 'conpherence-form',
|
||||
'menu_pane' => 'conpherence-menu',
|
||||
'fancy_ajax' => (bool) $this->getSelectedConpherencePHID()
|
||||
)
|
||||
);
|
||||
|
@ -219,6 +223,14 @@ abstract class ConpherenceController extends PhabricatorController {
|
|||
'form_pane' => 'conpherence-form'
|
||||
)
|
||||
);
|
||||
Javelin::initBehavior('conpherence-drag-and-drop-photo',
|
||||
array(
|
||||
'target' => 'conpherence-header-pane',
|
||||
'form_pane' => 'conpherence-form',
|
||||
'upload_uri' => '/file/dropupload/',
|
||||
'activated_class' => 'conpherence-header-upload-photo',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -30,6 +30,8 @@ final class ConpherenceUpdateController extends
|
|||
$conpherence = id(new ConpherenceThreadQuery())
|
||||
->setViewer($user)
|
||||
->withIDs(array($conpherence_id))
|
||||
->needOrigPics(true)
|
||||
->needHeaderPics(true)
|
||||
->executeOne();
|
||||
$supported_formats = PhabricatorFile::getTransformableImageFormats();
|
||||
|
||||
|
@ -59,31 +61,63 @@ final class ConpherenceUpdateController extends
|
|||
break;
|
||||
case 'metadata':
|
||||
$xactions = array();
|
||||
$images = $request->getArr('image');
|
||||
if ($images) {
|
||||
// just take the first one
|
||||
$file_phid = reset($images);
|
||||
$file = id(new PhabricatorFileQuery())
|
||||
$top = $request->getInt('image_y');
|
||||
$left = $request->getInt('image_x');
|
||||
$file_id = $request->getInt('file_id');
|
||||
if ($file_id) {
|
||||
$orig_file = id(new PhabricatorFileQuery())
|
||||
->setViewer($user)
|
||||
->withPHIDs(array($file_phid))
|
||||
->withIDs(array($file_id))
|
||||
->executeOne();
|
||||
$okay = $file->isTransformableImage();
|
||||
$okay = $orig_file->isTransformableImage();
|
||||
if ($okay) {
|
||||
$xformer = new PhabricatorImageTransformer();
|
||||
$xformed = $xformer->executeThumbTransform(
|
||||
$file,
|
||||
$x = 50,
|
||||
$y = 50);
|
||||
$image_phid = $xformed->getPHID();
|
||||
$xactions[] = id(new ConpherenceTransaction())
|
||||
->setTransactionType(ConpherenceTransactionType::TYPE_PICTURE)
|
||||
->setNewValue($image_phid);
|
||||
->setNewValue($orig_file->getPHID());
|
||||
// do 2 transformations "crudely"
|
||||
$xformer = new PhabricatorImageTransformer();
|
||||
$header_file = $xformer->executeConpherenceTransform(
|
||||
$orig_file,
|
||||
0,
|
||||
0,
|
||||
ConpherenceImageData::HEAD_WIDTH,
|
||||
ConpherenceImageData::HEAD_HEIGHT
|
||||
);
|
||||
// this is handled outside the editor for now. no particularly
|
||||
// good reason to move it inside
|
||||
$conpherence->setImagePHIDs(
|
||||
array(
|
||||
ConpherenceImageData::SIZE_HEAD => $header_file->getPHID(),
|
||||
)
|
||||
);
|
||||
$conpherence->setImages(
|
||||
array(
|
||||
ConpherenceImageData::SIZE_HEAD => $header_file,
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$e_file[] = $file;
|
||||
$e_file[] = $orig_file;
|
||||
$errors[] =
|
||||
pht('This server only supports these image formats: %s.',
|
||||
implode(', ', $supported_formats));
|
||||
}
|
||||
} else if ($top !== null || $left !== null) {
|
||||
$file = $conpherence->getImage(ConpherenceImageData::SIZE_ORIG);
|
||||
$xformer = new PhabricatorImageTransformer();
|
||||
$xformed = $xformer->executeConpherenceTransform(
|
||||
$file,
|
||||
$top,
|
||||
$left,
|
||||
ConpherenceImageData::HEAD_WIDTH,
|
||||
ConpherenceImageData::HEAD_HEIGHT
|
||||
);
|
||||
$image_phid = $xformed->getPHID();
|
||||
|
||||
$xactions[] = id(new ConpherenceTransaction())
|
||||
->setTransactionType(
|
||||
ConpherenceTransactionType::TYPE_PICTURE_CROP
|
||||
)
|
||||
->setNewValue($image_phid);
|
||||
}
|
||||
$title = $request->getStr('title');
|
||||
if ($title != $conpherence->getTitle()) {
|
||||
|
@ -131,24 +165,42 @@ final class ConpherenceUpdateController extends
|
|||
->setLabel(pht('Title'))
|
||||
->setName('title')
|
||||
->setValue($conpherence->getTitle())
|
||||
)
|
||||
->appendChild(
|
||||
id(new AphrontFormMarkupControl())
|
||||
->setLabel(pht('Image'))
|
||||
->setValue(phutil_tag(
|
||||
'img',
|
||||
array(
|
||||
'src' => $conpherence->loadImageURI(),
|
||||
))
|
||||
)
|
||||
)
|
||||
->appendChild(
|
||||
id(new AphrontFormDragAndDropUploadControl())
|
||||
->setLabel(pht('Change Image'))
|
||||
->setName('image')
|
||||
->setValue($e_file)
|
||||
->setCaption('Supported formats: '.implode(', ', $supported_formats))
|
||||
);
|
||||
|
||||
$image = $conpherence->getImage(ConpherenceImageData::SIZE_ORIG);
|
||||
if ($image) {
|
||||
$form
|
||||
->appendChild(
|
||||
id(new AphrontFormMarkupControl())
|
||||
->setLabel(pht('Image'))
|
||||
->setValue(phutil_tag(
|
||||
'img',
|
||||
array(
|
||||
'src' =>
|
||||
$conpherence->loadImageURI(ConpherenceImageData::SIZE_HEAD),
|
||||
))
|
||||
)
|
||||
)
|
||||
->appendChild(
|
||||
id(new AphrontFormCropControl())
|
||||
->setLabel(pht('Crop Image'))
|
||||
->setValue($image)
|
||||
->setWidth(ConpherenceImageData::HEAD_WIDTH)
|
||||
->setHeight(ConpherenceImageData::HEAD_HEIGHT)
|
||||
)
|
||||
->appendChild(
|
||||
id(new ConpherenceFormDragAndDropUploadControl())
|
||||
->setLabel(pht('Change Image'))
|
||||
);
|
||||
|
||||
} else {
|
||||
|
||||
$form
|
||||
->appendChild(
|
||||
id(new ConpherenceFormDragAndDropUploadControl())
|
||||
->setLabel(pht('Image'))
|
||||
);
|
||||
}
|
||||
|
||||
require_celerity_resource('conpherence-update-css');
|
||||
return id(new AphrontDialogResponse())
|
||||
|
|
|
@ -45,6 +45,7 @@ final class ConpherenceViewController extends
|
|||
->setViewer($user)
|
||||
->withIDs(array($conpherence_id))
|
||||
->needWidgetData(true)
|
||||
->needHeaderPics(true)
|
||||
->executeOne();
|
||||
$this->setConpherence($conpherence);
|
||||
|
||||
|
@ -67,23 +68,34 @@ final class ConpherenceViewController extends
|
|||
require_celerity_resource('conpherence-header-pane-css');
|
||||
$user = $this->getRequest()->getUser();
|
||||
$conpherence = $this->getConpherence();
|
||||
$display_data = $conpherence->getDisplayData($user);
|
||||
$display_data = $conpherence->getDisplayData(
|
||||
$user,
|
||||
ConpherenceImageData::SIZE_HEAD
|
||||
);
|
||||
$edit_href = $this->getApplicationURI('update/'.$conpherence->getID().'/');
|
||||
$class_mod = $display_data['image_class'];
|
||||
|
||||
$header =
|
||||
phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'upload-photo'
|
||||
),
|
||||
pht('Drop photo here to change this Conpherence photo.')
|
||||
).
|
||||
javelin_tag(
|
||||
'a',
|
||||
array(
|
||||
'class' => 'edit',
|
||||
'href' => $edit_href,
|
||||
'sigil' => 'workflow',
|
||||
'sigil' => 'workflow edit-action',
|
||||
),
|
||||
''
|
||||
).
|
||||
phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'header-image',
|
||||
'class' => $class_mod.'header-image',
|
||||
'style' => 'background-image: url('.$display_data['image'].');'
|
||||
),
|
||||
''
|
||||
|
@ -91,14 +103,14 @@ final class ConpherenceViewController extends
|
|||
phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'title',
|
||||
'class' => $class_mod.'title',
|
||||
),
|
||||
$display_data['title']
|
||||
).
|
||||
phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'subtitle',
|
||||
'class' => $class_mod.'subtitle',
|
||||
),
|
||||
$display_data['subtitle']
|
||||
);
|
||||
|
@ -114,6 +126,18 @@ final class ConpherenceViewController extends
|
|||
$rendered_transactions = array();
|
||||
|
||||
$transactions = $conpherence->getTransactions();
|
||||
|
||||
$engine = id(new PhabricatorMarkupEngine())
|
||||
->setViewer($user);
|
||||
foreach ($transactions as $transaction) {
|
||||
if ($transaction->getComment()) {
|
||||
$engine->addObject(
|
||||
$transaction->getComment(),
|
||||
PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);
|
||||
}
|
||||
}
|
||||
$engine->process();
|
||||
|
||||
foreach ($transactions as $transaction) {
|
||||
if ($transaction->shouldHide()) {
|
||||
continue;
|
||||
|
@ -122,6 +146,7 @@ final class ConpherenceViewController extends
|
|||
->setUser($user)
|
||||
->setConpherenceTransaction($transaction)
|
||||
->setHandles($handles)
|
||||
->setMarkupEngine($engine)
|
||||
->render();
|
||||
}
|
||||
$transactions = implode(' ', $rendered_transactions);
|
||||
|
|
|
@ -48,6 +48,7 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
|
|||
|
||||
$types[] = ConpherenceTransactionType::TYPE_TITLE;
|
||||
$types[] = ConpherenceTransactionType::TYPE_PICTURE;
|
||||
$types[] = ConpherenceTransactionType::TYPE_PICTURE_CROP;
|
||||
$types[] = ConpherenceTransactionType::TYPE_PARTICIPANTS;
|
||||
$types[] = ConpherenceTransactionType::TYPE_FILES;
|
||||
|
||||
|
@ -62,7 +63,9 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
|
|||
case ConpherenceTransactionType::TYPE_TITLE:
|
||||
return $object->getTitle();
|
||||
case ConpherenceTransactionType::TYPE_PICTURE:
|
||||
return $object->getImagePHID();
|
||||
return $object->getImagePHID(ConpherenceImageData::SIZE_ORIG);
|
||||
case ConpherenceTransactionType::TYPE_PICTURE_CROP:
|
||||
return $object->getImagePHID(ConpherenceImageData::SIZE_HEAD);
|
||||
case ConpherenceTransactionType::TYPE_PARTICIPANTS:
|
||||
return $object->getParticipantPHIDs();
|
||||
case ConpherenceTransactionType::TYPE_FILES:
|
||||
|
@ -77,6 +80,7 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
|
|||
switch ($xaction->getTransactionType()) {
|
||||
case ConpherenceTransactionType::TYPE_TITLE:
|
||||
case ConpherenceTransactionType::TYPE_PICTURE:
|
||||
case ConpherenceTransactionType::TYPE_PICTURE_CROP:
|
||||
return $xaction->getNewValue();
|
||||
case ConpherenceTransactionType::TYPE_PARTICIPANTS:
|
||||
case ConpherenceTransactionType::TYPE_FILES:
|
||||
|
@ -93,7 +97,16 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
|
|||
$object->setTitle($xaction->getNewValue());
|
||||
break;
|
||||
case ConpherenceTransactionType::TYPE_PICTURE:
|
||||
$object->setImagePHID($xaction->getNewValue());
|
||||
$object->setImagePHID(
|
||||
$xaction->getNewValue(),
|
||||
ConpherenceImageData::SIZE_ORIG
|
||||
);
|
||||
break;
|
||||
case ConpherenceTransactionType::TYPE_PICTURE_CROP:
|
||||
$object->setImagePHID(
|
||||
$xaction->getNewValue(),
|
||||
ConpherenceImageData::SIZE_HEAD
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
final class ConpherencePeopleMenuEventListener extends PhutilEventListener {
|
||||
|
||||
public function register() {
|
||||
$this->listen(PhabricatorEventType::TYPE_PEOPLE_DIDRENDERMENU);
|
||||
}
|
||||
|
||||
public function handleEvent(PhutilEvent $event) {
|
||||
switch ($event->getType()) {
|
||||
case PhabricatorEventType::TYPE_PEOPLE_DIDRENDERMENU:
|
||||
$this->handleMenuEvent($event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function handleMenuEvent($event) {
|
||||
$viewer = $event->getUser();
|
||||
$menu = $event->getValue('menu');
|
||||
$person = $event->getValue('person');
|
||||
|
||||
$conpherence_uri =
|
||||
new PhutilURI('/conpherence/new/?participant='.$person->getPHID());
|
||||
$name = pht('Conpherence');
|
||||
|
||||
$menu->addMenuItemBefore('activity',
|
||||
id(new PhabricatorMenuItemView())
|
||||
->setIsExternal(true)
|
||||
->setName($name)
|
||||
->setHref($conpherence_uri)
|
||||
->setKey($name)
|
||||
);
|
||||
|
||||
$event->setValue('menu', $menu);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -9,6 +9,18 @@ final class ConpherenceThreadQuery
|
|||
private $phids;
|
||||
private $ids;
|
||||
private $needWidgetData;
|
||||
private $needHeaderPics;
|
||||
private $needOrigPics;
|
||||
|
||||
public function needOrigPics($need_orig_pics) {
|
||||
$this->needOrigPics = $need_orig_pics;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function needHeaderPics($need_header_pics) {
|
||||
$this->needHeaderPics = $need_header_pics;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function needWidgetData($need_widget_data) {
|
||||
$this->needWidgetData = $need_widget_data;
|
||||
|
@ -47,6 +59,12 @@ final class ConpherenceThreadQuery
|
|||
if ($this->needWidgetData) {
|
||||
$this->loadWidgetData($conpherences);
|
||||
}
|
||||
if ($this->needOrigPics) {
|
||||
$this->loadOrigPics($conpherences);
|
||||
}
|
||||
if ($this->needHeaderPics) {
|
||||
$this->loadHeaderPics($conpherences);
|
||||
}
|
||||
}
|
||||
|
||||
return $conpherences;
|
||||
|
@ -187,4 +205,39 @@ final class ConpherenceThreadQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
private function loadOrigPics(array $conpherences) {
|
||||
return $this->loadPics(
|
||||
$conpherences,
|
||||
ConpherenceImageData::SIZE_ORIG
|
||||
);
|
||||
}
|
||||
|
||||
private function loadHeaderPics(array $conpherences) {
|
||||
return $this->loadPics(
|
||||
$conpherences,
|
||||
ConpherenceImageData::SIZE_HEAD
|
||||
);
|
||||
}
|
||||
|
||||
private function loadPics(array $conpherences, $size) {
|
||||
$conpherence_pic_phids = array();
|
||||
foreach ($conpherences as $conpherence) {
|
||||
$phid = $conpherence->getImagePHID($size);
|
||||
if ($phid) {
|
||||
$conpherence_pic_phids[$conpherence->getPHID()] = $phid;
|
||||
}
|
||||
}
|
||||
$files = id(new PhabricatorFileQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->withPHIDs($conpherence_pic_phids)
|
||||
->execute();
|
||||
$files = mpull($files, null, 'getPHID');
|
||||
|
||||
foreach ($conpherence_pic_phids as $conpherence_phid => $pic_phid) {
|
||||
$conpherences[$conpherence_phid]->setImage($files[$pic_phid], $size);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ final class ConpherenceThread extends ConpherenceDAO
|
|||
protected $id;
|
||||
protected $phid;
|
||||
protected $title;
|
||||
protected $imagePHID;
|
||||
protected $imagePHIDs = array();
|
||||
protected $mailKey;
|
||||
|
||||
private $participants;
|
||||
|
@ -17,10 +17,14 @@ final class ConpherenceThread extends ConpherenceDAO
|
|||
private $handles;
|
||||
private $filePHIDs;
|
||||
private $widgetData;
|
||||
private $images = array();
|
||||
|
||||
public function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_AUX_PHID => true,
|
||||
self::CONFIG_SERIALIZATION => array(
|
||||
'imagePHIDs' => self::SERIALIZATION_JSON,
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
|
@ -36,6 +40,33 @@ final class ConpherenceThread extends ConpherenceDAO
|
|||
return parent::save();
|
||||
}
|
||||
|
||||
public function getImagePHID($size) {
|
||||
$image_phids = $this->getImagePHIDs();
|
||||
return idx($image_phids, $size);
|
||||
}
|
||||
public function setImagePHID($phid, $size) {
|
||||
$image_phids = $this->getImagePHIDs();
|
||||
$image_phids[$size] = $phid;
|
||||
return $this->setImagePHIDs($image_phids);
|
||||
}
|
||||
|
||||
public function getImage($size) {
|
||||
$images = $this->getImages();
|
||||
return idx($images, $size);
|
||||
}
|
||||
public function setImage(PhabricatorFile $file, $size) {
|
||||
$files = $this->getImages();
|
||||
$files[$size] = $file;
|
||||
return $this->setImages($files);
|
||||
}
|
||||
public function setImages(array $files) {
|
||||
$this->images = $files;
|
||||
return $this;
|
||||
}
|
||||
private function getImages() {
|
||||
return $this->images;
|
||||
}
|
||||
|
||||
public function attachParticipants(array $participants) {
|
||||
assert_instances_of($participants, 'ConpherenceParticipant');
|
||||
$this->participants = $participants;
|
||||
|
@ -112,59 +143,36 @@ final class ConpherenceThread extends ConpherenceDAO
|
|||
return $this->widgetData;
|
||||
}
|
||||
|
||||
public function loadImageURI() {
|
||||
$src_phid = $this->getImagePHID();
|
||||
public function loadImageURI($size) {
|
||||
$file = $this->getImage($size);
|
||||
|
||||
if ($src_phid) {
|
||||
$file = id(new PhabricatorFile())->loadOneWhere('phid = %s', $src_phid);
|
||||
if ($file) {
|
||||
return $file->getBestURI();
|
||||
}
|
||||
if ($file) {
|
||||
return $file->getBestURI();
|
||||
}
|
||||
|
||||
return PhabricatorUser::getDefaultProfileImageURI();
|
||||
}
|
||||
|
||||
public function getDisplayData(PhabricatorUser $user) {
|
||||
public function getDisplayData(PhabricatorUser $user, $size) {
|
||||
$transactions = $this->getTransactions();
|
||||
$latest_transaction = end($transactions);
|
||||
$latest_participant = $latest_transaction->getAuthorPHID();
|
||||
$handles = $this->getHandles();
|
||||
$latest_handle = $handles[$latest_participant];
|
||||
if ($this->getImagePHID()) {
|
||||
$img_src = $this->loadImageURI();
|
||||
} else {
|
||||
$img_src = $latest_handle->getImageURI();
|
||||
}
|
||||
$title = $this->getTitle();
|
||||
if (!$title) {
|
||||
$title = $latest_handle->getName();
|
||||
unset($handles[$latest_participant]);
|
||||
}
|
||||
unset($handles[$user->getPHID()]);
|
||||
|
||||
$subtitle = '';
|
||||
$count = 0;
|
||||
$final = false;
|
||||
foreach ($handles as $handle) {
|
||||
if ($handle->getType() != PhabricatorPHIDConstants::PHID_TYPE_USER) {
|
||||
continue;
|
||||
}
|
||||
if ($subtitle) {
|
||||
if ($final) {
|
||||
$subtitle .= '...';
|
||||
break;
|
||||
} else {
|
||||
$subtitle .= ', ';
|
||||
}
|
||||
}
|
||||
$subtitle .= $handle->getName();
|
||||
$count++;
|
||||
$final = $count == 3;
|
||||
$handles = $this->getHandles();
|
||||
// we don't want to show the user unless they are babbling to themselves
|
||||
if (count($handles) > 1) {
|
||||
unset($handles[$user->getPHID()]);
|
||||
}
|
||||
|
||||
$participants = $this->getParticipants();
|
||||
$user_participation = $participants[$user->getPHID()];
|
||||
$latest_transaction = null;
|
||||
$title = $this->getTitle();
|
||||
$subtitle = '';
|
||||
$img_src = null;
|
||||
$img_class = null;
|
||||
if ($this->getImagePHID($size)) {
|
||||
$img_src = $this->getImage($size)->getBestURI();
|
||||
$img_class = 'custom-';
|
||||
}
|
||||
$unread_count = 0;
|
||||
$max_count = 10;
|
||||
$snippet = null;
|
||||
|
@ -186,9 +194,28 @@ final class ConpherenceThread extends ConpherenceDAO
|
|||
$transaction->getComment()->getContent(),
|
||||
48
|
||||
);
|
||||
if ($transaction->getAuthorPHID() == $user->getPHID()) {
|
||||
$snippet = "\xE2\x86\xB0 " . $snippet;
|
||||
}
|
||||
}
|
||||
// fallthrough intentionally here
|
||||
case ConpherenceTransactionType::TYPE_FILES:
|
||||
if (!$latest_transaction) {
|
||||
$latest_transaction = $transaction;
|
||||
}
|
||||
$latest_participant_phid = $transaction->getAuthorPHID();
|
||||
if ((!$title || !$img_src) &&
|
||||
$latest_participant_phid != $user->getPHID()) {
|
||||
$latest_handle = $handles[$latest_participant_phid];
|
||||
if (!$img_src) {
|
||||
$img_src = $latest_handle->getImageURI();
|
||||
}
|
||||
if (!$title) {
|
||||
$title = $latest_handle->getName();
|
||||
// (maybs) used the pic, definitely used the name -- discard
|
||||
unset($handles[$latest_participant_phid]);
|
||||
}
|
||||
}
|
||||
if ($behind_transaction_phid) {
|
||||
$unread_count++;
|
||||
if ($transaction->getPHID() == $behind_transaction_phid) {
|
||||
|
@ -210,12 +237,45 @@ final class ConpherenceThread extends ConpherenceDAO
|
|||
$unread_count = $max_count.'+';
|
||||
}
|
||||
|
||||
// This happens if the user has been babbling, maybs just to themselves,
|
||||
// but enough un-responded to transactions for our SQL limit would
|
||||
// hit this too... Also happens on new threads since only the first
|
||||
// author has participated.
|
||||
// ...so just pick a different handle in these cases.
|
||||
$some_handle = reset($handles);
|
||||
if (!$img_src) {
|
||||
$img_src = $some_handle->getImageURI();
|
||||
}
|
||||
if (!$title) {
|
||||
$title = $some_handle->getName();
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
$final = false;
|
||||
foreach ($handles as $handle) {
|
||||
if ($handle->getType() != PhabricatorPHIDConstants::PHID_TYPE_USER) {
|
||||
continue;
|
||||
}
|
||||
if ($subtitle) {
|
||||
if ($final) {
|
||||
$subtitle .= '...';
|
||||
break;
|
||||
} else {
|
||||
$subtitle .= ', ';
|
||||
}
|
||||
}
|
||||
$subtitle .= $handle->getName();
|
||||
$count++;
|
||||
$final = $count == 3;
|
||||
}
|
||||
|
||||
return array(
|
||||
'title' => $title,
|
||||
'subtitle' => $subtitle,
|
||||
'unread_count' => $unread_count,
|
||||
'epoch' => $latest_transaction->getDateCreated(),
|
||||
'image' => $img_src,
|
||||
'image_class' => $img_class,
|
||||
'snippet' => $snippet,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction {
|
|||
case ConpherenceTransactionType::TYPE_PICTURE:
|
||||
return false;
|
||||
case ConpherenceTransactionType::TYPE_FILES:
|
||||
case ConpherenceTransactionType::TYPE_PICTURE_CROP:
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -107,12 +108,10 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction {
|
|||
$phids[] = $this->getAuthorPHID();
|
||||
switch ($this->getTransactionType()) {
|
||||
case ConpherenceTransactionType::TYPE_PICTURE:
|
||||
$phids[] = $new;
|
||||
break;
|
||||
case ConpherenceTransactionType::TYPE_TITLE:
|
||||
case ConpherenceTransactionType::TYPE_FILES:
|
||||
break;
|
||||
case ConpherenceTransactionType::TYPE_PARTICIPANTS:
|
||||
case ConpherenceTransactionType::TYPE_FILES:
|
||||
$phids = array_merge($phids, $this->getOldValue());
|
||||
$phids = array_merge($phids, $this->getNewValue());
|
||||
break;
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
final class ConpherenceFormDragAndDropUploadControl extends AphrontFormControl {
|
||||
|
||||
private $dropID;
|
||||
|
||||
public function setDropID($drop_id) {
|
||||
$this->dropID = $drop_id;
|
||||
return $this;
|
||||
}
|
||||
public function getDropID() {
|
||||
return $this->dropID;
|
||||
}
|
||||
|
||||
protected function getCustomControlClass() {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function renderInput() {
|
||||
|
||||
$drop_id = celerity_generate_unique_node_id();
|
||||
Javelin::initBehavior('conpherence-drag-and-drop-photo',
|
||||
array(
|
||||
'target' => $drop_id,
|
||||
'form_pane' => 'conpherence-form',
|
||||
'upload_uri' => '/file/dropupload/',
|
||||
'activated_class' => 'conpherence-dialogue-upload-photo',
|
||||
)
|
||||
);
|
||||
require_celerity_resource('conpherence-update-css');
|
||||
|
||||
return phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'id' => $drop_id,
|
||||
'class' => 'conpherence-dialogue-drag-photo',
|
||||
),
|
||||
pht('Drag and drop an image here to upload it.')
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -7,6 +7,12 @@ final class ConpherenceTransactionView extends AphrontView {
|
|||
|
||||
private $conpherenceTransaction;
|
||||
private $handles;
|
||||
private $markupEngine;
|
||||
|
||||
public function setMarkupEngine(PhabricatorMarkupEngine $markup_engine) {
|
||||
$this->markupEngine = $markup_engine;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setHandles(array $handles) {
|
||||
assert_instances_of($handles, 'PhabricatorObjectHandle');
|
||||
|
@ -37,8 +43,11 @@ final class ConpherenceTransactionView extends AphrontView {
|
|||
|
||||
$content = null;
|
||||
$content_class = null;
|
||||
$content = null;
|
||||
switch ($transaction->getTransactionType()) {
|
||||
case ConpherenceTransactionType::TYPE_TITLE:
|
||||
case ConpherenceTransactionType::TYPE_PICTURE:
|
||||
case ConpherenceTransactionType::TYPE_PICTURE_CROP:
|
||||
$content = $transaction->getTitle();
|
||||
$transaction_view->addClass('conpherence-edited');
|
||||
break;
|
||||
|
@ -47,12 +56,13 @@ final class ConpherenceTransactionView extends AphrontView {
|
|||
break;
|
||||
case ConpherenceTransactionType::TYPE_PICTURE:
|
||||
$img = $transaction->getHandle($transaction->getNewValue());
|
||||
$content = $transaction->getTitle() .
|
||||
$content = array(
|
||||
$transaction->getTitle(),
|
||||
phutil_tag(
|
||||
'img',
|
||||
array(
|
||||
'src' => $img->getImageURI()
|
||||
));
|
||||
)));
|
||||
$transaction_view->addClass('conpherence-edited');
|
||||
break;
|
||||
case ConpherenceTransactionType::TYPE_PARTICIPANTS:
|
||||
|
@ -65,18 +75,9 @@ final class ConpherenceTransactionView extends AphrontView {
|
|||
PhabricatorMarkupEngine::extractFilePHIDsFromEmbeddedFiles(
|
||||
array($comment->getContent())
|
||||
);
|
||||
$markup_field = ConpherenceTransactionComment::MARKUP_FIELD_COMMENT;
|
||||
$engine = id(new PhabricatorMarkupEngine())
|
||||
->setViewer($this->getUser());
|
||||
$engine->addObject(
|
||||
$content = $this->markupEngine->getOutput(
|
||||
$comment,
|
||||
$markup_field
|
||||
);
|
||||
$engine->process();
|
||||
$content = $engine->getOutput(
|
||||
$comment,
|
||||
$markup_field
|
||||
);
|
||||
PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);
|
||||
$content_class = 'conpherence-message phabricator-remarkup';
|
||||
$transaction_view
|
||||
->setImageURI($author->getImageURI())
|
||||
|
@ -90,7 +91,7 @@ final class ConpherenceTransactionView extends AphrontView {
|
|||
array(
|
||||
'class' => $content_class
|
||||
),
|
||||
new PhutilSafeHTML($content))
|
||||
$this->renderHTMLView($content))
|
||||
);
|
||||
|
||||
return $transaction_view->render();
|
||||
|
|
|
@ -28,6 +28,12 @@ final class PhabricatorApplicationDifferential extends PhabricatorApplication {
|
|||
return "\xE2\x9A\x99";
|
||||
}
|
||||
|
||||
public function getEventListeners() {
|
||||
return array(
|
||||
new DifferentialPeopleMenuEventListener()
|
||||
);
|
||||
}
|
||||
|
||||
public function getRoutes() {
|
||||
return array(
|
||||
'/D(?P<id>[1-9]\d*)' => 'DifferentialRevisionViewController',
|
||||
|
|
|
@ -72,7 +72,6 @@ final class ConduitAPI_differential_getdiff_Method extends ConduitAPIMethod {
|
|||
$project_name = null;
|
||||
}
|
||||
$basic_dict['projectName'] = $project_name;
|
||||
$basic_dict['author'] = $diff->loadAuthorInformation();
|
||||
|
||||
return $basic_dict;
|
||||
}
|
||||
|
|
|
@ -127,28 +127,6 @@ final class PhabricatorDifferentialConfigOptions
|
|||
"If you set this to true, users won't need to login to view ".
|
||||
"Differential revisions. Anonymous users will have read-only ".
|
||||
"access and won't be able to interact with the revisions.")),
|
||||
$this->newOption('differential.expose-emails-prudently', 'bool', false)
|
||||
->setBoolOptions(
|
||||
array(
|
||||
pht("Expose revision author email address via Conduit"),
|
||||
pht("Don't expose revision author email address via Conduit"),
|
||||
))
|
||||
->setSummary(
|
||||
pht(
|
||||
"Determines whether or not the author's email address should be ".
|
||||
"exposed via Conduit."))
|
||||
->setDescription(
|
||||
pht(
|
||||
"If you set this to true, revision author email address ".
|
||||
"information will be exposed in Conduit. This is useful for ".
|
||||
"Arcanist.\n\n".
|
||||
"For example, consider the 'arc patch DX' workflow which needs ".
|
||||
"to ask Differential for the revision DX. This data often should ".
|
||||
"contain the author's email address, eg 'George Washington ".
|
||||
"<gwashinton@example.com>' when DX is a git or mercurial ".
|
||||
"revision. If this option is false, Differential defaults to the ".
|
||||
"best it can, something like 'George Washington' or ".
|
||||
"'gwashington'.")),
|
||||
$this->newOption('differential.generated-paths', 'list<string>', array())
|
||||
->setSummary(pht("File regexps to treat as automatically generated."))
|
||||
->setDescription(
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
final class DifferentialPeopleMenuEventListener extends PhutilEventListener {
|
||||
|
||||
public function register() {
|
||||
$this->listen(PhabricatorEventType::TYPE_PEOPLE_DIDRENDERMENU);
|
||||
}
|
||||
|
||||
public function handleEvent(PhutilEvent $event) {
|
||||
switch ($event->getType()) {
|
||||
case PhabricatorEventType::TYPE_PEOPLE_DIDRENDERMENU:
|
||||
$this->handleMenuEvent($event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function handleMenuEvent($event) {
|
||||
$viewer = $event->getUser();
|
||||
$menu = $event->getValue('menu');
|
||||
$person = $event->getValue('person');
|
||||
$username = phutil_escape_uri($person->getUserName());
|
||||
|
||||
$href = '/differential/filter/revisions/'.$username.'/';
|
||||
$name = pht('Revisions');
|
||||
|
||||
$menu->addMenuItemToLabel('activity',
|
||||
id(new PhabricatorMenuItemView())
|
||||
->setIsExternal(true)
|
||||
->setHref($href)
|
||||
->setName($name)
|
||||
->setKey($name)
|
||||
);
|
||||
|
||||
$event->setValue('menu', $menu);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -240,55 +240,17 @@ final class DifferentialDiff extends DifferentialDAO {
|
|||
$this->getID());
|
||||
foreach ($properties as $property) {
|
||||
$dict['properties'][$property->getName()] = $property->getData();
|
||||
|
||||
if ($property->getName() == 'local:commits') {
|
||||
foreach ($property->getData() as $commit) {
|
||||
$dict['authorName'] = $commit['author'];
|
||||
$dict['authorEmail'] = $commit['authorEmail'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $dict;
|
||||
}
|
||||
|
||||
/**
|
||||
* Figures out the right author information for a given diff based on the
|
||||
* repository and Phabricator configuration settings.
|
||||
*
|
||||
* Git is particularly finicky as it requires author information to be in
|
||||
* the format "George Washington <gwashington@example.com>" to
|
||||
* consistently work. If the Phabricator instance isn't configured to
|
||||
* expose emails prudently, then we are unable to get any author information
|
||||
* for git.
|
||||
*/
|
||||
public function loadAuthorInformation() {
|
||||
$author = id(new PhabricatorUser())
|
||||
->loadOneWhere('phid = %s', $this->getAuthorPHID());
|
||||
|
||||
$use_emails =
|
||||
PhabricatorEnv::getEnvConfig('differential.expose-emails-prudently');
|
||||
|
||||
switch ($this->getSourceControlSystem()) {
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
|
||||
if (!$use_emails) {
|
||||
$author_info = '';
|
||||
} else {
|
||||
$author_info = $this->getFullAuthorInfo($author);
|
||||
}
|
||||
break;
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
|
||||
if (!$use_emails) {
|
||||
$author_info = $author->getUsername();
|
||||
} else {
|
||||
$author_info = $this->getFullAuthorInfo($author);
|
||||
}
|
||||
break;
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
|
||||
default:
|
||||
$author_info = $author->getUsername();
|
||||
break;
|
||||
}
|
||||
|
||||
return $author_info;
|
||||
}
|
||||
|
||||
private function getFullAuthorInfo(PhabricatorUser $author) {
|
||||
return sprintf('%s <%s>',
|
||||
$author->getRealName(),
|
||||
$author->loadPrimaryEmailAddress());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,12 @@ final class PhabricatorApplicationDiffusion extends PhabricatorApplication {
|
|||
);
|
||||
}
|
||||
|
||||
public function getEventListeners() {
|
||||
return array(
|
||||
new DiffusionPeopleMenuEventListener()
|
||||
);
|
||||
}
|
||||
|
||||
public function getRoutes() {
|
||||
return array(
|
||||
'/r(?P<callsign>[A-Z]+)(?P<commit>[a-z0-9]+)'
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
final class DiffusionPeopleMenuEventListener extends PhutilEventListener {
|
||||
|
||||
public function register() {
|
||||
$this->listen(PhabricatorEventType::TYPE_PEOPLE_DIDRENDERMENU);
|
||||
}
|
||||
|
||||
public function handleEvent(PhutilEvent $event) {
|
||||
switch ($event->getType()) {
|
||||
case PhabricatorEventType::TYPE_PEOPLE_DIDRENDERMENU:
|
||||
$this->handleMenuEvent($event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function handleMenuEvent($event) {
|
||||
$viewer = $event->getUser();
|
||||
$menu = $event->getValue('menu');
|
||||
$person_phid = $event->getValue('person')->getPHID();
|
||||
|
||||
$href = '/diffusion/lint/?owner[0]='.$person_phid;
|
||||
$name = pht('Lint Messages');
|
||||
|
||||
$menu->addMenuItemToLabel('activity',
|
||||
id(new PhabricatorMenuItemView())
|
||||
->setIsExternal(true)
|
||||
->setHref($href)
|
||||
->setName($name)
|
||||
->setKey($name)
|
||||
);
|
||||
|
||||
$event->setValue('menu', $menu);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -56,6 +56,29 @@ final class PhabricatorImageTransformer {
|
|||
));
|
||||
}
|
||||
|
||||
public function executeConpherenceTransform(
|
||||
PhabricatorFile $file,
|
||||
$top,
|
||||
$left,
|
||||
$width,
|
||||
$height
|
||||
) {
|
||||
|
||||
$image = $this->crasslyCropTo(
|
||||
$file,
|
||||
$top,
|
||||
$left,
|
||||
$width,
|
||||
$height
|
||||
);
|
||||
|
||||
return PhabricatorFile::newFromFileData(
|
||||
$image,
|
||||
array(
|
||||
'name' => 'conpherence-'.$file->getName(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private function crudelyCropTo(PhabricatorFile $file, $x, $min_y, $max_y) {
|
||||
$data = $file->loadFileData();
|
||||
|
@ -80,6 +103,30 @@ final class PhabricatorImageTransformer {
|
|||
return $this->saveImageDataInAnyFormat($img, $file->getMimeType());
|
||||
}
|
||||
|
||||
private function crasslyCropTo(PhabricatorFile $file, $top, $left, $w, $h) {
|
||||
$data = $file->loadFileData();
|
||||
$src = imagecreatefromstring($data);
|
||||
$dst = $this->getBlankDestinationFile($w, $h);
|
||||
|
||||
$scale = self::getScaleForCrop($file, $w, $h);
|
||||
$orig_x = $left / $scale;
|
||||
$orig_y = $top / $scale;
|
||||
$orig_w = $w / $scale;
|
||||
$orig_h = $h / $scale;
|
||||
|
||||
imagecopyresampled(
|
||||
$dst,
|
||||
$src,
|
||||
0, 0,
|
||||
$orig_x, $orig_y,
|
||||
$w, $h,
|
||||
$orig_w, $orig_h
|
||||
);
|
||||
|
||||
return $this->saveImageDataInAnyFormat($dst, $file->getMimeType());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Very crudely scale an image up or down to an exact size.
|
||||
*/
|
||||
|
@ -92,15 +139,21 @@ final class PhabricatorImageTransformer {
|
|||
return $this->saveImageDataInAnyFormat($dst, $file->getMimeType());
|
||||
}
|
||||
|
||||
private function getBlankDestinationFile($dx, $dy) {
|
||||
$dst = imagecreatetruecolor($dx, $dy);
|
||||
imagesavealpha($dst, true);
|
||||
imagefill($dst, 0, 0, imagecolorallocatealpha($dst, 255, 255, 255, 127));
|
||||
|
||||
return $dst;
|
||||
}
|
||||
|
||||
private function applyScaleTo($src, $dx, $dy) {
|
||||
$x = imagesx($src);
|
||||
$y = imagesy($src);
|
||||
|
||||
$scale = min(($dx / $x), ($dy / $y), 1);
|
||||
|
||||
$dst = imagecreatetruecolor($dx, $dy);
|
||||
imagesavealpha($dst, true);
|
||||
imagefill($dst, 0, 0, imagecolorallocatealpha($dst, 255, 255, 255, 127));
|
||||
$dst = $this->getBlankDestinationFile($dx, $dy);
|
||||
|
||||
$sdx = $scale * $x;
|
||||
$sdy = $scale * $y;
|
||||
|
@ -141,6 +194,27 @@ final class PhabricatorImageTransformer {
|
|||
);
|
||||
}
|
||||
|
||||
public static function getScaleForCrop(
|
||||
PhabricatorFile $file,
|
||||
$des_width,
|
||||
$des_height) {
|
||||
|
||||
$metadata = $file->getMetadata();
|
||||
$width = $metadata[PhabricatorFile::METADATA_IMAGE_WIDTH];
|
||||
$height = $metadata[PhabricatorFile::METADATA_IMAGE_HEIGHT];
|
||||
|
||||
if ($height < $des_height) {
|
||||
$scale = $height / $des_height;
|
||||
} else if ($width < $des_width) {
|
||||
$scale = $width / $des_width;
|
||||
} else {
|
||||
$scale_x = $des_width / $width;
|
||||
$scale_y = $des_height / $height;
|
||||
$scale = max($scale_x, $scale_y);
|
||||
}
|
||||
|
||||
return $scale;
|
||||
}
|
||||
private function generatePreview(PhabricatorFile $file, $size) {
|
||||
$data = $file->loadFileData();
|
||||
$src = imagecreatefromstring($data);
|
||||
|
@ -153,9 +227,7 @@ final class PhabricatorImageTransformer {
|
|||
$sdx = $dimensions['sdx'];
|
||||
$sdy = $dimensions['sdy'];
|
||||
|
||||
$dst = imagecreatetruecolor($dx, $dy);
|
||||
imagesavealpha($dst, true);
|
||||
imagefill($dst, 0, 0, imagecolorallocatealpha($dst, 255, 255, 255, 127));
|
||||
$dst = $this->getBlankDestinationFile($dx, $dy);
|
||||
|
||||
imagecopyresampled(
|
||||
$dst,
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Test storage engine. Does not actually store files. Used for unit tests.
|
||||
*
|
||||
* @group filestorage
|
||||
*/
|
||||
final class PhabricatorTestStorageEngine
|
||||
extends PhabricatorFileStorageEngine {
|
||||
|
||||
private static $storage = array();
|
||||
private static $nextHandle = 1;
|
||||
|
||||
public function getEngineIdentifier() {
|
||||
return 'unit-test';
|
||||
}
|
||||
|
||||
public function writeFile($data, array $params) {
|
||||
AphrontWriteGuard::willWrite();
|
||||
self::$storage[self::$nextHandle] = $data;
|
||||
return self::$nextHandle++;
|
||||
}
|
||||
|
||||
public function readFile($handle) {
|
||||
if (isset(self::$storage[$handle])) {
|
||||
return self::$storage[$handle];
|
||||
}
|
||||
throw new Exception("No such file with handle '{$handle}'!");
|
||||
}
|
||||
|
||||
public function deleteFile($handle) {
|
||||
AphrontWriteGuard::willWrite();
|
||||
unset(self::$storage[$handle]);
|
||||
}
|
||||
|
||||
}
|
|
@ -134,9 +134,16 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
|
||||
|
||||
public static function newFromFileData($data, array $params = array()) {
|
||||
$selector = PhabricatorEnv::newObjectFromConfig('storage.engine-selector');
|
||||
|
||||
$engines = $selector->selectStorageEngines($data, $params);
|
||||
if (isset($params['storageEngines'])) {
|
||||
$engines = $params['storageEngines'];
|
||||
} else {
|
||||
$selector = PhabricatorEnv::newObjectFromConfig(
|
||||
'storage.engine-selector');
|
||||
$engines = $selector->selectStorageEngines($data, $params);
|
||||
}
|
||||
|
||||
assert_instances_of($engines, 'PhabricatorFileStorageEngine');
|
||||
if (!$engines) {
|
||||
throw new Exception("No valid storage engines are available!");
|
||||
}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorFileTestCase extends PhabricatorTestCase {
|
||||
|
||||
public function getPhabricatorTestCaseConfiguration() {
|
||||
return array(
|
||||
self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true,
|
||||
);
|
||||
}
|
||||
|
||||
public function testFileStorageReadWrite() {
|
||||
$engine = new PhabricatorTestStorageEngine();
|
||||
|
||||
$data = Filesystem::readRandomCharacters(64);
|
||||
|
||||
$params = array(
|
||||
'name' => 'test.dat',
|
||||
'storageEngines' => array(
|
||||
$engine,
|
||||
),
|
||||
);
|
||||
|
||||
$file = PhabricatorFile::newFromFileData($data, $params);
|
||||
|
||||
// Test that the storage engine worked, and was the target of the write. We
|
||||
// don't actually care what the data is (future changes may compress or
|
||||
// encrypt it), just that it exists in the test storage engine.
|
||||
$engine->readFile($file->getStorageHandle());
|
||||
|
||||
// Now test that we get the same data back out.
|
||||
$this->assertEqual($data, $file->loadFileData());
|
||||
}
|
||||
|
||||
|
||||
public function testFileStorageDelete() {
|
||||
$engine = new PhabricatorTestStorageEngine();
|
||||
|
||||
$data = Filesystem::readRandomCharacters(64);
|
||||
|
||||
$params = array(
|
||||
'name' => 'test.dat',
|
||||
'storageEngines' => array(
|
||||
$engine,
|
||||
),
|
||||
);
|
||||
|
||||
$file = PhabricatorFile::newFromFileData($data, $params);
|
||||
$handle = $file->getStorageHandle();
|
||||
$file->delete();
|
||||
|
||||
$caught = null;
|
||||
try {
|
||||
$engine->readFile($handle);
|
||||
} catch (Exception $ex) {
|
||||
$caught = $ex;
|
||||
}
|
||||
|
||||
$this->assertEqual(true, $caught instanceof Exception);
|
||||
}
|
||||
|
||||
}
|
|
@ -36,6 +36,12 @@ final class PhabricatorApplicationManiphest extends PhabricatorApplication {
|
|||
return $this->getBaseURI().'task/create/';
|
||||
}
|
||||
|
||||
public function getEventListeners() {
|
||||
return array(
|
||||
new ManiphestPeopleMenuEventListener()
|
||||
);
|
||||
}
|
||||
|
||||
public function getRoutes() {
|
||||
return array(
|
||||
'/T(?P<id>[1-9]\d*)' => 'ManiphestTaskDetailController',
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
final class ManiphestPeopleMenuEventListener extends PhutilEventListener {
|
||||
|
||||
public function register() {
|
||||
$this->listen(PhabricatorEventType::TYPE_PEOPLE_DIDRENDERMENU);
|
||||
}
|
||||
|
||||
public function handleEvent(PhutilEvent $event) {
|
||||
switch ($event->getType()) {
|
||||
case PhabricatorEventType::TYPE_PEOPLE_DIDRENDERMENU:
|
||||
$this->handleMenuEvent($event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function handleMenuEvent($event) {
|
||||
$viewer = $event->getUser();
|
||||
$menu = $event->getValue('menu');
|
||||
$person_phid = $event->getValue('person')->getPHID();
|
||||
|
||||
$href = '/maniphest/view/action/?users='.$person_phid;
|
||||
$name = pht('Tasks');
|
||||
|
||||
$menu->addMenuItemToLabel('activity',
|
||||
id(new PhabricatorMenuItemView())
|
||||
->setIsExternal(true)
|
||||
->setHref($href)
|
||||
->setName($name)
|
||||
->setKey($name)
|
||||
);
|
||||
|
||||
$event->setValue('menu', $menu);
|
||||
}
|
||||
|
||||
}
|
|
@ -15,7 +15,7 @@ extends PhabricatorAuthController {
|
|||
$current_user = $request->getUser();
|
||||
$server = new PhabricatorOAuthServer();
|
||||
$client_phid = $request->getStr('client_id');
|
||||
$scope = $request->getStr('scope');
|
||||
$scope = $request->getStr('scope', array());
|
||||
$redirect_uri = $request->getStr('redirect_uri');
|
||||
$state = $request->getStr('state');
|
||||
$response_type = $request->getStr('response_type');
|
||||
|
@ -63,10 +63,8 @@ extends PhabricatorAuthController {
|
|||
return $response;
|
||||
}
|
||||
$uri = $redirect_uri;
|
||||
$access_token_uri = $uri;
|
||||
} else {
|
||||
$uri = new PhutilURI($client->getRedirectURI());
|
||||
$access_token_uri = null;
|
||||
}
|
||||
// we've now validated this request enough overall such that we
|
||||
// can safely redirect to the client with the response
|
||||
|
@ -121,7 +119,7 @@ extends PhabricatorAuthController {
|
|||
if ($return_auth_code) {
|
||||
// step 1 -- generate authorization code
|
||||
$auth_code =
|
||||
$server->generateAuthorizationCode($access_token_uri);
|
||||
$server->generateAuthorizationCode($uri);
|
||||
|
||||
// step 2 return it
|
||||
$content = array(
|
||||
|
|
|
@ -16,6 +16,21 @@ final class PhabricatorPeopleProfileController
|
|||
return $this->profileUser;
|
||||
}
|
||||
|
||||
private function getMainFilters($username) {
|
||||
return array(
|
||||
array(
|
||||
'key' => 'feed',
|
||||
'name' => pht('Feed'),
|
||||
'href' => '/p/'.$username.'/feed/'
|
||||
),
|
||||
array(
|
||||
'key' => 'about',
|
||||
'name' => pht('About'),
|
||||
'href' => '/p/'.$username.'/about/'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
|
||||
$viewer = $this->getRequest()->getUser();
|
||||
|
@ -39,40 +54,14 @@ final class PhabricatorPeopleProfileController
|
|||
}
|
||||
$username = phutil_escape_uri($user->getUserName());
|
||||
|
||||
$external_arrow = "\xE2\x86\x97";
|
||||
$menu = new PhabricatorMenuView();
|
||||
foreach ($this->getMainFilters($username) as $filter) {
|
||||
$menu->newLink($filter['name'], $filter['href'], $filter['key']);
|
||||
}
|
||||
|
||||
$conpherence_uri =
|
||||
new PhutilURI('/conpherence/new/?participant='.$user->getPHID());
|
||||
$nav = new AphrontSideNavFilterView();
|
||||
$nav->setBaseURI(new PhutilURI('/p/'.$username.'/'));
|
||||
$nav->addFilter('feed', 'Feed');
|
||||
$nav->addMenuItem(
|
||||
id(new PhabricatorMenuItemView())
|
||||
->setName(pht('Conpherence').' '.$external_arrow)
|
||||
->setHref($conpherence_uri)
|
||||
);
|
||||
$nav->addFilter('about', 'About');
|
||||
$nav->addLabel('Activity');
|
||||
|
||||
$nav->addFilter(
|
||||
null,
|
||||
"Revisions {$external_arrow}",
|
||||
'/differential/filter/revisions/'.$username.'/');
|
||||
|
||||
$nav->addFilter(
|
||||
null,
|
||||
"Tasks {$external_arrow}",
|
||||
'/maniphest/view/action/?users='.$user->getPHID());
|
||||
|
||||
$nav->addFilter(
|
||||
null,
|
||||
"Commits {$external_arrow}",
|
||||
'/audit/view/author/'.$username.'/');
|
||||
|
||||
$nav->addFilter(
|
||||
null,
|
||||
"Lint Messages {$external_arrow}",
|
||||
'/diffusion/lint/?owner[0]='.$user->getPHID());
|
||||
$menu->newLabel(pht('Activity'), 'activity');
|
||||
// NOTE: applications install the various links through PhabricatorEvent
|
||||
// listeners
|
||||
|
||||
$oauths = id(new PhabricatorUserOAuthInfo())->loadAllWhere(
|
||||
'userID = %d',
|
||||
|
@ -92,18 +81,34 @@ final class PhabricatorPeopleProfileController
|
|||
continue;
|
||||
}
|
||||
|
||||
$name = $provider->getProviderName().' Profile';
|
||||
$name = pht('%s Profile', $provider->getProviderName());
|
||||
$href = $oauths[$provider_key]->getAccountURI();
|
||||
|
||||
if ($href) {
|
||||
if (!$added_label) {
|
||||
$nav->addLabel('Linked Accounts');
|
||||
$menu->newLabel(pht('Linked Accounts'), 'linked_accounts');
|
||||
$added_label = true;
|
||||
}
|
||||
$nav->addFilter(null, $name.' '.$external_arrow, $href);
|
||||
$menu->addMenuItem(
|
||||
id(new PhabricatorMenuItemView())
|
||||
->setIsExternal(true)
|
||||
->setName($name)
|
||||
->setHref($href)
|
||||
->setType(PhabricatorMenuItemView::TYPE_LINK)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$event = new PhabricatorEvent(
|
||||
PhabricatorEventType::TYPE_PEOPLE_DIDRENDERMENU,
|
||||
array(
|
||||
'menu' => $menu,
|
||||
'person' => $user,
|
||||
));
|
||||
$event->setUser($viewer);
|
||||
PhutilEventEngine::dispatchEvent($event);
|
||||
$nav = AphrontSideNavFilterView::newFromMenu($event->getValue('menu'));
|
||||
|
||||
$this->page = $nav->selectFilter($this->page, 'feed');
|
||||
|
||||
switch ($this->page) {
|
||||
|
@ -141,14 +146,19 @@ final class PhabricatorPeopleProfileController
|
|||
$header->appendChild($content);
|
||||
|
||||
if ($user->getPHID() == $viewer->getPHID()) {
|
||||
$nav->addFilter(null, 'Edit Profile...', '/settings/panel/profile/');
|
||||
$nav->addFilter(
|
||||
null,
|
||||
pht('Edit Profile...'),
|
||||
'/settings/panel/profile/'
|
||||
);
|
||||
}
|
||||
|
||||
if ($viewer->getIsAdmin()) {
|
||||
$nav->addFilter(
|
||||
null,
|
||||
'Administrate User...',
|
||||
'/people/edit/'.$user->getID().'/');
|
||||
pht('Administrate User...'),
|
||||
'/people/edit/'.$user->getID().'/'
|
||||
);
|
||||
}
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
|
@ -162,7 +172,10 @@ final class PhabricatorPeopleProfileController
|
|||
|
||||
$blurb = nonempty(
|
||||
$profile->getBlurb(),
|
||||
'//Nothing is known about this rare specimen.//');
|
||||
'//'.
|
||||
pht('Nothing is known about this rare specimen.')
|
||||
.'//'
|
||||
);
|
||||
|
||||
$engine = PhabricatorMarkupEngine::newProfileMarkupEngine();
|
||||
$blurb = phutil_safe_html($engine->markupText($blurb));
|
||||
|
|
|
@ -413,9 +413,6 @@ final class PhabricatorObjectHandleData {
|
|||
$handle->setName($file->getName());
|
||||
$handle->setURI($file->getBestURI());
|
||||
$handle->setComplete(true);
|
||||
if ($file->isViewableImage()) {
|
||||
$handle->setImageURI($file->getBestURI());
|
||||
}
|
||||
}
|
||||
$handles[$phid] = $handle;
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ final class PhabricatorApplicationPholio extends PhabricatorApplication {
|
|||
'new/' => 'PholioMockEditController',
|
||||
'edit/(?P<id>\d+)/' => 'PholioMockEditController',
|
||||
'comment/(?P<id>\d+)/' => 'PholioMockCommentController',
|
||||
'inline/(?P<id>\d+)/' => 'PholioInlineSaveController',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,5 +4,6 @@ final class PholioTransactionType extends PholioConstants {
|
|||
|
||||
const TYPE_NAME = 'name';
|
||||
const TYPE_DESCRIPTION = 'description';
|
||||
const TYPE_INLINE = 'inline';
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @group pholio
|
||||
*/
|
||||
final class PholioInlineSaveController extends PholioController {
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$user = $request->getUser();
|
||||
|
||||
$mock = id(new PholioMockQuery())
|
||||
->setViewer($user)
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->withIDs(array($request->getInt('mockID')))
|
||||
->executeOne();
|
||||
|
||||
if (!$mock) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$draft = id(new PholioTransactionComment());
|
||||
$draft->setImageID($request->getInt('imageID'));
|
||||
$draft->setX($request->getInt('startX'));
|
||||
$draft->setY($request->getInt('startY'));
|
||||
|
||||
$draft->setCommentVersion(1);
|
||||
$draft->setAuthorPHID($user->getPHID());
|
||||
$draft->setEditPolicy($user->getPHID());
|
||||
$draft->setViewPolicy(PhabricatorPolicies::POLICY_PUBLIC);
|
||||
|
||||
$content_source = PhabricatorContentSource::newForSource(
|
||||
PhabricatorContentSource::SOURCE_WEB,
|
||||
array(
|
||||
'ip' => $request->getRemoteAddr(),
|
||||
));
|
||||
|
||||
$draft->setContentSource($content_source);
|
||||
|
||||
$draft->setWidth($request->getInt('endX') - $request->getInt('startX'));
|
||||
$draft->setHeight($request->getInt('endY') - $request->getInt('startY'));
|
||||
|
||||
$draft->setContent($request->getStr('comment'));
|
||||
|
||||
$draft->save();
|
||||
|
||||
return id(new AphrontAjaxResponse())->setContent(array());
|
||||
}
|
||||
|
||||
}
|
|
@ -22,6 +22,7 @@ final class PholioMockCommentController extends PholioController {
|
|||
$mock = id(new PholioMockQuery())
|
||||
->setViewer($user)
|
||||
->withIDs(array($this->id))
|
||||
->needImages(true)
|
||||
->executeOne();
|
||||
|
||||
if (!$mock) {
|
||||
|
@ -49,6 +50,18 @@ final class PholioMockCommentController extends PholioController {
|
|||
id(new PholioTransactionComment())
|
||||
->setContent($comment));
|
||||
|
||||
$inlineComments = id(new PholioTransactionComment())->loadAllWhere(
|
||||
'authorphid = %s AND transactionphid IS NULL AND imageid IN (%Ld)',
|
||||
$user->getPHID(),
|
||||
mpull($mock->getImages(), 'getID')
|
||||
);
|
||||
|
||||
foreach ($inlineComments as $inlineComment) {
|
||||
$xactions[] = id(new PholioTransaction())
|
||||
->setTransactionType(PholioTransactionType::TYPE_INLINE)
|
||||
->attachComment($inlineComment);
|
||||
}
|
||||
|
||||
$editor = id(new PholioMockEditor())
|
||||
->setActor($user)
|
||||
->setContentSource($content_source)
|
||||
|
|
|
@ -14,6 +14,7 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor {
|
|||
|
||||
$types[] = PholioTransactionType::TYPE_NAME;
|
||||
$types[] = PholioTransactionType::TYPE_DESCRIPTION;
|
||||
$types[] = PholioTransactionType::TYPE_INLINE;
|
||||
return $types;
|
||||
}
|
||||
|
||||
|
@ -40,6 +41,18 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor {
|
|||
}
|
||||
}
|
||||
|
||||
protected function transactionHasEffect(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PholioTransactionType::TYPE_INLINE:
|
||||
return true;
|
||||
}
|
||||
|
||||
return parent::transactionHasEffect($object, $xaction);
|
||||
}
|
||||
|
||||
protected function applyCustomInternalTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
|
|
@ -15,7 +15,9 @@ final class PholioMockImagesView extends AphrontView {
|
|||
|
||||
$main_image_id = celerity_generate_unique_node_id();
|
||||
require_celerity_resource('javelin-behavior-pholio-mock-view');
|
||||
$config = array('mainID' => $main_image_id);
|
||||
$config = array(
|
||||
'mainID' => $main_image_id,
|
||||
'mockID' => $this->mock->getID());
|
||||
Javelin::initBehavior('pholio-mock-view', $config);
|
||||
|
||||
$mockview = "";
|
||||
|
|
|
@ -75,9 +75,12 @@ final class PhabricatorApplicationTransactionCommentEditor
|
|||
"Transaction must have a PHID before calling applyEdit()!");
|
||||
}
|
||||
|
||||
if ($comment->getPHID()) {
|
||||
throw new Exception(
|
||||
$type_comment = PhabricatorTransactions::TYPE_COMMENT;
|
||||
if ($xaction->getTransactionType() == $type_comment) {
|
||||
if ($comment->getPHID()) {
|
||||
throw new Exception(
|
||||
"Transaction comment must not yet have a PHID!");
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->getContentSource()) {
|
||||
|
|
132
src/infrastructure/daemon/bot/PhabricatorBot.php
Normal file
132
src/infrastructure/daemon/bot/PhabricatorBot.php
Normal file
|
@ -0,0 +1,132 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Simple IRC bot which runs as a Phabricator daemon. Although this bot is
|
||||
* somewhat useful, it is also intended to serve as a demo of how to write
|
||||
* "system agents" which communicate with Phabricator over Conduit, so you can
|
||||
* script system interactions and integrate with other systems.
|
||||
*
|
||||
* NOTE: This is super janky and experimental right now.
|
||||
*
|
||||
* @group irc
|
||||
*/
|
||||
final class PhabricatorBot extends PhabricatorDaemon {
|
||||
|
||||
private $handlers;
|
||||
|
||||
private $conduit;
|
||||
private $config;
|
||||
private $pollFrequency;
|
||||
|
||||
public function run() {
|
||||
$argv = $this->getArgv();
|
||||
if (count($argv) !== 1) {
|
||||
throw new Exception("usage: PhabricatorBot <json_config_file>");
|
||||
}
|
||||
|
||||
$json_raw = Filesystem::readFile($argv[0]);
|
||||
$config = json_decode($json_raw, true);
|
||||
if (!is_array($config)) {
|
||||
throw new Exception("File '{$argv[0]}' is not valid JSON!");
|
||||
}
|
||||
|
||||
$nick = idx($config, 'nick', 'phabot');
|
||||
$handlers = idx($config, 'handlers', array());
|
||||
$protocol_adapter_class = idx(
|
||||
$config,
|
||||
'protocol-adapter',
|
||||
'PhabricatorIRCProtocolAdapter');
|
||||
$this->pollFrequency = idx($config, 'poll-frequency', 1);
|
||||
|
||||
$this->config = $config;
|
||||
|
||||
foreach ($handlers as $handler) {
|
||||
$obj = newv($handler, array($this));
|
||||
$this->handlers[] = $obj;
|
||||
}
|
||||
|
||||
$conduit_uri = idx($config, 'conduit.uri');
|
||||
if ($conduit_uri) {
|
||||
$conduit_user = idx($config, 'conduit.user');
|
||||
$conduit_cert = idx($config, 'conduit.cert');
|
||||
|
||||
// Normalize the path component of the URI so users can enter the
|
||||
// domain without the "/api/" part.
|
||||
$conduit_uri = new PhutilURI($conduit_uri);
|
||||
$conduit_uri->setPath('/api/');
|
||||
$conduit_uri = (string)$conduit_uri;
|
||||
|
||||
$conduit = new ConduitClient($conduit_uri);
|
||||
$response = $conduit->callMethodSynchronous(
|
||||
'conduit.connect',
|
||||
array(
|
||||
'client' => 'PhabricatorBot',
|
||||
'clientVersion' => '1.0',
|
||||
'clientDescription' => php_uname('n').':'.$nick,
|
||||
'user' => $conduit_user,
|
||||
'certificate' => $conduit_cert,
|
||||
));
|
||||
|
||||
$this->conduit = $conduit;
|
||||
}
|
||||
|
||||
// Instantiate Protocol Adapter, for now follow same technique as
|
||||
// handler instantiation
|
||||
$this->protocolAdapter = newv($protocol_adapter_class, array());
|
||||
$this->protocolAdapter
|
||||
->setConfig($this->config)
|
||||
->connect();
|
||||
|
||||
$this->runLoop();
|
||||
}
|
||||
|
||||
public function getConfig($key, $default = null) {
|
||||
return idx($this->config, $key, $default);
|
||||
}
|
||||
|
||||
private function runLoop() {
|
||||
do {
|
||||
$this->stillWorking();
|
||||
|
||||
$messages = $this->protocolAdapter->getNextMessages($this->pollFrequency);
|
||||
if (count($messages) > 0) {
|
||||
foreach ($messages as $message) {
|
||||
$this->routeMessage($message);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->handlers as $handler) {
|
||||
$handler->runBackgroundTasks();
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
public function writeMessage(PhabricatorBotMessage $message) {
|
||||
return $this->protocolAdapter->writeMessage($message);
|
||||
}
|
||||
|
||||
private function routeMessage(PhabricatorBotMessage $message) {
|
||||
$ignore = $this->getConfig('ignore');
|
||||
if ($ignore && in_array($message->getSenderNickName(), $ignore)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->handlers as $handler) {
|
||||
try {
|
||||
$handler->receiveMessage($message);
|
||||
} catch (Exception $ex) {
|
||||
phlog($ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getConduit() {
|
||||
if (empty($this->conduit)) {
|
||||
throw new Exception(
|
||||
"This bot is not configured with a Conduit uplink. Set 'conduit.uri', ".
|
||||
"'conduit.user' and 'conduit.cert' in the configuration to connect.");
|
||||
}
|
||||
return $this->conduit;
|
||||
}
|
||||
|
||||
}
|
68
src/infrastructure/daemon/bot/PhabricatorBotMessage.php
Normal file
68
src/infrastructure/daemon/bot/PhabricatorBotMessage.php
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorBotMessage {
|
||||
|
||||
private $sender;
|
||||
private $command;
|
||||
private $body;
|
||||
private $target;
|
||||
private $public;
|
||||
|
||||
public function __construct() {
|
||||
// By default messages are public
|
||||
$this->public = true;
|
||||
}
|
||||
|
||||
public function setSender($sender) {
|
||||
$this->sender = $sender;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSender() {
|
||||
return $this->sender;
|
||||
}
|
||||
|
||||
public function setCommand($command) {
|
||||
$this->command = $command;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCommand() {
|
||||
return $this->command;
|
||||
}
|
||||
|
||||
public function setBody($body) {
|
||||
$this->body = $body;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBody() {
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function setTarget($target) {
|
||||
$this->target = $target;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTarget() {
|
||||
return $this->target;
|
||||
}
|
||||
|
||||
public function isPublic() {
|
||||
return $this->public;
|
||||
}
|
||||
|
||||
public function setPublic($is_public) {
|
||||
$this->public = $is_public;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getReplyTo() {
|
||||
if ($this->public) {
|
||||
return $this->target;
|
||||
} else {
|
||||
return $this->sender;
|
||||
}
|
||||
}
|
||||
}
|
11
src/infrastructure/daemon/bot/PhabricatorIRCBot.php
Normal file
11
src/infrastructure/daemon/bot/PhabricatorIRCBot.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Placeholder to let people know that the bot has been renamed
|
||||
*/
|
||||
final class PhabricatorIRCBot extends PhabricatorDaemon {
|
||||
public function run() {
|
||||
throw new Exception(
|
||||
"This daemon has been deprecated, use `PhabricatorBot` instead.");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Defines the api for protocol adapters for @{class:PhabricatorBot}
|
||||
*/
|
||||
abstract class PhabricatorBaseProtocolAdapter {
|
||||
protected $config;
|
||||
|
||||
public function setConfig($config) {
|
||||
$this->config = $config;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs any connection logic necessary for the protocol
|
||||
*/
|
||||
abstract public function connect();
|
||||
|
||||
/**
|
||||
* This is the spout for messages coming in from the protocol.
|
||||
* This will be called in the main event loop of the bot daemon
|
||||
* So if if doesn't implement some sort of blocking timeout
|
||||
* (e.g. select-based socket polling), it should at least sleep
|
||||
* for some period of time in order to not overwhelm the processor.
|
||||
*
|
||||
* @param Int $poll_frequency The number of seconds between polls
|
||||
*/
|
||||
abstract public function getNextMessages($poll_frequency);
|
||||
|
||||
/**
|
||||
* This is the output mechanism for the protocol.
|
||||
*
|
||||
* @param PhabricatorBotMessage $message The message to write
|
||||
*/
|
||||
abstract public function writeMessage(PhabricatorBotMessage $message);
|
||||
}
|
|
@ -0,0 +1,201 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCampfireProtocolAdapter
|
||||
extends PhabricatorBaseProtocolAdapter {
|
||||
|
||||
private $readBuffers;
|
||||
private $authtoken;
|
||||
private $server;
|
||||
private $readHandles;
|
||||
private $multiHandle;
|
||||
private $active;
|
||||
private $rooms;
|
||||
|
||||
public function connect() {
|
||||
$this->server = idx($this->config, 'server');
|
||||
$this->authtoken = idx($this->config, 'authtoken');
|
||||
$ssl = idx($this->config, 'ssl', false);
|
||||
$this->rooms = idx($this->config, 'join');
|
||||
|
||||
// First, join the room
|
||||
if (!$this->rooms) {
|
||||
throw new Exception("Not configured to join any rooms!");
|
||||
}
|
||||
|
||||
$this->readBuffers = array();
|
||||
|
||||
// Set up our long poll in a curl multi request so we can
|
||||
// continue running while it executes in the background
|
||||
$this->multiHandle = curl_multi_init();
|
||||
$this->readHandles = array();
|
||||
|
||||
foreach ($this->rooms as $room_id) {
|
||||
$this->joinRoom($room_id);
|
||||
|
||||
// Set up the curl stream for reading
|
||||
$url = ($ssl) ? "https://" : "http://";
|
||||
$url .= "streaming.campfirenow.com/room/{$room_id}/live.json";
|
||||
$this->readHandle[$url] = curl_init();
|
||||
curl_setopt($this->readHandle[$url], CURLOPT_URL, $url);
|
||||
curl_setopt($this->readHandle[$url], CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($this->readHandle[$url], CURLOPT_FOLLOWLOCATION, 1);
|
||||
curl_setopt(
|
||||
$this->readHandle[$url],
|
||||
CURLOPT_USERPWD,
|
||||
$this->authtoken.':x');
|
||||
curl_setopt(
|
||||
$this->readHandle[$url],
|
||||
CURLOPT_HTTPHEADER,
|
||||
array("Content-type: application/json"));
|
||||
curl_setopt(
|
||||
$this->readHandle[$url],
|
||||
CURLOPT_WRITEFUNCTION,
|
||||
array($this, 'read'));
|
||||
curl_setopt($this->readHandle[$url], CURLOPT_BUFFERSIZE, 128);
|
||||
curl_setopt($this->readHandle[$url], CURLOPT_TIMEOUT, 0);
|
||||
|
||||
curl_multi_add_handle($this->multiHandle, $this->readHandle[$url]);
|
||||
|
||||
// Initialize read buffer
|
||||
$this->readBuffers[$url] = '';
|
||||
}
|
||||
|
||||
$this->active = null;
|
||||
$this->blockingMultiExec();
|
||||
}
|
||||
|
||||
// This is our callback for the background curl multi-request.
|
||||
// Puts the data read in on the readBuffer for processing.
|
||||
private function read($ch, $data) {
|
||||
$info = curl_getinfo($ch);
|
||||
$length = strlen($data);
|
||||
$this->readBuffers[$info['url']] .= $data;
|
||||
return $length;
|
||||
}
|
||||
|
||||
private function blockingMultiExec() {
|
||||
do {
|
||||
$status = curl_multi_exec($this->multiHandle, $this->active);
|
||||
} while ($status == CURLM_CALL_MULTI_PERFORM);
|
||||
|
||||
// Check for errors
|
||||
if ($status != CURLM_OK) {
|
||||
throw new Exception(
|
||||
"Phabricator Bot had a problem reading from campfire.");
|
||||
}
|
||||
}
|
||||
|
||||
public function getNextMessages($poll_frequency) {
|
||||
$messages = array();
|
||||
|
||||
if (!$this->active) {
|
||||
throw new Exception("Phabricator Bot stopped reading from campfire.");
|
||||
}
|
||||
|
||||
// Prod our http request
|
||||
curl_multi_select($this->multiHandle, $poll_frequency);
|
||||
$this->blockingMultiExec();
|
||||
|
||||
// Process anything waiting on the read buffer
|
||||
while ($m = $this->processReadBuffer()) {
|
||||
$messages[] = $m;
|
||||
}
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
private function processReadBuffer() {
|
||||
foreach ($this->readBuffers as $url => &$buffer) {
|
||||
$until = strpos($buffer, "}\r");
|
||||
if ($until == false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$message = substr($buffer, 0, $until + 1);
|
||||
$buffer = substr($buffer, $until + 2);
|
||||
|
||||
$m_obj = json_decode($message, true);
|
||||
|
||||
return id(new PhabricatorBotMessage())
|
||||
->setCommand('MESSAGE')
|
||||
->setTarget($m_obj['room_id'])
|
||||
->setBody($m_obj['body']);
|
||||
}
|
||||
|
||||
// If we're here, there's nothing to process
|
||||
return false;
|
||||
}
|
||||
|
||||
public function writeMessage(PhabricatorBotMessage $message) {
|
||||
switch ($message->getCommand()) {
|
||||
case 'MESSAGE':
|
||||
$this->speak(
|
||||
$message->getBody(),
|
||||
$message->getTarget());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function joinRoom($room_id) {
|
||||
$this->performPost("/room/{$room_id}/join.json");
|
||||
}
|
||||
|
||||
private function leaveRoom($room_id) {
|
||||
$this->performPost("/room/{$room_id}/leave.json");
|
||||
}
|
||||
|
||||
private function speak($message, $room_id) {
|
||||
$this->performPost(
|
||||
"/room/{$room_id}/speak.json",
|
||||
array(
|
||||
'message' => array(
|
||||
'type' => 'TextMessage',
|
||||
'body' => $message)));
|
||||
}
|
||||
|
||||
private function performPost($endpoint, $data = Null) {
|
||||
$url = $this->server.$endpoint;
|
||||
|
||||
$payload = json_encode($data);
|
||||
|
||||
// cURL init & config
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
|
||||
curl_setopt($ch, CURLOPT_USERPWD, $this->authtoken . ':x');
|
||||
curl_setopt(
|
||||
$ch,
|
||||
CURLOPT_HTTPHEADER,
|
||||
array("Content-type: application/json"));
|
||||
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
||||
$output = curl_exec($ch);
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
$output = trim($output);
|
||||
|
||||
if (strlen($output)) {
|
||||
return json_decode($output);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
if ($this->rooms) {
|
||||
foreach ($this->rooms as $room_id) {
|
||||
$this->leaveRoom($room_id);
|
||||
}
|
||||
}
|
||||
if ($this->readHandles) {
|
||||
foreach ($this->readHandles as $read_handle) {
|
||||
curl_multi_remove_handle($this->multiHandle, $read_handle);
|
||||
curl_close($read_handle);
|
||||
}
|
||||
}
|
||||
curl_multi_close($this->multiHandle);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,221 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorIRCProtocolAdapter
|
||||
extends PhabricatorBaseProtocolAdapter {
|
||||
|
||||
private $socket;
|
||||
|
||||
private $writeBuffer;
|
||||
private $readBuffer;
|
||||
|
||||
// Hash map of command translations
|
||||
public static $commandTranslations = array(
|
||||
'PRIVMSG' => 'MESSAGE');
|
||||
|
||||
public function connect() {
|
||||
$nick = idx($this->config, 'nick', 'phabot');
|
||||
$server = idx($this->config, 'server');
|
||||
$port = idx($this->config, 'port', 6667);
|
||||
$pass = idx($this->config, 'pass');
|
||||
$ssl = idx($this->config, 'ssl', false);
|
||||
$user = idx($this->config, 'user', $nick);
|
||||
|
||||
if (!preg_match('/^[A-Za-z0-9_`[{}^|\]\\-]+$/', $nick)) {
|
||||
throw new Exception(
|
||||
"Nickname '{$nick}' is invalid!");
|
||||
}
|
||||
|
||||
$errno = null;
|
||||
$error = null;
|
||||
if (!$ssl) {
|
||||
$socket = fsockopen($server, $port, $errno, $error);
|
||||
} else {
|
||||
$socket = fsockopen('ssl://'.$server, $port, $errno, $error);
|
||||
}
|
||||
if (!$socket) {
|
||||
throw new Exception("Failed to connect, #{$errno}: {$error}");
|
||||
}
|
||||
$ok = stream_set_blocking($socket, false);
|
||||
if (!$ok) {
|
||||
throw new Exception("Failed to set stream nonblocking.");
|
||||
}
|
||||
|
||||
$this->socket = $socket;
|
||||
$this->writeMessage(
|
||||
id(new PhabricatorBotMessage())
|
||||
->setCommand('USER')
|
||||
->setBody("{$user} 0 * :{$user}"));
|
||||
if ($pass) {
|
||||
$this->writeMessage(
|
||||
id(new PhabricatorBotMessage())
|
||||
->setCommand('PASS')
|
||||
->setBody("{$pass}"));
|
||||
}
|
||||
|
||||
$this->writeMessage(
|
||||
id(new PhabricatorBotMessage())
|
||||
->setCommand('NICK')
|
||||
->setBody("{$nick}"));
|
||||
}
|
||||
|
||||
public function getNextMessages($poll_frequency) {
|
||||
$messages = array();
|
||||
|
||||
$read = array($this->socket);
|
||||
if (strlen($this->writeBuffer)) {
|
||||
$write = array($this->socket);
|
||||
} else {
|
||||
$write = array();
|
||||
}
|
||||
$except = array();
|
||||
|
||||
$ok = @stream_select($read, $write, $except, $timeout_sec = 1);
|
||||
if ($ok === false) {
|
||||
throw new Exception(
|
||||
"socket_select() failed: ".socket_strerror(socket_last_error()));
|
||||
}
|
||||
|
||||
if ($read) {
|
||||
// Test for connection termination; in PHP, fread() off a nonblocking,
|
||||
// closed socket is empty string.
|
||||
if (feof($this->socket)) {
|
||||
// This indicates the connection was terminated on the other side,
|
||||
// just exit via exception and let the overseer restart us after a
|
||||
// delay so we can reconnect.
|
||||
throw new Exception("Remote host closed connection.");
|
||||
}
|
||||
do {
|
||||
$data = fread($this->socket, 4096);
|
||||
if ($data === false) {
|
||||
throw new Exception("fread() failed!");
|
||||
} else {
|
||||
$messages[] = id(new PhabricatorBotMessage())
|
||||
->setCommand("LOG")
|
||||
->setBody(">>> ".$data);
|
||||
$this->readBuffer .= $data;
|
||||
}
|
||||
} while (strlen($data));
|
||||
}
|
||||
|
||||
if ($write) {
|
||||
do {
|
||||
$len = fwrite($this->socket, $this->writeBuffer);
|
||||
if ($len === false) {
|
||||
throw new Exception("fwrite() failed!");
|
||||
} else {
|
||||
$messages[] = id(new PhabricatorBotMessage())
|
||||
->setCommand("LOG")
|
||||
->setBody(">>> ".substr($this->writeBuffer, 0, $len));
|
||||
$this->writeBuffer = substr($this->writeBuffer, $len);
|
||||
}
|
||||
} while (strlen($this->writeBuffer));
|
||||
}
|
||||
|
||||
while ($m = $this->processReadBuffer()) {
|
||||
$messages[] = $m;
|
||||
}
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
private function write($message) {
|
||||
$this->writeBuffer .= $message;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function writeMessage(PhabricatorBotMessage $message) {
|
||||
$irc_command = $this->getIRCCommand($message->getCommand());
|
||||
switch ($message->getCommand()) {
|
||||
case 'MESSAGE':
|
||||
$data = $irc_command.' '.
|
||||
$message->getTarget().' :'.
|
||||
$message->getBody()."\r\n";
|
||||
break;
|
||||
default:
|
||||
$data = $irc_command.' '.
|
||||
$message->getBody()."\r\n";
|
||||
break;
|
||||
}
|
||||
|
||||
return $this->write($data);
|
||||
}
|
||||
|
||||
private function processReadBuffer() {
|
||||
$until = strpos($this->readBuffer, "\r\n");
|
||||
if ($until === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$message = substr($this->readBuffer, 0, $until);
|
||||
$this->readBuffer = substr($this->readBuffer, $until + 2);
|
||||
|
||||
$pattern =
|
||||
'/^'.
|
||||
'(?::(?P<sender>(\S+?))(?:!\S*)? )?'. // This may not be present.
|
||||
'(?P<command>[A-Z0-9]+) '.
|
||||
'(?P<data>.*)'.
|
||||
'$/';
|
||||
|
||||
$matches = null;
|
||||
if (!preg_match($pattern, $message, $matches)) {
|
||||
throw new Exception("Unexpected message from server: {$message}");
|
||||
}
|
||||
|
||||
$command = $this->getBotCommand($matches['command']);
|
||||
list($target, $body) = $this->parseMessageData($command, $matches['data']);
|
||||
|
||||
$bot_message = id(new PhabricatorBotMessage())
|
||||
->setSender(idx($matches, 'sender'))
|
||||
->setCommand($command)
|
||||
->setTarget($target)
|
||||
->setBody($body);
|
||||
|
||||
if (!empty($target) && strncmp($target, '#', 1) !== 0) {
|
||||
$bot_message->setPublic(false);
|
||||
}
|
||||
|
||||
return $bot_message;
|
||||
}
|
||||
|
||||
private function getBotCommand($irc_command) {
|
||||
if (isset(self::$commandTranslations[$irc_command])) {
|
||||
return self::$commandTranslations[$irc_command];
|
||||
}
|
||||
|
||||
// We have no translation for this command, use as-is
|
||||
return $irc_command;
|
||||
}
|
||||
|
||||
private function getIRCCommand($original_bot_command) {
|
||||
foreach (self::$commandTranslations as $irc_command=>$bot_command) {
|
||||
if ($bot_command === $original_bot_command) {
|
||||
return $irc_command;
|
||||
}
|
||||
}
|
||||
|
||||
return $original_bot_command;
|
||||
}
|
||||
|
||||
private function parseMessageData($command, $data) {
|
||||
switch ($command) {
|
||||
case 'MESSAGE':
|
||||
$matches = null;
|
||||
if (preg_match('/^(\S+)\s+:?(.*)$/', $data, $matches)) {
|
||||
return array(
|
||||
$matches[1],
|
||||
rtrim($matches[2], "\r\n"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// By default we assume there is no target, only a body
|
||||
return array(
|
||||
null,
|
||||
$data);
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
$this->write("QUIT Goodbye.\r\n");
|
||||
fclose($this->socket);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Logs messages to stdout
|
||||
*/
|
||||
final class PhabricatorBotDebugLogHandler extends PhabricatorBotHandler {
|
||||
public function receiveMessage(PhabricatorBotMessage $message) {
|
||||
switch ($message->getCommand()) {
|
||||
case 'LOG':
|
||||
echo addcslashes(
|
||||
$message->getBody(),
|
||||
"\0..\37\177..\377");
|
||||
echo "\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,12 +3,12 @@
|
|||
/**
|
||||
* @group irc
|
||||
*/
|
||||
final class PhabricatorIRCDifferentialNotificationHandler
|
||||
extends PhabricatorIRCHandler {
|
||||
final class PhabricatorBotDifferentialNotificationHandler
|
||||
extends PhabricatorBotHandler {
|
||||
|
||||
private $skippedOldEvents;
|
||||
|
||||
public function receiveMessage(PhabricatorIRCMessage $message) {
|
||||
public function receiveMessage(PhabricatorBotMessage $message) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -39,11 +39,16 @@ final class PhabricatorIRCDifferentialNotificationHandler
|
|||
$verb = DifferentialAction::getActionPastTenseVerb($data['action']);
|
||||
|
||||
$actor_name = $handles[$actor_phid]->getName();
|
||||
$message = "{$actor_name} {$verb} revision D".$data['revision_id'].".";
|
||||
$message_body =
|
||||
"{$actor_name} {$verb} revision D".$data['revision_id'].".";
|
||||
|
||||
$channels = $this->getConfig('notification.channels', array());
|
||||
foreach ($channels as $channel) {
|
||||
$this->write('PRIVMSG', "{$channel} :{$message}");
|
||||
$this->writeMessage(
|
||||
id(new PhabricatorBotMessage())
|
||||
->setCommand('MESSAGE')
|
||||
->setTarget($channel)
|
||||
->setBody($message_body));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,8 +5,8 @@
|
|||
*
|
||||
* @group irc
|
||||
*/
|
||||
final class PhabricatorIRCFeedNotificationHandler
|
||||
extends PhabricatorIRCHandler {
|
||||
final class PhabricatorBotFeedNotificationHandler
|
||||
extends PhabricatorBotHandler {
|
||||
|
||||
private $startupDelay = 30;
|
||||
private $lastSeenChronoKey = 0;
|
||||
|
@ -82,7 +82,7 @@ final class PhabricatorIRCFeedNotificationHandler
|
|||
return false;
|
||||
}
|
||||
|
||||
public function receiveMessage(PhabricatorIRCMessage $message) {
|
||||
public function receiveMessage(PhabricatorBotMessage $message) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -150,7 +150,11 @@ final class PhabricatorIRCFeedNotificationHandler
|
|||
|
||||
$channels = $this->getConfig('join');
|
||||
foreach ($channels as $channel) {
|
||||
$this->write('PRIVMSG', "{$channel} :{$story['text']}");
|
||||
$this->writeMessage(
|
||||
id(new PhabricatorBotMessage())
|
||||
->setCommand('MESSAGE')
|
||||
->setTarget($channel)
|
||||
->setBody($story['text']));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Responds to IRC messages. You plug a bunch of these into a
|
||||
* @{class:PhabricatorBot} to give it special behavior.
|
||||
*
|
||||
* @group irc
|
||||
*/
|
||||
abstract class PhabricatorBotHandler {
|
||||
|
||||
private $bot;
|
||||
|
||||
final public function __construct(PhabricatorBot $irc_bot) {
|
||||
$this->bot = $irc_bot;
|
||||
}
|
||||
|
||||
final protected function writeMessage(PhabricatorBotMessage $message) {
|
||||
$this->bot->writeMessage($message);
|
||||
return $this;
|
||||
}
|
||||
|
||||
final protected function getConduit() {
|
||||
return $this->bot->getConduit();
|
||||
}
|
||||
|
||||
final protected function getConfig($key, $default = null) {
|
||||
return $this->bot->getConfig($key, $default);
|
||||
}
|
||||
|
||||
final protected function getURI($path) {
|
||||
$base_uri = new PhutilURI($this->bot->getConfig('conduit.uri'));
|
||||
$base_uri->setPath($path);
|
||||
return (string)$base_uri;
|
||||
}
|
||||
|
||||
abstract public function receiveMessage(PhabricatorBotMessage $message);
|
||||
|
||||
public function runBackgroundTasks() {
|
||||
return;
|
||||
}
|
||||
|
||||
public function replyTo($original_message, $body) {
|
||||
if ($original_message->getCommand() != 'MESSAGE') {
|
||||
throw new Exception(
|
||||
"Handler is trying to reply to something which is not a message!");
|
||||
}
|
||||
|
||||
$reply = id(new PhabricatorBotMessage())
|
||||
->setCommand('MESSAGE');
|
||||
|
||||
if ($original_message->isPublic()) {
|
||||
// This is a public target, like a chatroom. Send the response to the
|
||||
// chatroom.
|
||||
$reply->setTarget($original_message->getTarget());
|
||||
} else {
|
||||
// This is a private target, like a private message. Send the response
|
||||
// back to the sender (presumably, we are the target).
|
||||
$reply->setTarget($original_message->getSender())
|
||||
->setPublic(false);
|
||||
}
|
||||
|
||||
$reply->setBody($body);
|
||||
|
||||
return $this->writeMessage($reply);
|
||||
}
|
||||
}
|
|
@ -5,19 +5,19 @@
|
|||
*
|
||||
* @group irc
|
||||
*/
|
||||
final class PhabricatorIRCLogHandler extends PhabricatorIRCHandler {
|
||||
final class PhabricatorBotLogHandler extends PhabricatorBotHandler {
|
||||
|
||||
private $futures = array();
|
||||
|
||||
public function receiveMessage(PhabricatorIRCMessage $message) {
|
||||
public function receiveMessage(PhabricatorBotMessage $message) {
|
||||
|
||||
switch ($message->getCommand()) {
|
||||
case 'PRIVMSG':
|
||||
case 'MESSAGE':
|
||||
$reply_to = $message->getReplyTo();
|
||||
if (!$reply_to) {
|
||||
break;
|
||||
}
|
||||
if (!$this->isChannelName($reply_to)) {
|
||||
if (!$message->isPublic()) {
|
||||
// Don't log private messages, although maybe we should for debugging?
|
||||
break;
|
||||
}
|
||||
|
@ -27,8 +27,8 @@ final class PhabricatorIRCLogHandler extends PhabricatorIRCHandler {
|
|||
'channel' => $reply_to,
|
||||
'type' => 'mesg',
|
||||
'epoch' => time(),
|
||||
'author' => $message->getSenderNickname(),
|
||||
'message' => $message->getMessageText(),
|
||||
'author' => $message->getSender(),
|
||||
'message' => $message->getBody(),
|
||||
),
|
||||
);
|
||||
|
||||
|
@ -46,7 +46,7 @@ final class PhabricatorIRCLogHandler extends PhabricatorIRCHandler {
|
|||
|
||||
$tell = false;
|
||||
foreach ($prompts as $prompt) {
|
||||
if (preg_match($prompt, $message->getMessageText())) {
|
||||
if (preg_match($prompt, $message->getBody())) {
|
||||
$tell = true;
|
||||
break;
|
||||
}
|
||||
|
@ -55,7 +55,8 @@ final class PhabricatorIRCLogHandler extends PhabricatorIRCHandler {
|
|||
if ($tell) {
|
||||
$response = $this->getURI(
|
||||
'/chatlog/channel/'.phutil_escape_uri($reply_to).'/');
|
||||
$this->write('PRIVMSG', "{$reply_to} :{$response}");
|
||||
|
||||
$this->replyTo($message, $response);
|
||||
}
|
||||
|
||||
break;
|
|
@ -3,7 +3,7 @@
|
|||
/**
|
||||
* @group irc
|
||||
*/
|
||||
final class PhabricatorIRCMacroHandler extends PhabricatorIRCHandler {
|
||||
final class PhabricatorBotMacroHandler extends PhabricatorBotHandler {
|
||||
|
||||
private $macros;
|
||||
private $regexp;
|
||||
|
@ -40,44 +40,44 @@ final class PhabricatorIRCMacroHandler extends PhabricatorIRCHandler {
|
|||
return true;
|
||||
}
|
||||
|
||||
public function receiveMessage(PhabricatorIRCMessage $message) {
|
||||
public function receiveMessage(PhabricatorBotMessage $message) {
|
||||
if (!$this->init()) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($message->getCommand()) {
|
||||
case 'PRIVMSG':
|
||||
$reply_to = $message->getReplyTo();
|
||||
if (!$reply_to) {
|
||||
break;
|
||||
}
|
||||
|
||||
$message = $message->getMessageText();
|
||||
|
||||
$matches = null;
|
||||
if (!preg_match($this->regexp, $message, $matches)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$macro = $matches[1];
|
||||
|
||||
$ascii = idx($this->macros[$macro], 'ascii');
|
||||
if ($ascii === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$ascii) {
|
||||
$this->macros[$macro]['ascii'] = $this->rasterize(
|
||||
$this->macros[$macro],
|
||||
$this->getConfig('macro.size', 48),
|
||||
$this->getConfig('macro.aspect', 0.66));
|
||||
$ascii = $this->macros[$macro]['ascii'];
|
||||
}
|
||||
|
||||
foreach ($ascii as $line) {
|
||||
$this->buffer[$reply_to][] = $line;
|
||||
}
|
||||
case 'MESSAGE':
|
||||
$reply_to = $message->getReplyTo();
|
||||
if (!$reply_to) {
|
||||
break;
|
||||
}
|
||||
|
||||
$message_body = $message->getBody();
|
||||
|
||||
$matches = null;
|
||||
if (!preg_match($this->regexp, $message_body, $matches)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$macro = $matches[1];
|
||||
|
||||
$ascii = idx($this->macros[$macro], 'ascii');
|
||||
if ($ascii === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$ascii) {
|
||||
$this->macros[$macro]['ascii'] = $this->rasterize(
|
||||
$this->macros[$macro],
|
||||
$this->getConfig('macro.size', 48),
|
||||
$this->getConfig('macro.aspect', 0.66));
|
||||
$ascii = $this->macros[$macro]['ascii'];
|
||||
}
|
||||
|
||||
foreach ($ascii as $line) {
|
||||
$this->buffer[$reply_to][] = $line;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,7 +92,11 @@ final class PhabricatorIRCMacroHandler extends PhabricatorIRCHandler {
|
|||
continue;
|
||||
}
|
||||
foreach ($lines as $key => $line) {
|
||||
$this->write('PRIVMSG', "{$channel} :{$line}");
|
||||
$this->writeMessage(
|
||||
id(new PhabricatorBotMessage())
|
||||
->setCommand('MESSAGE')
|
||||
->setTarget($channel)
|
||||
->setBody($line));
|
||||
unset($this->buffer[$channel][$key]);
|
||||
break 2;
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Looks for Dxxxx, Txxxx and links to them.
|
||||
*
|
||||
* @group irc
|
||||
*/
|
||||
final class PhabricatorBotObjectNameHandler extends PhabricatorBotHandler {
|
||||
|
||||
/**
|
||||
* Map of PHIDs to the last mention of them (as an epoch timestamp); prevents
|
||||
* us from spamming chat when a single object is discussed.
|
||||
*/
|
||||
private $recentlyMentioned = array();
|
||||
|
||||
public function receiveMessage(PhabricatorBotMessage $original_message) {
|
||||
|
||||
switch ($original_message->getCommand()) {
|
||||
case 'MESSAGE':
|
||||
$message = $original_message->getBody();
|
||||
$matches = null;
|
||||
|
||||
$pattern =
|
||||
'@'.
|
||||
'(?<!/)(?:^|\b)'. // Negative lookbehind prevent matching "/D123".
|
||||
'(D|T|P|V|F)(\d+)'.
|
||||
'(?:\b|$)'.
|
||||
'@';
|
||||
|
||||
$revision_ids = array();
|
||||
$task_ids = array();
|
||||
$paste_ids = array();
|
||||
$commit_names = array();
|
||||
$vote_ids = array();
|
||||
$file_ids = array();
|
||||
|
||||
if (preg_match_all($pattern, $message, $matches, PREG_SET_ORDER)) {
|
||||
foreach ($matches as $match) {
|
||||
switch ($match[1]) {
|
||||
case 'D':
|
||||
$revision_ids[] = $match[2];
|
||||
break;
|
||||
case 'T':
|
||||
$task_ids[] = $match[2];
|
||||
break;
|
||||
case 'P':
|
||||
$paste_ids[] = $match[2];
|
||||
break;
|
||||
case 'V':
|
||||
$vote_ids[] = $match[2];
|
||||
break;
|
||||
case 'F':
|
||||
$file_ids[] = $match[2];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$pattern =
|
||||
'@'.
|
||||
'(?<!/)(?:^|\b)'.
|
||||
'(r[A-Z]+[0-9a-z]{1,40})'.
|
||||
'(?:\b|$)'.
|
||||
'@';
|
||||
if (preg_match_all($pattern, $message, $matches, PREG_SET_ORDER)) {
|
||||
foreach ($matches as $match) {
|
||||
$commit_names[] = $match[1];
|
||||
}
|
||||
}
|
||||
|
||||
$output = array();
|
||||
|
||||
if ($revision_ids) {
|
||||
$revisions = $this->getConduit()->callMethodSynchronous(
|
||||
'differential.query',
|
||||
array(
|
||||
'ids' => $revision_ids,
|
||||
));
|
||||
$revisions = array_select_keys(
|
||||
ipull($revisions, null, 'id'),
|
||||
$revision_ids
|
||||
);
|
||||
foreach ($revisions as $revision) {
|
||||
$output[$revision['phid']] =
|
||||
'D'.$revision['id'].' '.$revision['title'].' - '.
|
||||
$revision['uri'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($task_ids) {
|
||||
foreach ($task_ids as $task_id) {
|
||||
if ($task_id == 1000) {
|
||||
$output[1000] = 'T1000: A nanomorph mimetic poly-alloy'
|
||||
.'(liquid metal) assassin controlled by Skynet: '
|
||||
.'http://en.wikipedia.org/wiki/T-1000';
|
||||
continue;
|
||||
}
|
||||
$task = $this->getConduit()->callMethodSynchronous(
|
||||
'maniphest.info',
|
||||
array(
|
||||
'task_id' => $task_id,
|
||||
));
|
||||
$output[$task['phid']] = 'T'.$task['id'].': '.$task['title'].
|
||||
' (Priority: '.$task['priority'].') - '.$task['uri'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($vote_ids) {
|
||||
foreach ($vote_ids as $vote_id) {
|
||||
$vote = $this->getConduit()->callMethodSynchronous(
|
||||
'slowvote.info',
|
||||
array(
|
||||
'poll_id' => $vote_id,
|
||||
));
|
||||
$output[$vote['phid']] = 'V'.$vote['id'].': '.$vote['question'].
|
||||
' Come Vote '.$vote['uri'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($file_ids) {
|
||||
foreach ($file_ids as $file_id) {
|
||||
$file = $this->getConduit()->callMethodSynchronous(
|
||||
'file.info',
|
||||
array(
|
||||
'id' => $file_id,
|
||||
));
|
||||
$output[$file['phid']] = $file['objectName'].": ".$file['uri']." - ".
|
||||
$file['name'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($paste_ids) {
|
||||
foreach ($paste_ids as $paste_id) {
|
||||
$paste = $this->getConduit()->callMethodSynchronous(
|
||||
'paste.info',
|
||||
array(
|
||||
'paste_id' => $paste_id,
|
||||
));
|
||||
// Eventually I'd like to show the username of the paster as well,
|
||||
// however that will need something like a user.username_from_phid
|
||||
// since we (ideally) want to keep the bot to Conduit calls...and
|
||||
// not call to Phabricator-specific stuff (like actually loading
|
||||
// the User object and fetching his/her username.)
|
||||
$output[$paste['phid']] = 'P'.$paste['id'].': '.$paste['uri'].' - '.
|
||||
$paste['title'];
|
||||
|
||||
if ($paste['language']) {
|
||||
$output[$paste['phid']] .= ' ('.$paste['language'].')';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($commit_names) {
|
||||
$commits = $this->getConduit()->callMethodSynchronous(
|
||||
'diffusion.getcommits',
|
||||
array(
|
||||
'commits' => $commit_names,
|
||||
));
|
||||
foreach ($commits as $commit) {
|
||||
if (isset($commit['error'])) {
|
||||
continue;
|
||||
}
|
||||
$output[$commit['commitPHID']] = $commit['uri'];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($output as $phid => $description) {
|
||||
|
||||
// Don't mention the same object more than once every 10 minutes
|
||||
// in public channels, so we avoid spamming the chat over and over
|
||||
// again for discsussions of a specific revision, for example.
|
||||
|
||||
$reply_to = $original_message->getReplyTo();
|
||||
if (empty($this->recentlyMentioned[$reply_to])) {
|
||||
$this->recentlyMentioned[$reply_to] = array();
|
||||
}
|
||||
|
||||
$quiet_until = idx(
|
||||
$this->recentlyMentioned[$reply_to],
|
||||
$phid,
|
||||
0) + (60 * 10);
|
||||
|
||||
if (time() < $quiet_until) {
|
||||
// Remain quiet on this channel.
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->recentlyMentioned[$reply_to][$phid] = time();
|
||||
$this->replyTo($original_message, $description);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Watches for "where is <symbol>?"
|
||||
*
|
||||
* @group irc
|
||||
*/
|
||||
final class PhabricatorBotSymbolHandler extends PhabricatorBotHandler {
|
||||
|
||||
public function receiveMessage(PhabricatorBotMessage $message) {
|
||||
|
||||
switch ($message->getCommand()) {
|
||||
case 'MESSAGE':
|
||||
$text = $message->getBody();
|
||||
|
||||
$matches = null;
|
||||
if (!preg_match('/where(?: in the world)? is (\S+?)\?/i',
|
||||
$text, $matches)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$symbol = $matches[1];
|
||||
$results = $this->getConduit()->callMethodSynchronous(
|
||||
'diffusion.findsymbols',
|
||||
array(
|
||||
'name' => $symbol,
|
||||
));
|
||||
|
||||
$default_uri = $this->getURI('/diffusion/symbol/'.$symbol.'/');
|
||||
|
||||
if (count($results) > 1) {
|
||||
$response = "Multiple symbols named '{$symbol}': {$default_uri}";
|
||||
} else if (count($results) == 1) {
|
||||
$result = head($results);
|
||||
$response =
|
||||
$result['type'].' '.
|
||||
$result['name'].' '.
|
||||
'('.$result['language'].'): '.
|
||||
nonempty($result['uri'], $default_uri);
|
||||
} else {
|
||||
$response = "No symbol '{$symbol}' found anywhere.";
|
||||
}
|
||||
|
||||
$this->replyTo($message, $response);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -5,23 +5,23 @@
|
|||
*
|
||||
* @group irc
|
||||
*/
|
||||
final class PhabricatorIRCWhatsNewHandler extends PhabricatorIRCHandler {
|
||||
final class PhabricatorBotWhatsNewHandler extends PhabricatorBotHandler {
|
||||
|
||||
private $floodblock = 0;
|
||||
|
||||
public function receiveMessage(PhabricatorIRCMessage $message) {
|
||||
public function receiveMessage(PhabricatorBotMessage $message) {
|
||||
|
||||
switch ($message->getCommand()) {
|
||||
case 'PRIVMSG':
|
||||
case 'MESSAGE':
|
||||
$reply_to = $message->getReplyTo();
|
||||
if (!$reply_to) {
|
||||
break;
|
||||
}
|
||||
|
||||
$message = $message->getMessageText();
|
||||
$message_body = $message->getBody();
|
||||
|
||||
$prompt = '~what( i|\')?s new\?~i';
|
||||
if (preg_match($prompt, $message)) {
|
||||
if (preg_match($prompt, $message_body)) {
|
||||
if (time() < $this->floodblock) {
|
||||
return;
|
||||
}
|
||||
|
@ -108,9 +108,15 @@ final class PhabricatorIRCWhatsNewHandler extends PhabricatorIRCHandler {
|
|||
$gray = $color.'15';
|
||||
$bold = chr(2);
|
||||
$reset = chr(15);
|
||||
$content = "{$bold}{$user}{$reset} {$gray}{$action} {$blue}{$bold}".
|
||||
"{$title}{$reset} - {$gray}{$uri}{$reset}";
|
||||
$this->write('PRIVMSG',"{$reply_to} :{$content}");
|
||||
// Disabling irc-specific styling, at least for now
|
||||
// $content = "{$bold}{$user}{$reset} {$gray}{$action} {$blue}{$bold}".
|
||||
// "{$title}{$reset} - {$gray}{$uri}{$reset}";
|
||||
$content = "{$user} {$action} {$title} - {$uri}";
|
||||
$this->writeMessage(
|
||||
id(new PhabricatorBotMessage())
|
||||
->setCommand('MESSAGE')
|
||||
->setTarget($reply_to)
|
||||
->setBody($content));
|
||||
}
|
||||
return;
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Implements the base IRC protocol so servers don't kick you off.
|
||||
*
|
||||
* @group irc
|
||||
*/
|
||||
final class PhabricatorIRCProtocolHandler extends PhabricatorBotHandler {
|
||||
|
||||
public function receiveMessage(PhabricatorBotMessage $message) {
|
||||
switch ($message->getCommand()) {
|
||||
case '422': // Error - no MOTD
|
||||
case '376': // End of MOTD
|
||||
$nickpass = $this->getConfig('nickpass');
|
||||
if ($nickpass) {
|
||||
$this->writeMessage(
|
||||
id(new PhabricatorBotMessage())
|
||||
->setCommand('MESSAGE')
|
||||
->setTarget('nickserv')
|
||||
->setBody("IDENTIFY {$nickpass}"));
|
||||
}
|
||||
$join = $this->getConfig('join');
|
||||
if (!$join) {
|
||||
throw new Exception("Not configured to join any channels!");
|
||||
}
|
||||
foreach ($join as $channel) {
|
||||
$this->writeMessage(
|
||||
id(new PhabricatorBotMessage())
|
||||
->setCommand('JOIN')
|
||||
->setBody($channel));
|
||||
}
|
||||
break;
|
||||
case 'PING':
|
||||
$this->writeMessage(
|
||||
id(new PhabricatorBotMessage())
|
||||
->setCommand('PONG')
|
||||
->setBody($message->getBody()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,250 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Simple IRC bot which runs as a Phabricator daemon. Although this bot is
|
||||
* somewhat useful, it is also intended to serve as a demo of how to write
|
||||
* "system agents" which communicate with Phabricator over Conduit, so you can
|
||||
* script system interactions and integrate with other systems.
|
||||
*
|
||||
* NOTE: This is super janky and experimental right now.
|
||||
*
|
||||
* @group irc
|
||||
*/
|
||||
final class PhabricatorIRCBot extends PhabricatorDaemon {
|
||||
|
||||
private $socket;
|
||||
private $handlers;
|
||||
|
||||
private $writeBuffer;
|
||||
private $readBuffer;
|
||||
|
||||
private $conduit;
|
||||
private $config;
|
||||
|
||||
public function run() {
|
||||
|
||||
$argv = $this->getArgv();
|
||||
if (count($argv) !== 1) {
|
||||
throw new Exception("usage: PhabricatorIRCBot <json_config_file>");
|
||||
}
|
||||
|
||||
$json_raw = Filesystem::readFile($argv[0]);
|
||||
$config = json_decode($json_raw, true);
|
||||
if (!is_array($config)) {
|
||||
throw new Exception("File '{$argv[0]}' is not valid JSON!");
|
||||
}
|
||||
|
||||
$server = idx($config, 'server');
|
||||
$port = idx($config, 'port', 6667);
|
||||
$handlers = idx($config, 'handlers', array());
|
||||
$pass = idx($config, 'pass');
|
||||
$nick = idx($config, 'nick', 'phabot');
|
||||
$user = idx($config, 'user', $nick);
|
||||
$ssl = idx($config, 'ssl', false);
|
||||
$nickpass = idx($config, 'nickpass');
|
||||
|
||||
$this->config = $config;
|
||||
|
||||
if (!preg_match('/^[A-Za-z0-9_`[{}^|\]\\-]+$/', $nick)) {
|
||||
throw new Exception(
|
||||
"Nickname '{$nick}' is invalid!");
|
||||
}
|
||||
|
||||
foreach ($handlers as $handler) {
|
||||
$obj = newv($handler, array($this));
|
||||
$this->handlers[] = $obj;
|
||||
}
|
||||
|
||||
$conduit_uri = idx($config, 'conduit.uri');
|
||||
if ($conduit_uri) {
|
||||
$conduit_user = idx($config, 'conduit.user');
|
||||
$conduit_cert = idx($config, 'conduit.cert');
|
||||
|
||||
// Normalize the path component of the URI so users can enter the
|
||||
// domain without the "/api/" part.
|
||||
$conduit_uri = new PhutilURI($conduit_uri);
|
||||
$conduit_uri->setPath('/api/');
|
||||
$conduit_uri = (string)$conduit_uri;
|
||||
|
||||
$conduit = new ConduitClient($conduit_uri);
|
||||
$response = $conduit->callMethodSynchronous(
|
||||
'conduit.connect',
|
||||
array(
|
||||
'client' => 'PhabricatorIRCBot',
|
||||
'clientVersion' => '1.0',
|
||||
'clientDescription' => php_uname('n').':'.$nick,
|
||||
'user' => $conduit_user,
|
||||
'certificate' => $conduit_cert,
|
||||
));
|
||||
|
||||
$this->conduit = $conduit;
|
||||
}
|
||||
|
||||
$errno = null;
|
||||
$error = null;
|
||||
if (!$ssl) {
|
||||
$socket = fsockopen($server, $port, $errno, $error);
|
||||
} else {
|
||||
$socket = fsockopen('ssl://'.$server, $port, $errno, $error);
|
||||
}
|
||||
if (!$socket) {
|
||||
throw new Exception("Failed to connect, #{$errno}: {$error}");
|
||||
}
|
||||
$ok = stream_set_blocking($socket, false);
|
||||
if (!$ok) {
|
||||
throw new Exception("Failed to set stream nonblocking.");
|
||||
}
|
||||
|
||||
$this->socket = $socket;
|
||||
$this->writeCommand('USER', "{$user} 0 * :{$user}");
|
||||
if ($pass) {
|
||||
$this->writeCommand('PASS', "{$pass}");
|
||||
}
|
||||
|
||||
$this->writeCommand('NICK', "{$nick}");
|
||||
$this->runSelectLoop();
|
||||
}
|
||||
|
||||
public function getConfig($key, $default = null) {
|
||||
return idx($this->config, $key, $default);
|
||||
}
|
||||
|
||||
private function runSelectLoop() {
|
||||
do {
|
||||
$this->stillWorking();
|
||||
|
||||
$read = array($this->socket);
|
||||
if (strlen($this->writeBuffer)) {
|
||||
$write = array($this->socket);
|
||||
} else {
|
||||
$write = array();
|
||||
}
|
||||
$except = array();
|
||||
|
||||
$ok = @stream_select($read, $write, $except, $timeout_sec = 1);
|
||||
if ($ok === false) {
|
||||
throw new Exception(
|
||||
"socket_select() failed: ".socket_strerror(socket_last_error()));
|
||||
}
|
||||
|
||||
if ($read) {
|
||||
// Test for connection termination; in PHP, fread() off a nonblocking,
|
||||
// closed socket is empty string.
|
||||
if (feof($this->socket)) {
|
||||
// This indicates the connection was terminated on the other side,
|
||||
// just exit via exception and let the overseer restart us after a
|
||||
// delay so we can reconnect.
|
||||
throw new Exception("Remote host closed connection.");
|
||||
}
|
||||
do {
|
||||
$data = fread($this->socket, 4096);
|
||||
if ($data === false) {
|
||||
throw new Exception("fread() failed!");
|
||||
} else {
|
||||
$this->debugLog(true, $data);
|
||||
$this->readBuffer .= $data;
|
||||
}
|
||||
} while (strlen($data));
|
||||
}
|
||||
|
||||
if ($write) {
|
||||
do {
|
||||
$len = fwrite($this->socket, $this->writeBuffer);
|
||||
if ($len === false) {
|
||||
throw new Exception("fwrite() failed!");
|
||||
} else {
|
||||
$this->debugLog(false, substr($this->writeBuffer, 0, $len));
|
||||
$this->writeBuffer = substr($this->writeBuffer, $len);
|
||||
}
|
||||
} while (strlen($this->writeBuffer));
|
||||
}
|
||||
|
||||
do {
|
||||
$routed_message = $this->processReadBuffer();
|
||||
} while ($routed_message);
|
||||
|
||||
foreach ($this->handlers as $handler) {
|
||||
$handler->runBackgroundTasks();
|
||||
}
|
||||
|
||||
} while (true);
|
||||
}
|
||||
|
||||
private function write($message) {
|
||||
$this->writeBuffer .= $message;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function writeCommand($command, $message) {
|
||||
return $this->write($command.' '.$message."\r\n");
|
||||
}
|
||||
|
||||
private function processReadBuffer() {
|
||||
$until = strpos($this->readBuffer, "\r\n");
|
||||
if ($until === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$message = substr($this->readBuffer, 0, $until);
|
||||
$this->readBuffer = substr($this->readBuffer, $until + 2);
|
||||
|
||||
$pattern =
|
||||
'/^'.
|
||||
'(?:(?P<sender>:(\S+)) )?'. // This may not be present.
|
||||
'(?P<command>[A-Z0-9]+) '.
|
||||
'(?P<data>.*)'.
|
||||
'$/';
|
||||
|
||||
$matches = null;
|
||||
if (!preg_match($pattern, $message, $matches)) {
|
||||
throw new Exception("Unexpected message from server: {$message}");
|
||||
}
|
||||
|
||||
$irc_message = new PhabricatorIRCMessage(
|
||||
idx($matches, 'sender'),
|
||||
$matches['command'],
|
||||
$matches['data']);
|
||||
|
||||
$this->routeMessage($irc_message);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function routeMessage(PhabricatorIRCMessage $message) {
|
||||
$ignore = $this->getConfig('ignore');
|
||||
if ($ignore && in_array($message->getSenderNickName(), $ignore)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->handlers as $handler) {
|
||||
try {
|
||||
$handler->receiveMessage($message);
|
||||
} catch (Exception $ex) {
|
||||
phlog($ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
$this->write("QUIT Goodbye.\r\n");
|
||||
fclose($this->socket);
|
||||
}
|
||||
|
||||
private function debugLog($is_read, $message) {
|
||||
if ($this->getTraceMode()) {
|
||||
echo $is_read ? '<<< ' : '>>> ';
|
||||
echo addcslashes($message, "\0..\37\177..\377");
|
||||
echo "\n";
|
||||
}
|
||||
}
|
||||
|
||||
public function getConduit() {
|
||||
if (empty($this->conduit)) {
|
||||
throw new Exception(
|
||||
"This bot is not configured with a Conduit uplink. Set 'conduit.uri', ".
|
||||
"'conduit.user' and 'conduit.cert' in the configuration to connect.");
|
||||
}
|
||||
return $this->conduit;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorIRCMessage {
|
||||
|
||||
private $sender;
|
||||
private $command;
|
||||
private $data;
|
||||
|
||||
public function __construct($sender, $command, $data) {
|
||||
$this->sender = $sender;
|
||||
$this->command = $command;
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function getRawSender() {
|
||||
return $this->sender;
|
||||
}
|
||||
|
||||
public function getRawData() {
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function getCommand() {
|
||||
return $this->command;
|
||||
}
|
||||
|
||||
public function getReplyTo() {
|
||||
switch ($this->getCommand()) {
|
||||
case 'PRIVMSG':
|
||||
$target = $this->getTarget();
|
||||
if ($target[0] == '#') {
|
||||
return $target;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getSenderNickname() {
|
||||
$nick = $this->getRawSender();
|
||||
$nick = ltrim($nick, ':');
|
||||
$nick = head(explode('!', $nick));
|
||||
return $nick;
|
||||
}
|
||||
|
||||
public function getTarget() {
|
||||
switch ($this->getCommand()) {
|
||||
case 'PRIVMSG':
|
||||
$matches = null;
|
||||
$raw = $this->getRawData();
|
||||
if (preg_match('/^(\S+)\s/', $raw, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getMessageText() {
|
||||
switch ($this->getCommand()) {
|
||||
case 'PRIVMSG':
|
||||
$matches = null;
|
||||
$raw = $this->getRawData();
|
||||
if (preg_match('/^\S+\s+:?(.*)$/', $raw, $matches)) {
|
||||
return rtrim($matches[1], "\r\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Responds to IRC messages. You plug a bunch of these into a
|
||||
* @{class:PhabricatorIRCBot} to give it special behavior.
|
||||
*
|
||||
* @group irc
|
||||
*/
|
||||
abstract class PhabricatorIRCHandler {
|
||||
|
||||
private $bot;
|
||||
|
||||
final public function __construct(PhabricatorIRCBot $irc_bot) {
|
||||
$this->bot = $irc_bot;
|
||||
}
|
||||
|
||||
final protected function write($command, $message) {
|
||||
$this->bot->writeCommand($command, $message);
|
||||
return $this;
|
||||
}
|
||||
|
||||
final protected function getConduit() {
|
||||
return $this->bot->getConduit();
|
||||
}
|
||||
|
||||
final protected function getConfig($key, $default = null) {
|
||||
return $this->bot->getConfig($key, $default);
|
||||
}
|
||||
|
||||
final protected function getURI($path) {
|
||||
$base_uri = new PhutilURI($this->bot->getConfig('conduit.uri'));
|
||||
$base_uri->setPath($path);
|
||||
return (string)$base_uri;
|
||||
}
|
||||
|
||||
final protected function isChannelName($name) {
|
||||
return (strncmp($name, '#', 1) === 0);
|
||||
}
|
||||
|
||||
abstract public function receiveMessage(PhabricatorIRCMessage $message);
|
||||
|
||||
public function runBackgroundTasks() {
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,199 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Looks for Dxxxx, Txxxx and links to them.
|
||||
*
|
||||
* @group irc
|
||||
*/
|
||||
final class PhabricatorIRCObjectNameHandler extends PhabricatorIRCHandler {
|
||||
|
||||
/**
|
||||
* Map of PHIDs to the last mention of them (as an epoch timestamp); prevents
|
||||
* us from spamming chat when a single object is discussed.
|
||||
*/
|
||||
private $recentlyMentioned = array();
|
||||
|
||||
public function receiveMessage(PhabricatorIRCMessage $message) {
|
||||
|
||||
switch ($message->getCommand()) {
|
||||
case 'PRIVMSG':
|
||||
$reply_to = $message->getReplyTo();
|
||||
if (!$reply_to) {
|
||||
break;
|
||||
}
|
||||
|
||||
$message = $message->getMessageText();
|
||||
$matches = null;
|
||||
|
||||
$pattern =
|
||||
'@'.
|
||||
'(?<!/)(?:^|\b)'. // Negative lookbehind prevent matching "/D123".
|
||||
'(D|T|P|V|F)(\d+)'.
|
||||
'(?:\b|$)'.
|
||||
'@';
|
||||
|
||||
$revision_ids = array();
|
||||
$task_ids = array();
|
||||
$paste_ids = array();
|
||||
$commit_names = array();
|
||||
$vote_ids = array();
|
||||
$file_ids = array();
|
||||
|
||||
if (preg_match_all($pattern, $message, $matches, PREG_SET_ORDER)) {
|
||||
foreach ($matches as $match) {
|
||||
switch ($match[1]) {
|
||||
case 'D':
|
||||
$revision_ids[] = $match[2];
|
||||
break;
|
||||
case 'T':
|
||||
$task_ids[] = $match[2];
|
||||
break;
|
||||
case 'P':
|
||||
$paste_ids[] = $match[2];
|
||||
break;
|
||||
case 'V':
|
||||
$vote_ids[] = $match[2];
|
||||
break;
|
||||
case 'F':
|
||||
$file_ids[] = $match[2];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$pattern =
|
||||
'@'.
|
||||
'(?<!/)(?:^|\b)'.
|
||||
'(r[A-Z]+[0-9a-z]{1,40})'.
|
||||
'(?:\b|$)'.
|
||||
'@';
|
||||
if (preg_match_all($pattern, $message, $matches, PREG_SET_ORDER)) {
|
||||
foreach ($matches as $match) {
|
||||
$commit_names[] = $match[1];
|
||||
}
|
||||
}
|
||||
|
||||
$output = array();
|
||||
|
||||
if ($revision_ids) {
|
||||
$revisions = $this->getConduit()->callMethodSynchronous(
|
||||
'differential.query',
|
||||
array(
|
||||
'ids' => $revision_ids,
|
||||
));
|
||||
$revisions = array_select_keys(
|
||||
ipull($revisions, null, 'id'),
|
||||
$revision_ids
|
||||
);
|
||||
foreach ($revisions as $revision) {
|
||||
$output[$revision['phid']] =
|
||||
'D'.$revision['id'].' '.$revision['title'].' - '.
|
||||
$revision['uri'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($task_ids) {
|
||||
foreach ($task_ids as $task_id) {
|
||||
if ($task_id == 1000) {
|
||||
$output[1000] = 'T1000: A nanomorph mimetic poly-alloy'
|
||||
.'(liquid metal) assassin controlled by Skynet: '
|
||||
.'http://en.wikipedia.org/wiki/T-1000';
|
||||
continue;
|
||||
}
|
||||
$task = $this->getConduit()->callMethodSynchronous(
|
||||
'maniphest.info',
|
||||
array(
|
||||
'task_id' => $task_id,
|
||||
));
|
||||
$output[$task['phid']] = 'T'.$task['id'].': '.$task['title'].
|
||||
' (Priority: '.$task['priority'].') - '.$task['uri'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($vote_ids) {
|
||||
foreach ($vote_ids as $vote_id) {
|
||||
$vote = $this->getConduit()->callMethodSynchronous(
|
||||
'slowvote.info',
|
||||
array(
|
||||
'poll_id' => $vote_id,
|
||||
));
|
||||
$output[$vote['phid']] = 'V'.$vote['id'].': '.$vote['question'].
|
||||
' Come Vote '.$vote['uri'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($file_ids) {
|
||||
foreach ($file_ids as $file_id) {
|
||||
$file = $this->getConduit()->callMethodSynchronous(
|
||||
'file.info',
|
||||
array(
|
||||
'id' => $file_id,
|
||||
));
|
||||
$output[$file['phid']] = $file['objectName'].": ".$file['uri']." - ".
|
||||
$file['name'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($paste_ids) {
|
||||
foreach ($paste_ids as $paste_id) {
|
||||
$paste = $this->getConduit()->callMethodSynchronous(
|
||||
'paste.info',
|
||||
array(
|
||||
'paste_id' => $paste_id,
|
||||
));
|
||||
// Eventually I'd like to show the username of the paster as well,
|
||||
// however that will need something like a user.username_from_phid
|
||||
// since we (ideally) want to keep the bot to Conduit calls...and
|
||||
// not call to Phabricator-specific stuff (like actually loading
|
||||
// the User object and fetching his/her username.)
|
||||
$output[$paste['phid']] = 'P'.$paste['id'].': '.$paste['uri'].' - '.
|
||||
$paste['title'];
|
||||
|
||||
if ($paste['language']) {
|
||||
$output[$paste['phid']] .= ' ('.$paste['language'].')';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($commit_names) {
|
||||
$commits = $this->getConduit()->callMethodSynchronous(
|
||||
'diffusion.getcommits',
|
||||
array(
|
||||
'commits' => $commit_names,
|
||||
));
|
||||
foreach ($commits as $commit) {
|
||||
if (isset($commit['error'])) {
|
||||
continue;
|
||||
}
|
||||
$output[$commit['commitPHID']] = $commit['uri'];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($output as $phid => $description) {
|
||||
|
||||
// Don't mention the same object more than once every 10 minutes
|
||||
// in public channels, so we avoid spamming the chat over and over
|
||||
// again for discsussions of a specific revision, for example.
|
||||
|
||||
if (empty($this->recentlyMentioned[$reply_to])) {
|
||||
$this->recentlyMentioned[$reply_to] = array();
|
||||
}
|
||||
|
||||
$quiet_until = idx(
|
||||
$this->recentlyMentioned[$reply_to],
|
||||
$phid,
|
||||
0) + (60 * 10);
|
||||
|
||||
if (time() < $quiet_until) {
|
||||
// Remain quiet on this channel.
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->recentlyMentioned[$reply_to][$phid] = time();
|
||||
$this->write('PRIVMSG', "{$reply_to} :{$description}");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Implements the base IRC protocol so servers don't kick you off.
|
||||
*
|
||||
* @group irc
|
||||
*/
|
||||
final class PhabricatorIRCProtocolHandler extends PhabricatorIRCHandler {
|
||||
|
||||
public function receiveMessage(PhabricatorIRCMessage $message) {
|
||||
switch ($message->getCommand()) {
|
||||
case '422': // Error - no MOTD
|
||||
case '376': // End of MOTD
|
||||
$nickpass = $this->getConfig('nickpass');
|
||||
if ($nickpass) {
|
||||
$this->write('PRIVMSG', "nickserv :IDENTIFY {$nickpass}");
|
||||
}
|
||||
$join = $this->getConfig('join');
|
||||
if (!$join) {
|
||||
throw new Exception("Not configured to join any channels!");
|
||||
}
|
||||
foreach ($join as $channel) {
|
||||
$this->write('JOIN', $channel);
|
||||
}
|
||||
break;
|
||||
case 'PING':
|
||||
$this->write('PONG', $message->getRawData());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Watches for "where is <symbol>?"
|
||||
*
|
||||
* @group irc
|
||||
*/
|
||||
final class PhabricatorIRCSymbolHandler extends PhabricatorIRCHandler {
|
||||
|
||||
public function receiveMessage(PhabricatorIRCMessage $message) {
|
||||
|
||||
switch ($message->getCommand()) {
|
||||
case 'PRIVMSG':
|
||||
$reply_to = $message->getReplyTo();
|
||||
if (!$reply_to) {
|
||||
break;
|
||||
}
|
||||
|
||||
$text = $message->getMessageText();
|
||||
|
||||
$matches = null;
|
||||
if (!preg_match('/where(?: in the world)? is (\S+?)\?/i',
|
||||
$text, $matches)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$symbol = $matches[1];
|
||||
$results = $this->getConduit()->callMethodSynchronous(
|
||||
'diffusion.findsymbols',
|
||||
array(
|
||||
'name' => $symbol,
|
||||
));
|
||||
|
||||
$default_uri = $this->getURI('/diffusion/symbol/'.$symbol.'/');
|
||||
|
||||
if (count($results) > 1) {
|
||||
$response = "Multiple symbols named '{$symbol}': {$default_uri}";
|
||||
} else if (count($results) == 1) {
|
||||
$result = head($results);
|
||||
$response =
|
||||
$result['type'].' '.
|
||||
$result['name'].' '.
|
||||
'('.$result['language'].'): '.
|
||||
nonempty($result['uri'], $default_uri);
|
||||
} else {
|
||||
$response = "No symbol '{$symbol}' found anywhere.";
|
||||
}
|
||||
|
||||
$this->write('PRIVMSG', "{$reply_to} :{$response}");
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -30,4 +30,5 @@ final class PhabricatorEventType extends PhutilEventType {
|
|||
const TYPE_UI_DDIDRENDEROBJECT = 'ui.didRenderObject';
|
||||
const TYPE_UI_DIDRENDEROBJECTS = 'ui.didRenderObjects';
|
||||
|
||||
const TYPE_PEOPLE_DIDRENDERMENU = 'people.didRenderMenu';
|
||||
}
|
||||
|
|
|
@ -50,37 +50,41 @@ final class PhabricatorRemarkupRuleEmbedFile
|
|||
$file_name = coalesce($options['name'], $file->getName());
|
||||
$options['name'] = $file_name;
|
||||
|
||||
$is_viewable_image = $file->isViewableImage();
|
||||
|
||||
$attrs = array();
|
||||
switch ((string)$options['size']) {
|
||||
case 'full':
|
||||
$attrs['src'] = $file->getBestURI();
|
||||
$options['image_class'] = null;
|
||||
$file_data = $file->getMetadata();
|
||||
$height = idx($file_data, PhabricatorFile::METADATA_IMAGE_HEIGHT);
|
||||
if ($height) {
|
||||
$attrs['height'] = $height;
|
||||
}
|
||||
$width = idx($file_data, PhabricatorFile::METADATA_IMAGE_WIDTH);
|
||||
if ($width) {
|
||||
$attrs['width'] = $width;
|
||||
}
|
||||
break;
|
||||
case 'thumb':
|
||||
default:
|
||||
$attrs['src'] = $file->getPreview220URI();
|
||||
$dimensions =
|
||||
PhabricatorImageTransformer::getPreviewDimensions($file, 220);
|
||||
$attrs['width'] = $dimensions['sdx'];
|
||||
$attrs['height'] = $dimensions['sdy'];
|
||||
$options['image_class'] = 'phabricator-remarkup-embed-image';
|
||||
break;
|
||||
if ($is_viewable_image) {
|
||||
switch ((string)$options['size']) {
|
||||
case 'full':
|
||||
$attrs['src'] = $file->getBestURI();
|
||||
$options['image_class'] = null;
|
||||
$file_data = $file->getMetadata();
|
||||
$height = idx($file_data, PhabricatorFile::METADATA_IMAGE_HEIGHT);
|
||||
if ($height) {
|
||||
$attrs['height'] = $height;
|
||||
}
|
||||
$width = idx($file_data, PhabricatorFile::METADATA_IMAGE_WIDTH);
|
||||
if ($width) {
|
||||
$attrs['width'] = $width;
|
||||
}
|
||||
break;
|
||||
case 'thumb':
|
||||
default:
|
||||
$attrs['src'] = $file->getPreview220URI();
|
||||
$dimensions =
|
||||
PhabricatorImageTransformer::getPreviewDimensions($file, 220);
|
||||
$attrs['width'] = $dimensions['sdx'];
|
||||
$attrs['height'] = $dimensions['sdy'];
|
||||
$options['image_class'] = 'phabricator-remarkup-embed-image';
|
||||
break;
|
||||
}
|
||||
}
|
||||
$bundle['attrs'] = $attrs;
|
||||
$bundle['options'] = $options;
|
||||
|
||||
$bundle['meta'] = array(
|
||||
'phid' => $file->getPHID(),
|
||||
'viewable' => $file->isViewableImage(),
|
||||
'viewable' => $is_viewable_image,
|
||||
'uri' => $file->getBestURI(),
|
||||
'dUri' => $file->getDownloadURI(),
|
||||
'name' => $options['name'],
|
||||
|
|
|
@ -1105,6 +1105,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
|
|||
'type' => 'sql',
|
||||
'name' => $this->getPatchPath('20130201.revisionunsubscribed.sql'),
|
||||
),
|
||||
'20130131.conpherencepics.sql' => array(
|
||||
'type' => 'sql',
|
||||
'name' => $this->getPatchPath('20130131.conpherencepics.sql'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
99
src/view/form/control/AphrontFormCropControl.php
Normal file
99
src/view/form/control/AphrontFormCropControl.php
Normal file
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
final class AphrontFormCropControl extends AphrontFormControl {
|
||||
|
||||
private $width = 50;
|
||||
private $height = 50;
|
||||
|
||||
public function setHeight($height) {
|
||||
$this->height = $height;
|
||||
return $this;
|
||||
}
|
||||
public function getHeight() {
|
||||
return $this->height;
|
||||
}
|
||||
|
||||
public function setWidth($width) {
|
||||
$this->width = $width;
|
||||
return $this;
|
||||
}
|
||||
public function getWidth() {
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
protected function getCustomControlClass() {
|
||||
return 'aphront-form-crop';
|
||||
}
|
||||
|
||||
protected function renderInput() {
|
||||
$file = $this->getValue();
|
||||
|
||||
if ($file === null) {
|
||||
return phutil_render_tag(
|
||||
'img',
|
||||
array(
|
||||
'src' => PhabricatorUser::getDefaultProfileImageURI()
|
||||
),
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
$c_id = celerity_generate_unique_node_id();
|
||||
$metadata = $file->getMetadata();
|
||||
$scale = PhabricatorImageTransformer::getScaleForCrop(
|
||||
$file,
|
||||
$this->getWidth(),
|
||||
$this->getHeight()
|
||||
);
|
||||
|
||||
Javelin::initBehavior(
|
||||
'aphront-crop',
|
||||
array(
|
||||
'cropBoxID' => $c_id,
|
||||
'width' => $this->getWidth(),
|
||||
'height' => $this->getHeight(),
|
||||
'scale' => $scale,
|
||||
'imageH' => $metadata[PhabricatorFile::METADATA_IMAGE_HEIGHT],
|
||||
'imageW' => $metadata[PhabricatorFile::METADATA_IMAGE_WIDTH],
|
||||
)
|
||||
);
|
||||
|
||||
return javelin_render_tag(
|
||||
'div',
|
||||
array(
|
||||
'id' => $c_id,
|
||||
'sigil' => 'crop-box',
|
||||
'mustcapture' => true,
|
||||
'class' => 'crop-box'
|
||||
),
|
||||
javelin_render_tag(
|
||||
'img',
|
||||
array(
|
||||
'src' => $file->getBestURI(),
|
||||
'class' => 'crop-image',
|
||||
'sigil' => 'crop-image'
|
||||
),
|
||||
''
|
||||
).
|
||||
javelin_render_tag(
|
||||
'input',
|
||||
array(
|
||||
'type' => 'hidden',
|
||||
'name' => 'image_x',
|
||||
'sigil' => 'crop-x',
|
||||
),
|
||||
''
|
||||
).
|
||||
javelin_render_tag(
|
||||
'input',
|
||||
array(
|
||||
'type' => 'hidden',
|
||||
'name' => 'image_y',
|
||||
'sigil' => 'crop-y',
|
||||
),
|
||||
''
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -8,22 +8,30 @@ final class PhabricatorMenuView extends AphrontTagView {
|
|||
return false;
|
||||
}
|
||||
|
||||
public function newLabel($name) {
|
||||
public function newLabel($name, $key = null) {
|
||||
$item = id(new PhabricatorMenuItemView())
|
||||
->setType(PhabricatorMenuItemView::TYPE_LABEL)
|
||||
->setName($name);
|
||||
|
||||
if ($key !== null) {
|
||||
$item->setKey($key);
|
||||
}
|
||||
|
||||
$this->addMenuItem($item);
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
public function newLink($name, $href) {
|
||||
public function newLink($name, $href, $key = null) {
|
||||
$item = id(new PhabricatorMenuItemView())
|
||||
->setType(PhabricatorMenuItemView::TYPE_LINK)
|
||||
->setName($name)
|
||||
->setHref($href);
|
||||
|
||||
if ($key !== null) {
|
||||
$item->setKey($key);
|
||||
}
|
||||
|
||||
$this->addMenuItem($item);
|
||||
|
||||
return $item;
|
||||
|
|
|
@ -231,6 +231,18 @@ table.aphront-form-control-checkbox-layout th {
|
|||
border-color: #669966;
|
||||
}
|
||||
|
||||
.aphront-form-crop .crop-box {
|
||||
cursor: move;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.aphront-form-crop .crop-box .crop-image {
|
||||
position: relative;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
|
||||
.calendar-button {
|
||||
display: inline;
|
||||
background: url(/rsrc/image/icon/fatcow/calendar_edit.png)
|
||||
|
|
|
@ -29,6 +29,14 @@
|
|||
width: 50px;
|
||||
}
|
||||
|
||||
.conpherence-header-pane .custom-header-image {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
height: 80px;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.conpherence-header-pane .title {
|
||||
position: relative;
|
||||
font-size: 16px;
|
||||
|
@ -39,6 +47,16 @@
|
|||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.conpherence-header-pane .custom-title {
|
||||
position: relative;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
left: 132px;
|
||||
top: 21px;
|
||||
max-width: 80%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.conpherence-header-pane .subtitle {
|
||||
position: relative;
|
||||
left: 77px;
|
||||
|
@ -47,4 +65,34 @@
|
|||
max-width: 80%;
|
||||
}
|
||||
|
||||
.conpherence-header-pane .custom-subtitle {
|
||||
position: relative;
|
||||
left: 132px;
|
||||
top: 21px;
|
||||
color: #bfbfbf;
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
.conpherence-header-pane .upload-photo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.conpherence-header-upload-photo .upload-photo {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 32px;
|
||||
font-size: 16px;
|
||||
background: #99ff99;
|
||||
border-color: #669966;
|
||||
}
|
||||
|
||||
.conpherence-header-upload-photo .edit,
|
||||
.conpherence-header-upload-photo .header-image,
|
||||
.conpherence-header-upload-photo .custom-header-image,
|
||||
.conpherence-header-upload-photo .title,
|
||||
.conpherence-header-upload-photo .custom-title,
|
||||
.conpherence-header-upload-photo .subtitle,
|
||||
.conpherence-header-upload-photo .custom-subtitle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,3 +5,13 @@
|
|||
.phabricator-standard-page-body .aphront-dialog-view {
|
||||
margin: 20px auto 0px auto;
|
||||
}
|
||||
|
||||
.aphront-dialog-view .conpherence-dialogue-drag-photo {
|
||||
border: 1px dashed #bfbfbf;
|
||||
padding: 10px 0px 10px 10px;
|
||||
}
|
||||
|
||||
.aphront-dialog-view .conpherence-dialogue-upload-photo {
|
||||
background: #99ff99;
|
||||
border-color: #669966;
|
||||
}
|
||||
|
|
|
@ -21,9 +21,18 @@
|
|||
display: inline-block;
|
||||
}
|
||||
|
||||
.pholio-mock-select {
|
||||
border: 1px solid #FF0000;
|
||||
position: absolute;
|
||||
.pholio-mock-select-border {
|
||||
position: absolute;
|
||||
background: #ffffff;
|
||||
opacity: 0.25;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #000000;
|
||||
}
|
||||
|
||||
.pholio-mock-select-fill {
|
||||
position: absolute;
|
||||
border: 1px dashed #ffffff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.pholio-mock-wrapper {
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* @provides javelin-behavior-conpherence-drag-and-drop-photo
|
||||
* @requires javelin-behavior
|
||||
* javelin-dom
|
||||
* javelin-workflow
|
||||
* phabricator-drag-and-drop-file-upload
|
||||
*/
|
||||
|
||||
JX.behavior('conpherence-drag-and-drop-photo', function(config) {
|
||||
|
||||
var target = JX.$(config.target);
|
||||
var form_pane = JX.$(config.form_pane);
|
||||
|
||||
function onupload(f) {
|
||||
var data = {
|
||||
'file_id' : f.getID(),
|
||||
'action' : 'metadata'
|
||||
};
|
||||
|
||||
var form = JX.DOM.find(form_pane, 'form');
|
||||
var workflow = JX.Workflow.newFromForm(form, data);
|
||||
workflow.start();
|
||||
}
|
||||
|
||||
if (JX.PhabricatorDragAndDropFileUpload.isSupported()) {
|
||||
var drop = new JX.PhabricatorDragAndDropFileUpload(target)
|
||||
.setURI(config.upload_uri);
|
||||
drop.listen('didBeginDrag', function(e) {
|
||||
JX.DOM.alterClass(target, config.activated_class, true);
|
||||
});
|
||||
drop.listen('didEndDrag', function(e) {
|
||||
JX.DOM.alterClass(target, config.activated_class, false);
|
||||
});
|
||||
drop.listen('didUpload', onupload);
|
||||
drop.start();
|
||||
}
|
||||
|
||||
});
|
||||
|
|
@ -19,14 +19,21 @@ JX.behavior('conpherence-menu', function(config) {
|
|||
var messagesRoot = JX.$(config.messages);
|
||||
var formRoot = JX.$(config.form_pane);
|
||||
var widgetsRoot = JX.$(config.widgets_pane);
|
||||
var menuRoot = JX.$(config.menu_pane);
|
||||
JX.DOM.setContent(headerRoot, header);
|
||||
JX.DOM.setContent(messagesRoot, messages);
|
||||
messagesRoot.scrollTop = messagesRoot.scrollHeight;
|
||||
JX.DOM.setContent(formRoot, form);
|
||||
JX.DOM.setContent(widgetsRoot, widgets);
|
||||
|
||||
for (var i = 0; i < context.parentNode.childNodes.length; i++) {
|
||||
var current = context.parentNode.childNodes[i];
|
||||
var conpherences = JX.DOM.scry(
|
||||
menuRoot,
|
||||
'a',
|
||||
'conpherence-menu-click'
|
||||
);
|
||||
|
||||
for (var i = 0; i < conpherences.length; i++) {
|
||||
var current = conpherences[i];
|
||||
if (current.id == context.id) {
|
||||
JX.DOM.alterClass(current, 'conpherence-selected', true);
|
||||
JX.DOM.alterClass(current, 'hide-unread-count', true);
|
||||
|
|
94
webroot/rsrc/js/application/core/behavior-crop.js
Normal file
94
webroot/rsrc/js/application/core/behavior-crop.js
Normal file
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* @provides javelin-behavior-aphront-crop
|
||||
* @requires javelin-behavior
|
||||
* javelin-dom
|
||||
* javelin-vector
|
||||
* javelin-magical-init
|
||||
*/
|
||||
|
||||
JX.behavior('aphront-crop', function(config) {
|
||||
|
||||
var dragging = false;
|
||||
var startX, startY;
|
||||
var finalX, finalY;
|
||||
var dScale = config.scale;
|
||||
|
||||
var cropBox = JX.$(config.cropBoxID);
|
||||
var basePos = JX.$V(cropBox);
|
||||
cropBox.style.height = config.height + 'px';
|
||||
cropBox.style.width = config.width + 'px';
|
||||
var baseD = JX.$V(config.width, config.height);
|
||||
|
||||
var image = JX.DOM.find(cropBox, 'img', 'crop-image');
|
||||
image.style.height = (config.imageH * config.scale) + 'px';
|
||||
image.style.width = (config.imageW * config.scale) + 'px';
|
||||
var imageD = JX.$V(
|
||||
config.imageW * config.scale,
|
||||
config.imageH * config.scale
|
||||
);
|
||||
var minLeft = baseD.x - imageD.x;
|
||||
var minTop = baseD.y - imageD.y;
|
||||
|
||||
var minScale = Math.min(
|
||||
config.width / config.imageW,
|
||||
config.height / config.imageH,
|
||||
1
|
||||
);
|
||||
var maxScale = Math.max(
|
||||
config.imageW / config.width,
|
||||
config.imageH / config.height,
|
||||
2
|
||||
);
|
||||
|
||||
var ondrag = function(e) {
|
||||
e.kill();
|
||||
dragging = true;
|
||||
var p = JX.$V(e);
|
||||
startX = p.x;
|
||||
startY = p.y;
|
||||
};
|
||||
|
||||
var onmove = function(e) {
|
||||
if (!dragging) {
|
||||
return;
|
||||
}
|
||||
e.kill();
|
||||
|
||||
var p = JX.$V(e);
|
||||
var dx = startX - p.x;
|
||||
var dy = startY - p.y;
|
||||
var imagePos = JX.$V(image);
|
||||
var moveLeft = imagePos.x - basePos.x - dx;
|
||||
var moveTop = imagePos.y - basePos.y - dy;
|
||||
|
||||
image.style.left = Math.min(Math.max(minLeft, moveLeft), 0) + 'px';
|
||||
image.style.top = Math.min(Math.max(minTop, moveTop), 0) + 'px';
|
||||
|
||||
// reset these; a new beginning!
|
||||
startX = p.x;
|
||||
startY = p.y;
|
||||
|
||||
// save off where we are right now
|
||||
imagePos = JX.$V(image);
|
||||
finalX = Math.abs(imagePos.x - basePos.x);
|
||||
finalY = Math.abs(imagePos.y - basePos.y);
|
||||
JX.DOM.find(cropBox, 'input', 'crop-x').value = finalX;
|
||||
JX.DOM.find(cropBox, 'input', 'crop-y').value = finalY;
|
||||
};
|
||||
|
||||
var ondrop = function(e) {
|
||||
if (!dragging) {
|
||||
return;
|
||||
}
|
||||
dragging = false;
|
||||
};
|
||||
|
||||
// NOTE: Javelin does not dispatch mousemove by default.
|
||||
JX.enableDispatch(cropBox, 'mousemove');
|
||||
|
||||
JX.DOM.listen(cropBox, 'mousedown', [], ondrag);
|
||||
JX.DOM.listen(cropBox, 'mousemove', [], onmove);
|
||||
JX.DOM.listen(cropBox, 'mouseup', [], ondrop);
|
||||
JX.DOM.listen(cropBox, 'mouseout', [], ondrop);
|
||||
|
||||
});
|
|
@ -4,7 +4,8 @@
|
|||
* javelin-stratcom
|
||||
* javelin-dom
|
||||
* javelin-vector
|
||||
* javelin-event
|
||||
* javelin-magical-init
|
||||
* javelin-request
|
||||
*/
|
||||
JX.behavior('pholio-mock-view', function(config) {
|
||||
var is_dragging = false;
|
||||
|
@ -13,7 +14,9 @@ JX.behavior('pholio-mock-view', function(config) {
|
|||
var imageData;
|
||||
var startPos;
|
||||
var endPos;
|
||||
var selection;
|
||||
|
||||
var selection_border;
|
||||
var selection_fill;
|
||||
|
||||
JX.Stratcom.listen(
|
||||
'click', // Listen for clicks...
|
||||
|
@ -35,26 +38,28 @@ JX.behavior('pholio-mock-view', function(config) {
|
|||
});
|
||||
|
||||
|
||||
function draw_rectangle(node, current, init) {
|
||||
JX.$V(
|
||||
Math.abs(current.x-init.x),
|
||||
Math.abs(current.y-init.y))
|
||||
.setDim(node);
|
||||
function draw_rectangle(nodes, current, init) {
|
||||
for (var ii = 0; ii < nodes.length; ii++) {
|
||||
var node = nodes[ii];
|
||||
|
||||
JX.$V(
|
||||
(current.x-init.x < 0) ? current.x:init.x,
|
||||
(current.y-init.y < 0) ? current.y:init.y)
|
||||
.setPos(node);
|
||||
JX.$V(
|
||||
Math.abs(current.x-init.x),
|
||||
Math.abs(current.y-init.y))
|
||||
.setDim(node);
|
||||
|
||||
JX.$V(
|
||||
(current.x-init.x < 0) ? current.x:init.x,
|
||||
(current.y-init.y < 0) ? current.y:init.y)
|
||||
.setPos(node);
|
||||
}
|
||||
}
|
||||
|
||||
function getRealXY(parent, point) {
|
||||
var pos = {x: (point.x - parent.x), y: (point.y - parent.y)};
|
||||
var dim = JX.Vector.getDim(image);
|
||||
|
||||
if (pos.x < 0) pos.x = 0;
|
||||
else if (pos.x > image.clientWidth) pos.x = image.clientWidth - 1;
|
||||
|
||||
if (pos.y < 0) pos.y = 0;
|
||||
else if (pos.y > image.clientHeight) pos.y = image.clientHeight - 2;
|
||||
pos.x = Math.max(0, Math.min(pos.x, dim.x));
|
||||
pos.y = Math.max(0, Math.min(pos.y, dim.y));
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
@ -72,17 +77,18 @@ JX.behavior('pholio-mock-view', function(config) {
|
|||
|
||||
startPos = getRealXY(JX.$V(wrapper),JX.$V(e));
|
||||
|
||||
selection = JX.$N(
|
||||
selection_border = JX.$N(
|
||||
'div',
|
||||
{className: 'pholio-mock-select'}
|
||||
);
|
||||
{className: 'pholio-mock-select-border'});
|
||||
|
||||
selection_fill = JX.$N(
|
||||
'div',
|
||||
{className: 'pholio-mock-select-fill'});
|
||||
|
||||
JX.$V(startPos.x,startPos.y).setPos(selection);
|
||||
|
||||
JX.DOM.appendContent(wrapper, selection);
|
||||
|
||||
JX.$V(startPos.x, startPos.y).setPos(selection_border);
|
||||
JX.$V(startPos.x, startPos.y).setPos(selection_fill);
|
||||
|
||||
JX.DOM.appendContent(wrapper, [selection_border, selection_fill]);
|
||||
});
|
||||
|
||||
JX.enableDispatch(document.body, 'mousemove');
|
||||
|
@ -91,7 +97,10 @@ JX.behavior('pholio-mock-view', function(config) {
|
|||
return;
|
||||
}
|
||||
|
||||
draw_rectangle(selection, getRealXY(JX.$V(wrapper), JX.$V(e)), startPos);
|
||||
draw_rectangle(
|
||||
[selection_border, selection_fill],
|
||||
getRealXY(JX.$V(wrapper),
|
||||
JX.$V(e)), startPos);
|
||||
});
|
||||
|
||||
JX.Stratcom.listen(
|
||||
|
@ -107,17 +116,31 @@ JX.behavior('pholio-mock-view', function(config) {
|
|||
|
||||
comment = window.prompt("Add your comment");
|
||||
if (comment == null || comment == "") {
|
||||
selection.remove();
|
||||
JX.DOM.remove(selection_border);
|
||||
JX.DOM.remove(selection_fill);
|
||||
return;
|
||||
}
|
||||
|
||||
selection.title = comment;
|
||||
selection_fill.title = comment;
|
||||
|
||||
console.log("ImageID: " + imageData['imageID'] +
|
||||
", coords: (" + Math.min(startPos.x, endPos.x) + "," +
|
||||
Math.min(startPos.y, endPos.y) + ") -> (" +
|
||||
Math.max(startPos.x,endPos.x) + "," + Math.max(startPos.y,endPos.y) +
|
||||
"), comment: " + comment);
|
||||
var saveURL = "/pholio/inline/" + imageData['imageID'] + "/";
|
||||
|
||||
var inlineComment = new JX.Request(saveURL, function(r) {
|
||||
|
||||
});
|
||||
|
||||
var commentToAdd = {
|
||||
mockID: config.mockID,
|
||||
imageID: imageData['imageID'],
|
||||
startX: Math.min(startPos.x, endPos.x),
|
||||
startY: Math.min(startPos.y, endPos.y),
|
||||
endX: Math.max(startPos.x,endPos.x),
|
||||
endY: Math.max(startPos.y,endPos.y),
|
||||
comment: comment};
|
||||
|
||||
|
||||
inlineComment.addData(commentToAdd);
|
||||
inlineComment.send();
|
||||
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue