Merge branch 'master' into phutil_tag
(Sync.)
This commit is contained in:
@@ -1015,17 +1015,6 @@ return array(
|
|||||||
// interact with the revisions.
|
// interact with the revisions.
|
||||||
'differential.anonymous-access' => false,
|
'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
|
// 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.
|
// an automatic process, and thus get hidden by default in differential.
|
||||||
'differential.generated-paths' => array(
|
'differential.generated-paths' => array(
|
||||||
|
|||||||
@@ -7,12 +7,12 @@
|
|||||||
],
|
],
|
||||||
"handlers" : [
|
"handlers" : [
|
||||||
"PhabricatorIRCProtocolHandler",
|
"PhabricatorIRCProtocolHandler",
|
||||||
"PhabricatorIRCObjectNameHandler",
|
"PhabricatorBotObjectNameHandler",
|
||||||
"PhabricatorIRCSymbolHandler",
|
"PhabricatorBotSymbolHandler",
|
||||||
"PhabricatorIRCLogHandler",
|
"PhabricatorBotLogHandler",
|
||||||
"PhabricatorIRCWhatsNewHandler",
|
"PhabricatorBotWhatsNewHandler",
|
||||||
"PhabricatorIRCDifferentialNotificationHandler",
|
"PhabricatorBotDifferentialNotificationHandler",
|
||||||
"PhabricatorIRCMacroHandler"
|
"PhabricatorBotMacroHandler"
|
||||||
],
|
],
|
||||||
|
|
||||||
"conduit.uri" : null,
|
"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' =>
|
'aphront-form-view-css' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/1e191b83/rsrc/css/aphront/form-view.css',
|
'uri' => '/res/ec323d34/rsrc/css/aphront/form-view.css',
|
||||||
'type' => 'css',
|
'type' => 'css',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
@@ -737,7 +737,7 @@ celerity_register_resource_map(array(
|
|||||||
),
|
),
|
||||||
'conpherence-header-pane-css' =>
|
'conpherence-header-pane-css' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/4b8aebd2/rsrc/css/application/conpherence/header-pane.css',
|
'uri' => '/res/11c32adc/rsrc/css/application/conpherence/header-pane.css',
|
||||||
'type' => 'css',
|
'type' => 'css',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
@@ -764,7 +764,7 @@ celerity_register_resource_map(array(
|
|||||||
),
|
),
|
||||||
'conpherence-update-css' =>
|
'conpherence-update-css' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/8e4757b5/rsrc/css/application/conpherence/update.css',
|
'uri' => '/res/92094ed7/rsrc/css/application/conpherence/update.css',
|
||||||
'type' => 'css',
|
'type' => 'css',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
@@ -773,7 +773,7 @@ celerity_register_resource_map(array(
|
|||||||
),
|
),
|
||||||
'conpherence-widget-pane-css' =>
|
'conpherence-widget-pane-css' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/b3e6a558/rsrc/css/application/conpherence/widget-pane.css',
|
'uri' => '/res/6e5755bb/rsrc/css/application/conpherence/widget-pane.css',
|
||||||
'type' => 'css',
|
'type' => 'css',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
@@ -1031,6 +1031,19 @@ celerity_register_resource_map(array(
|
|||||||
),
|
),
|
||||||
'disk' => '/rsrc/js/application/core/behavior-tokenizer.js',
|
'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' =>
|
'javelin-behavior-aphront-drag-and-drop' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/3d809b40/rsrc/js/application/core/behavior-drag-and-drop.js',
|
'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',
|
'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' =>
|
'javelin-behavior-conpherence-init' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/bd911b43/rsrc/js/application/conpherence/behavior-init.js',
|
'uri' => '/res/bd911b43/rsrc/js/application/conpherence/behavior-init.js',
|
||||||
@@ -1109,7 +1135,7 @@ celerity_register_resource_map(array(
|
|||||||
),
|
),
|
||||||
'javelin-behavior-conpherence-menu' =>
|
'javelin-behavior-conpherence-menu' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/9d21fb86/rsrc/js/application/conpherence/behavior-menu.js',
|
'uri' => '/res/986774a0/rsrc/js/application/conpherence/behavior-menu.js',
|
||||||
'type' => 'js',
|
'type' => 'js',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
@@ -1124,7 +1150,7 @@ celerity_register_resource_map(array(
|
|||||||
),
|
),
|
||||||
'javelin-behavior-conpherence-widget-pane' =>
|
'javelin-behavior-conpherence-widget-pane' =>
|
||||||
array(
|
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',
|
'type' => 'js',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
@@ -1148,7 +1174,7 @@ celerity_register_resource_map(array(
|
|||||||
),
|
),
|
||||||
'javelin-behavior-dark-console' =>
|
'javelin-behavior-dark-console' =>
|
||||||
array(
|
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',
|
'type' => 'js',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
@@ -1269,7 +1295,7 @@ celerity_register_resource_map(array(
|
|||||||
),
|
),
|
||||||
'javelin-behavior-differential-keyboard-navigation' =>
|
'javelin-behavior-differential-keyboard-navigation' =>
|
||||||
array(
|
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',
|
'type' => 'js',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
@@ -1282,7 +1308,7 @@ celerity_register_resource_map(array(
|
|||||||
),
|
),
|
||||||
'javelin-behavior-differential-populate' =>
|
'javelin-behavior-differential-populate' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/71effec4/rsrc/js/application/differential/behavior-populate.js',
|
'uri' => '/res/526c2615/rsrc/js/application/differential/behavior-populate.js',
|
||||||
'type' => 'js',
|
'type' => 'js',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
@@ -1292,7 +1318,8 @@ celerity_register_resource_map(array(
|
|||||||
3 => 'javelin-dom',
|
3 => 'javelin-dom',
|
||||||
4 => 'javelin-stratcom',
|
4 => 'javelin-stratcom',
|
||||||
5 => 'javelin-behavior-device',
|
5 => 'javelin-behavior-device',
|
||||||
6 => 'phabricator-tooltip',
|
6 => 'javelin-vector',
|
||||||
|
7 => 'phabricator-tooltip',
|
||||||
),
|
),
|
||||||
'disk' => '/rsrc/js/application/differential/behavior-populate.js',
|
'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',
|
'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' =>
|
'javelin-behavior-phabricator-home-reveal-tiles' =>
|
||||||
array(
|
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',
|
'type' => 'js',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
@@ -1680,7 +1719,7 @@ celerity_register_resource_map(array(
|
|||||||
),
|
),
|
||||||
'javelin-behavior-phabricator-nav' =>
|
'javelin-behavior-phabricator-nav' =>
|
||||||
array(
|
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',
|
'type' => 'js',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
@@ -1827,12 +1866,16 @@ celerity_register_resource_map(array(
|
|||||||
),
|
),
|
||||||
'javelin-behavior-pholio-mock-view' =>
|
'javelin-behavior-pholio-mock-view' =>
|
||||||
array(
|
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',
|
'type' => 'js',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
0 => 'javelin-behavior',
|
0 => 'javelin-behavior',
|
||||||
1 => 'javelin-stratcom',
|
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',
|
'disk' => '/rsrc/js/application/pholio/behavior-pholio-mock-view.js',
|
||||||
),
|
),
|
||||||
@@ -2015,7 +2058,7 @@ celerity_register_resource_map(array(
|
|||||||
),
|
),
|
||||||
'javelin-event' =>
|
'javelin-event' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/3815b473/rsrc/js/javelin/core/Event.js',
|
'uri' => '/res/e6582051/rsrc/js/javelin/core/Event.js',
|
||||||
'type' => 'js',
|
'type' => 'js',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
@@ -2671,7 +2714,7 @@ celerity_register_resource_map(array(
|
|||||||
),
|
),
|
||||||
'phabricator-header-view-css' =>
|
'phabricator-header-view-css' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/88ef478c/rsrc/css/layout/phabricator-header-view.css',
|
'uri' => '/res/585b771c/rsrc/css/layout/phabricator-header-view.css',
|
||||||
'type' => 'css',
|
'type' => 'css',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
@@ -3167,7 +3210,7 @@ celerity_register_resource_map(array(
|
|||||||
),
|
),
|
||||||
'pholio-css' =>
|
'pholio-css' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/f101ad7c/rsrc/css/application/pholio/pholio.css',
|
'uri' => '/res/9de6e0b2/rsrc/css/application/pholio/pholio.css',
|
||||||
'type' => 'css',
|
'type' => 'css',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
@@ -3293,7 +3336,7 @@ celerity_register_resource_map(array(
|
|||||||
),
|
),
|
||||||
'sprite-conpher-css' =>
|
'sprite-conpher-css' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/f640f0c5/rsrc/css/sprite-conph.css',
|
'uri' => '/res/89821322/rsrc/css/sprite-conph.css',
|
||||||
'type' => 'css',
|
'type' => 'css',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
@@ -3357,7 +3400,7 @@ celerity_register_resource_map(array(
|
|||||||
), array(
|
), array(
|
||||||
'packages' =>
|
'packages' =>
|
||||||
array(
|
array(
|
||||||
'0e6165d0' =>
|
'4b569463' =>
|
||||||
array(
|
array(
|
||||||
'name' => 'core.pkg.css',
|
'name' => 'core.pkg.css',
|
||||||
'symbols' =>
|
'symbols' =>
|
||||||
@@ -3400,10 +3443,10 @@ celerity_register_resource_map(array(
|
|||||||
35 => 'phabricator-object-item-list-view-css',
|
35 => 'phabricator-object-item-list-view-css',
|
||||||
36 => 'global-drag-and-drop-css',
|
36 => 'global-drag-and-drop-css',
|
||||||
),
|
),
|
||||||
'uri' => '/res/pkg/0e6165d0/core.pkg.css',
|
'uri' => '/res/pkg/4b569463/core.pkg.css',
|
||||||
'type' => 'css',
|
'type' => 'css',
|
||||||
),
|
),
|
||||||
'ff199687' =>
|
'bc0774e5' =>
|
||||||
array(
|
array(
|
||||||
'name' => 'core.pkg.js',
|
'name' => 'core.pkg.js',
|
||||||
'symbols' =>
|
'symbols' =>
|
||||||
@@ -3442,10 +3485,10 @@ celerity_register_resource_map(array(
|
|||||||
31 => 'javelin-behavior-global-drag-and-drop',
|
31 => 'javelin-behavior-global-drag-and-drop',
|
||||||
32 => 'javelin-behavior-phabricator-home-reveal-tiles',
|
32 => 'javelin-behavior-phabricator-home-reveal-tiles',
|
||||||
),
|
),
|
||||||
'uri' => '/res/pkg/ff199687/core.pkg.js',
|
'uri' => '/res/pkg/bc0774e5/core.pkg.js',
|
||||||
'type' => 'js',
|
'type' => 'js',
|
||||||
),
|
),
|
||||||
'74593df4' =>
|
'3e0098ea' =>
|
||||||
array(
|
array(
|
||||||
'name' => 'darkconsole.pkg.js',
|
'name' => 'darkconsole.pkg.js',
|
||||||
'symbols' =>
|
'symbols' =>
|
||||||
@@ -3453,7 +3496,7 @@ celerity_register_resource_map(array(
|
|||||||
0 => 'javelin-behavior-dark-console',
|
0 => 'javelin-behavior-dark-console',
|
||||||
1 => 'javelin-behavior-error-log',
|
1 => 'javelin-behavior-error-log',
|
||||||
),
|
),
|
||||||
'uri' => '/res/pkg/74593df4/darkconsole.pkg.js',
|
'uri' => '/res/pkg/3e0098ea/darkconsole.pkg.js',
|
||||||
'type' => 'js',
|
'type' => 'js',
|
||||||
),
|
),
|
||||||
'8aaacd1b' =>
|
'8aaacd1b' =>
|
||||||
@@ -3478,7 +3521,7 @@ celerity_register_resource_map(array(
|
|||||||
'uri' => '/res/pkg/8aaacd1b/differential.pkg.css',
|
'uri' => '/res/pkg/8aaacd1b/differential.pkg.css',
|
||||||
'type' => 'css',
|
'type' => 'css',
|
||||||
),
|
),
|
||||||
'9dae5f20' =>
|
'95d0d865' =>
|
||||||
array(
|
array(
|
||||||
'name' => 'differential.pkg.js',
|
'name' => 'differential.pkg.js',
|
||||||
'symbols' =>
|
'symbols' =>
|
||||||
@@ -3503,7 +3546,7 @@ celerity_register_resource_map(array(
|
|||||||
17 => 'javelin-behavior-differential-toggle-files',
|
17 => 'javelin-behavior-differential-toggle-files',
|
||||||
18 => 'javelin-behavior-differential-user-select',
|
18 => 'javelin-behavior-differential-user-select',
|
||||||
),
|
),
|
||||||
'uri' => '/res/pkg/9dae5f20/differential.pkg.js',
|
'uri' => '/res/pkg/95d0d865/differential.pkg.js',
|
||||||
'type' => 'js',
|
'type' => 'js',
|
||||||
),
|
),
|
||||||
'c8ce2d88' =>
|
'c8ce2d88' =>
|
||||||
@@ -3529,7 +3572,7 @@ celerity_register_resource_map(array(
|
|||||||
'uri' => '/res/pkg/f96657b8/diffusion.pkg.js',
|
'uri' => '/res/pkg/f96657b8/diffusion.pkg.js',
|
||||||
'type' => 'js',
|
'type' => 'js',
|
||||||
),
|
),
|
||||||
'1c6f020b' =>
|
'd466c034' =>
|
||||||
array(
|
array(
|
||||||
'name' => 'javelin.pkg.js',
|
'name' => 'javelin.pkg.js',
|
||||||
'symbols' =>
|
'symbols' =>
|
||||||
@@ -3554,7 +3597,7 @@ celerity_register_resource_map(array(
|
|||||||
17 => 'javelin-typeahead-ondemand-source',
|
17 => 'javelin-typeahead-ondemand-source',
|
||||||
18 => 'javelin-tokenizer',
|
18 => 'javelin-tokenizer',
|
||||||
),
|
),
|
||||||
'uri' => '/res/pkg/1c6f020b/javelin.pkg.js',
|
'uri' => '/res/pkg/d466c034/javelin.pkg.js',
|
||||||
'type' => 'js',
|
'type' => 'js',
|
||||||
),
|
),
|
||||||
'e30a3fa8' =>
|
'e30a3fa8' =>
|
||||||
@@ -3588,20 +3631,20 @@ celerity_register_resource_map(array(
|
|||||||
'reverse' =>
|
'reverse' =>
|
||||||
array(
|
array(
|
||||||
'aphront-attached-file-view-css' => 'e30a3fa8',
|
'aphront-attached-file-view-css' => 'e30a3fa8',
|
||||||
'aphront-crumbs-view-css' => '0e6165d0',
|
'aphront-crumbs-view-css' => '4b569463',
|
||||||
'aphront-dialog-view-css' => '0e6165d0',
|
'aphront-dialog-view-css' => '4b569463',
|
||||||
'aphront-error-view-css' => '0e6165d0',
|
'aphront-error-view-css' => '4b569463',
|
||||||
'aphront-form-view-css' => '0e6165d0',
|
'aphront-form-view-css' => '4b569463',
|
||||||
'aphront-list-filter-view-css' => '0e6165d0',
|
'aphront-list-filter-view-css' => '4b569463',
|
||||||
'aphront-pager-view-css' => '0e6165d0',
|
'aphront-pager-view-css' => '4b569463',
|
||||||
'aphront-panel-view-css' => '0e6165d0',
|
'aphront-panel-view-css' => '4b569463',
|
||||||
'aphront-table-view-css' => '0e6165d0',
|
'aphront-table-view-css' => '4b569463',
|
||||||
'aphront-tokenizer-control-css' => '0e6165d0',
|
'aphront-tokenizer-control-css' => '4b569463',
|
||||||
'aphront-tooltip-css' => '0e6165d0',
|
'aphront-tooltip-css' => '4b569463',
|
||||||
'aphront-typeahead-control-css' => '0e6165d0',
|
'aphront-typeahead-control-css' => '4b569463',
|
||||||
'differential-changeset-view-css' => '8aaacd1b',
|
'differential-changeset-view-css' => '8aaacd1b',
|
||||||
'differential-core-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-local-commits-view-css' => '8aaacd1b',
|
||||||
'differential-results-table-css' => '8aaacd1b',
|
'differential-results-table-css' => '8aaacd1b',
|
||||||
'differential-revision-add-comment-css' => '8aaacd1b',
|
'differential-revision-add-comment-css' => '8aaacd1b',
|
||||||
@@ -3612,117 +3655,117 @@ celerity_register_resource_map(array(
|
|||||||
'differential-table-of-contents-css' => '8aaacd1b',
|
'differential-table-of-contents-css' => '8aaacd1b',
|
||||||
'diffusion-commit-view-css' => 'c8ce2d88',
|
'diffusion-commit-view-css' => 'c8ce2d88',
|
||||||
'diffusion-icons-css' => 'c8ce2d88',
|
'diffusion-icons-css' => 'c8ce2d88',
|
||||||
'global-drag-and-drop-css' => '0e6165d0',
|
'global-drag-and-drop-css' => '4b569463',
|
||||||
'inline-comment-summary-css' => '8aaacd1b',
|
'inline-comment-summary-css' => '8aaacd1b',
|
||||||
'javelin-aphlict' => 'ff199687',
|
'javelin-aphlict' => 'bc0774e5',
|
||||||
'javelin-behavior' => '1c6f020b',
|
'javelin-behavior' => 'd466c034',
|
||||||
'javelin-behavior-aphlict-dropdown' => 'ff199687',
|
'javelin-behavior-aphlict-dropdown' => 'bc0774e5',
|
||||||
'javelin-behavior-aphlict-listen' => 'ff199687',
|
'javelin-behavior-aphlict-listen' => 'bc0774e5',
|
||||||
'javelin-behavior-aphront-basic-tokenizer' => 'ff199687',
|
'javelin-behavior-aphront-basic-tokenizer' => 'bc0774e5',
|
||||||
'javelin-behavior-aphront-drag-and-drop' => '9dae5f20',
|
'javelin-behavior-aphront-drag-and-drop' => '95d0d865',
|
||||||
'javelin-behavior-aphront-drag-and-drop-textarea' => '9dae5f20',
|
'javelin-behavior-aphront-drag-and-drop-textarea' => '95d0d865',
|
||||||
'javelin-behavior-aphront-form-disable-on-submit' => 'ff199687',
|
'javelin-behavior-aphront-form-disable-on-submit' => 'bc0774e5',
|
||||||
'javelin-behavior-audit-preview' => 'f96657b8',
|
'javelin-behavior-audit-preview' => 'f96657b8',
|
||||||
'javelin-behavior-dark-console' => '74593df4',
|
'javelin-behavior-dark-console' => '3e0098ea',
|
||||||
'javelin-behavior-device' => 'ff199687',
|
'javelin-behavior-device' => 'bc0774e5',
|
||||||
'javelin-behavior-differential-accept-with-errors' => '9dae5f20',
|
'javelin-behavior-differential-accept-with-errors' => '95d0d865',
|
||||||
'javelin-behavior-differential-add-reviewers-and-ccs' => '9dae5f20',
|
'javelin-behavior-differential-add-reviewers-and-ccs' => '95d0d865',
|
||||||
'javelin-behavior-differential-comment-jump' => '9dae5f20',
|
'javelin-behavior-differential-comment-jump' => '95d0d865',
|
||||||
'javelin-behavior-differential-diff-radios' => '9dae5f20',
|
'javelin-behavior-differential-diff-radios' => '95d0d865',
|
||||||
'javelin-behavior-differential-dropdown-menus' => '9dae5f20',
|
'javelin-behavior-differential-dropdown-menus' => '95d0d865',
|
||||||
'javelin-behavior-differential-edit-inline-comments' => '9dae5f20',
|
'javelin-behavior-differential-edit-inline-comments' => '95d0d865',
|
||||||
'javelin-behavior-differential-feedback-preview' => '9dae5f20',
|
'javelin-behavior-differential-feedback-preview' => '95d0d865',
|
||||||
'javelin-behavior-differential-keyboard-navigation' => '9dae5f20',
|
'javelin-behavior-differential-keyboard-navigation' => '95d0d865',
|
||||||
'javelin-behavior-differential-populate' => '9dae5f20',
|
'javelin-behavior-differential-populate' => '95d0d865',
|
||||||
'javelin-behavior-differential-show-more' => '9dae5f20',
|
'javelin-behavior-differential-show-more' => '95d0d865',
|
||||||
'javelin-behavior-differential-toggle-files' => '9dae5f20',
|
'javelin-behavior-differential-toggle-files' => '95d0d865',
|
||||||
'javelin-behavior-differential-user-select' => '9dae5f20',
|
'javelin-behavior-differential-user-select' => '95d0d865',
|
||||||
'javelin-behavior-diffusion-commit-graph' => 'f96657b8',
|
'javelin-behavior-diffusion-commit-graph' => 'f96657b8',
|
||||||
'javelin-behavior-diffusion-pull-lastmodified' => 'f96657b8',
|
'javelin-behavior-diffusion-pull-lastmodified' => 'f96657b8',
|
||||||
'javelin-behavior-error-log' => '74593df4',
|
'javelin-behavior-error-log' => '3e0098ea',
|
||||||
'javelin-behavior-global-drag-and-drop' => 'ff199687',
|
'javelin-behavior-global-drag-and-drop' => 'bc0774e5',
|
||||||
'javelin-behavior-konami' => 'ff199687',
|
'javelin-behavior-konami' => 'bc0774e5',
|
||||||
'javelin-behavior-lightbox-attachments' => 'ff199687',
|
'javelin-behavior-lightbox-attachments' => 'bc0774e5',
|
||||||
'javelin-behavior-maniphest-batch-selector' => '7707de41',
|
'javelin-behavior-maniphest-batch-selector' => '7707de41',
|
||||||
'javelin-behavior-maniphest-subpriority-editor' => '7707de41',
|
'javelin-behavior-maniphest-subpriority-editor' => '7707de41',
|
||||||
'javelin-behavior-maniphest-transaction-controls' => '7707de41',
|
'javelin-behavior-maniphest-transaction-controls' => '7707de41',
|
||||||
'javelin-behavior-maniphest-transaction-expand' => '7707de41',
|
'javelin-behavior-maniphest-transaction-expand' => '7707de41',
|
||||||
'javelin-behavior-maniphest-transaction-preview' => '7707de41',
|
'javelin-behavior-maniphest-transaction-preview' => '7707de41',
|
||||||
'javelin-behavior-phabricator-active-nav' => 'ff199687',
|
'javelin-behavior-phabricator-active-nav' => 'bc0774e5',
|
||||||
'javelin-behavior-phabricator-autofocus' => 'ff199687',
|
'javelin-behavior-phabricator-autofocus' => 'bc0774e5',
|
||||||
'javelin-behavior-phabricator-home-reveal-tiles' => 'ff199687',
|
'javelin-behavior-phabricator-home-reveal-tiles' => 'bc0774e5',
|
||||||
'javelin-behavior-phabricator-keyboard-shortcuts' => 'ff199687',
|
'javelin-behavior-phabricator-keyboard-shortcuts' => 'bc0774e5',
|
||||||
'javelin-behavior-phabricator-nav' => 'ff199687',
|
'javelin-behavior-phabricator-nav' => 'bc0774e5',
|
||||||
'javelin-behavior-phabricator-object-selector' => '9dae5f20',
|
'javelin-behavior-phabricator-object-selector' => '95d0d865',
|
||||||
'javelin-behavior-phabricator-oncopy' => 'ff199687',
|
'javelin-behavior-phabricator-oncopy' => 'bc0774e5',
|
||||||
'javelin-behavior-phabricator-remarkup-assist' => 'ff199687',
|
'javelin-behavior-phabricator-remarkup-assist' => 'bc0774e5',
|
||||||
'javelin-behavior-phabricator-search-typeahead' => 'ff199687',
|
'javelin-behavior-phabricator-search-typeahead' => 'bc0774e5',
|
||||||
'javelin-behavior-phabricator-tooltips' => 'ff199687',
|
'javelin-behavior-phabricator-tooltips' => 'bc0774e5',
|
||||||
'javelin-behavior-phabricator-watch-anchor' => 'ff199687',
|
'javelin-behavior-phabricator-watch-anchor' => 'bc0774e5',
|
||||||
'javelin-behavior-refresh-csrf' => 'ff199687',
|
'javelin-behavior-refresh-csrf' => 'bc0774e5',
|
||||||
'javelin-behavior-repository-crossreference' => '9dae5f20',
|
'javelin-behavior-repository-crossreference' => '95d0d865',
|
||||||
'javelin-behavior-toggle-class' => 'ff199687',
|
'javelin-behavior-toggle-class' => 'bc0774e5',
|
||||||
'javelin-behavior-workflow' => 'ff199687',
|
'javelin-behavior-workflow' => 'bc0774e5',
|
||||||
'javelin-dom' => '1c6f020b',
|
'javelin-dom' => 'd466c034',
|
||||||
'javelin-event' => '1c6f020b',
|
'javelin-event' => 'd466c034',
|
||||||
'javelin-install' => '1c6f020b',
|
'javelin-install' => 'd466c034',
|
||||||
'javelin-json' => '1c6f020b',
|
'javelin-json' => 'd466c034',
|
||||||
'javelin-mask' => '1c6f020b',
|
'javelin-mask' => 'd466c034',
|
||||||
'javelin-request' => '1c6f020b',
|
'javelin-request' => 'd466c034',
|
||||||
'javelin-resource' => '1c6f020b',
|
'javelin-resource' => 'd466c034',
|
||||||
'javelin-stratcom' => '1c6f020b',
|
'javelin-stratcom' => 'd466c034',
|
||||||
'javelin-tokenizer' => '1c6f020b',
|
'javelin-tokenizer' => 'd466c034',
|
||||||
'javelin-typeahead' => '1c6f020b',
|
'javelin-typeahead' => 'd466c034',
|
||||||
'javelin-typeahead-normalizer' => '1c6f020b',
|
'javelin-typeahead-normalizer' => 'd466c034',
|
||||||
'javelin-typeahead-ondemand-source' => '1c6f020b',
|
'javelin-typeahead-ondemand-source' => 'd466c034',
|
||||||
'javelin-typeahead-preloaded-source' => '1c6f020b',
|
'javelin-typeahead-preloaded-source' => 'd466c034',
|
||||||
'javelin-typeahead-source' => '1c6f020b',
|
'javelin-typeahead-source' => 'd466c034',
|
||||||
'javelin-uri' => '1c6f020b',
|
'javelin-uri' => 'd466c034',
|
||||||
'javelin-util' => '1c6f020b',
|
'javelin-util' => 'd466c034',
|
||||||
'javelin-vector' => '1c6f020b',
|
'javelin-vector' => 'd466c034',
|
||||||
'javelin-workflow' => '1c6f020b',
|
'javelin-workflow' => 'd466c034',
|
||||||
'lightbox-attachment-css' => '0e6165d0',
|
'lightbox-attachment-css' => '4b569463',
|
||||||
'maniphest-task-summary-css' => 'e30a3fa8',
|
'maniphest-task-summary-css' => 'e30a3fa8',
|
||||||
'maniphest-transaction-detail-css' => 'e30a3fa8',
|
'maniphest-transaction-detail-css' => 'e30a3fa8',
|
||||||
'phabricator-busy' => 'ff199687',
|
'phabricator-busy' => 'bc0774e5',
|
||||||
'phabricator-content-source-view-css' => '8aaacd1b',
|
'phabricator-content-source-view-css' => '8aaacd1b',
|
||||||
'phabricator-core-buttons-css' => '0e6165d0',
|
'phabricator-core-buttons-css' => '4b569463',
|
||||||
'phabricator-core-css' => '0e6165d0',
|
'phabricator-core-css' => '4b569463',
|
||||||
'phabricator-crumbs-view-css' => '0e6165d0',
|
'phabricator-crumbs-view-css' => '4b569463',
|
||||||
'phabricator-directory-css' => '0e6165d0',
|
'phabricator-directory-css' => '4b569463',
|
||||||
'phabricator-drag-and-drop-file-upload' => '9dae5f20',
|
'phabricator-drag-and-drop-file-upload' => '95d0d865',
|
||||||
'phabricator-dropdown-menu' => 'ff199687',
|
'phabricator-dropdown-menu' => 'bc0774e5',
|
||||||
'phabricator-file-upload' => 'ff199687',
|
'phabricator-file-upload' => 'bc0774e5',
|
||||||
'phabricator-filetree-view-css' => '0e6165d0',
|
'phabricator-filetree-view-css' => '4b569463',
|
||||||
'phabricator-flag-css' => '0e6165d0',
|
'phabricator-flag-css' => '4b569463',
|
||||||
'phabricator-form-view-css' => '0e6165d0',
|
'phabricator-form-view-css' => '4b569463',
|
||||||
'phabricator-header-view-css' => '0e6165d0',
|
'phabricator-header-view-css' => '4b569463',
|
||||||
'phabricator-jump-nav' => '0e6165d0',
|
'phabricator-jump-nav' => '4b569463',
|
||||||
'phabricator-keyboard-shortcut' => 'ff199687',
|
'phabricator-keyboard-shortcut' => 'bc0774e5',
|
||||||
'phabricator-keyboard-shortcut-manager' => 'ff199687',
|
'phabricator-keyboard-shortcut-manager' => 'bc0774e5',
|
||||||
'phabricator-main-menu-view' => '0e6165d0',
|
'phabricator-main-menu-view' => '4b569463',
|
||||||
'phabricator-menu-item' => 'ff199687',
|
'phabricator-menu-item' => 'bc0774e5',
|
||||||
'phabricator-nav-view-css' => '0e6165d0',
|
'phabricator-nav-view-css' => '4b569463',
|
||||||
'phabricator-notification' => 'ff199687',
|
'phabricator-notification' => 'bc0774e5',
|
||||||
'phabricator-notification-css' => '0e6165d0',
|
'phabricator-notification-css' => '4b569463',
|
||||||
'phabricator-notification-menu-css' => '0e6165d0',
|
'phabricator-notification-menu-css' => '4b569463',
|
||||||
'phabricator-object-item-list-view-css' => '0e6165d0',
|
'phabricator-object-item-list-view-css' => '4b569463',
|
||||||
'phabricator-object-selector-css' => '8aaacd1b',
|
'phabricator-object-selector-css' => '8aaacd1b',
|
||||||
'phabricator-paste-file-upload' => 'ff199687',
|
'phabricator-paste-file-upload' => 'bc0774e5',
|
||||||
'phabricator-prefab' => 'ff199687',
|
'phabricator-prefab' => 'bc0774e5',
|
||||||
'phabricator-project-tag-css' => 'e30a3fa8',
|
'phabricator-project-tag-css' => 'e30a3fa8',
|
||||||
'phabricator-remarkup-css' => '0e6165d0',
|
'phabricator-remarkup-css' => '4b569463',
|
||||||
'phabricator-shaped-request' => '9dae5f20',
|
'phabricator-shaped-request' => '95d0d865',
|
||||||
'phabricator-side-menu-view-css' => '0e6165d0',
|
'phabricator-side-menu-view-css' => '4b569463',
|
||||||
'phabricator-standard-page-view' => '0e6165d0',
|
'phabricator-standard-page-view' => '4b569463',
|
||||||
'phabricator-textareautils' => 'ff199687',
|
'phabricator-textareautils' => 'bc0774e5',
|
||||||
'phabricator-tooltip' => 'ff199687',
|
'phabricator-tooltip' => 'bc0774e5',
|
||||||
'phabricator-transaction-view-css' => '0e6165d0',
|
'phabricator-transaction-view-css' => '4b569463',
|
||||||
'phabricator-zindex-css' => '0e6165d0',
|
'phabricator-zindex-css' => '4b569463',
|
||||||
'sprite-apps-large-css' => '0e6165d0',
|
'sprite-apps-large-css' => '4b569463',
|
||||||
'sprite-gradient-css' => '0e6165d0',
|
'sprite-gradient-css' => '4b569463',
|
||||||
'sprite-icon-css' => '0e6165d0',
|
'sprite-icon-css' => '4b569463',
|
||||||
'sprite-menu-css' => '0e6165d0',
|
'sprite-menu-css' => '4b569463',
|
||||||
'syntax-highlighting-css' => '0e6165d0',
|
'syntax-highlighting-css' => '4b569463',
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ phutil_register_library_map(array(
|
|||||||
'AphrontFileResponse' => 'aphront/response/AphrontFileResponse.php',
|
'AphrontFileResponse' => 'aphront/response/AphrontFileResponse.php',
|
||||||
'AphrontFormCheckboxControl' => 'view/form/control/AphrontFormCheckboxControl.php',
|
'AphrontFormCheckboxControl' => 'view/form/control/AphrontFormCheckboxControl.php',
|
||||||
'AphrontFormControl' => 'view/form/control/AphrontFormControl.php',
|
'AphrontFormControl' => 'view/form/control/AphrontFormControl.php',
|
||||||
|
'AphrontFormCropControl' => 'view/form/control/AphrontFormCropControl.php',
|
||||||
'AphrontFormDateControl' => 'view/form/control/AphrontFormDateControl.php',
|
'AphrontFormDateControl' => 'view/form/control/AphrontFormDateControl.php',
|
||||||
'AphrontFormDividerControl' => 'view/form/control/AphrontFormDividerControl.php',
|
'AphrontFormDividerControl' => 'view/form/control/AphrontFormDividerControl.php',
|
||||||
'AphrontFormDragAndDropUploadControl' => 'view/form/control/AphrontFormDragAndDropUploadControl.php',
|
'AphrontFormDragAndDropUploadControl' => 'view/form/control/AphrontFormDragAndDropUploadControl.php',
|
||||||
@@ -87,6 +88,7 @@ phutil_register_library_map(array(
|
|||||||
'AphrontUsageException' => 'aphront/exception/AphrontUsageException.php',
|
'AphrontUsageException' => 'aphront/exception/AphrontUsageException.php',
|
||||||
'AphrontView' => 'view/AphrontView.php',
|
'AphrontView' => 'view/AphrontView.php',
|
||||||
'AphrontWebpageResponse' => 'aphront/response/AphrontWebpageResponse.php',
|
'AphrontWebpageResponse' => 'aphront/response/AphrontWebpageResponse.php',
|
||||||
|
'AuditPeopleMenuEventListener' => 'applications/audit/events/AuditPeopleMenuEventListener.php',
|
||||||
'CelerityAPI' => 'infrastructure/celerity/CelerityAPI.php',
|
'CelerityAPI' => 'infrastructure/celerity/CelerityAPI.php',
|
||||||
'CelerityPhabricatorResourceController' => 'infrastructure/celerity/CelerityPhabricatorResourceController.php',
|
'CelerityPhabricatorResourceController' => 'infrastructure/celerity/CelerityPhabricatorResourceController.php',
|
||||||
'CelerityResourceController' => 'infrastructure/celerity/CelerityResourceController.php',
|
'CelerityResourceController' => 'infrastructure/celerity/CelerityResourceController.php',
|
||||||
@@ -197,12 +199,15 @@ phutil_register_library_map(array(
|
|||||||
'ConpherenceController' => 'applications/conpherence/controller/ConpherenceController.php',
|
'ConpherenceController' => 'applications/conpherence/controller/ConpherenceController.php',
|
||||||
'ConpherenceDAO' => 'applications/conpherence/storage/ConpherenceDAO.php',
|
'ConpherenceDAO' => 'applications/conpherence/storage/ConpherenceDAO.php',
|
||||||
'ConpherenceEditor' => 'applications/conpherence/editor/ConpherenceEditor.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',
|
'ConpherenceListController' => 'applications/conpherence/controller/ConpherenceListController.php',
|
||||||
'ConpherenceMenuItemView' => 'applications/conpherence/view/ConpherenceMenuItemView.php',
|
'ConpherenceMenuItemView' => 'applications/conpherence/view/ConpherenceMenuItemView.php',
|
||||||
'ConpherenceNewController' => 'applications/conpherence/controller/ConpherenceNewController.php',
|
'ConpherenceNewController' => 'applications/conpherence/controller/ConpherenceNewController.php',
|
||||||
'ConpherenceParticipant' => 'applications/conpherence/storage/ConpherenceParticipant.php',
|
'ConpherenceParticipant' => 'applications/conpherence/storage/ConpherenceParticipant.php',
|
||||||
'ConpherenceParticipantQuery' => 'applications/conpherence/query/ConpherenceParticipantQuery.php',
|
'ConpherenceParticipantQuery' => 'applications/conpherence/query/ConpherenceParticipantQuery.php',
|
||||||
'ConpherenceParticipationStatus' => 'applications/conpherence/constants/ConpherenceParticipationStatus.php',
|
'ConpherenceParticipationStatus' => 'applications/conpherence/constants/ConpherenceParticipationStatus.php',
|
||||||
|
'ConpherencePeopleMenuEventListener' => 'applications/conpherence/events/ConpherencePeopleMenuEventListener.php',
|
||||||
'ConpherenceReplyHandler' => 'applications/conpherence/mail/ConpherenceReplyHandler.php',
|
'ConpherenceReplyHandler' => 'applications/conpherence/mail/ConpherenceReplyHandler.php',
|
||||||
'ConpherenceThread' => 'applications/conpherence/storage/ConpherenceThread.php',
|
'ConpherenceThread' => 'applications/conpherence/storage/ConpherenceThread.php',
|
||||||
'ConpherenceThreadQuery' => 'applications/conpherence/query/ConpherenceThreadQuery.php',
|
'ConpherenceThreadQuery' => 'applications/conpherence/query/ConpherenceThreadQuery.php',
|
||||||
@@ -307,6 +312,7 @@ phutil_register_library_map(array(
|
|||||||
'DifferentialNewDiffMail' => 'applications/differential/mail/DifferentialNewDiffMail.php',
|
'DifferentialNewDiffMail' => 'applications/differential/mail/DifferentialNewDiffMail.php',
|
||||||
'DifferentialParseRenderTestCase' => 'applications/differential/__tests__/DifferentialParseRenderTestCase.php',
|
'DifferentialParseRenderTestCase' => 'applications/differential/__tests__/DifferentialParseRenderTestCase.php',
|
||||||
'DifferentialPathFieldSpecification' => 'applications/differential/field/specification/DifferentialPathFieldSpecification.php',
|
'DifferentialPathFieldSpecification' => 'applications/differential/field/specification/DifferentialPathFieldSpecification.php',
|
||||||
|
'DifferentialPeopleMenuEventListener' => 'applications/differential/events/DifferentialPeopleMenuEventListener.php',
|
||||||
'DifferentialPrimaryPaneView' => 'applications/differential/view/DifferentialPrimaryPaneView.php',
|
'DifferentialPrimaryPaneView' => 'applications/differential/view/DifferentialPrimaryPaneView.php',
|
||||||
'DifferentialReplyHandler' => 'applications/differential/DifferentialReplyHandler.php',
|
'DifferentialReplyHandler' => 'applications/differential/DifferentialReplyHandler.php',
|
||||||
'DifferentialResultsTableView' => 'applications/differential/view/DifferentialResultsTableView.php',
|
'DifferentialResultsTableView' => 'applications/differential/view/DifferentialResultsTableView.php',
|
||||||
@@ -420,6 +426,7 @@ phutil_register_library_map(array(
|
|||||||
'DiffusionPathQuery' => 'applications/diffusion/query/DiffusionPathQuery.php',
|
'DiffusionPathQuery' => 'applications/diffusion/query/DiffusionPathQuery.php',
|
||||||
'DiffusionPathQueryTestCase' => 'applications/diffusion/query/pathid/__tests__/DiffusionPathQueryTestCase.php',
|
'DiffusionPathQueryTestCase' => 'applications/diffusion/query/pathid/__tests__/DiffusionPathQueryTestCase.php',
|
||||||
'DiffusionPathValidateController' => 'applications/diffusion/controller/DiffusionPathValidateController.php',
|
'DiffusionPathValidateController' => 'applications/diffusion/controller/DiffusionPathValidateController.php',
|
||||||
|
'DiffusionPeopleMenuEventListener' => 'applications/diffusion/events/DiffusionPeopleMenuEventListener.php',
|
||||||
'DiffusionQuery' => 'applications/diffusion/query/DiffusionQuery.php',
|
'DiffusionQuery' => 'applications/diffusion/query/DiffusionQuery.php',
|
||||||
'DiffusionRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionRawDiffQuery.php',
|
'DiffusionRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionRawDiffQuery.php',
|
||||||
'DiffusionRenameHistoryQuery' => 'applications/diffusion/query/DiffusionRenameHistoryQuery.php',
|
'DiffusionRenameHistoryQuery' => 'applications/diffusion/query/DiffusionRenameHistoryQuery.php',
|
||||||
@@ -563,6 +570,7 @@ phutil_register_library_map(array(
|
|||||||
'ManiphestDefaultTaskExtensions' => 'applications/maniphest/extensions/ManiphestDefaultTaskExtensions.php',
|
'ManiphestDefaultTaskExtensions' => 'applications/maniphest/extensions/ManiphestDefaultTaskExtensions.php',
|
||||||
'ManiphestEdgeEventListener' => 'applications/maniphest/event/ManiphestEdgeEventListener.php',
|
'ManiphestEdgeEventListener' => 'applications/maniphest/event/ManiphestEdgeEventListener.php',
|
||||||
'ManiphestExportController' => 'applications/maniphest/controller/ManiphestExportController.php',
|
'ManiphestExportController' => 'applications/maniphest/controller/ManiphestExportController.php',
|
||||||
|
'ManiphestPeopleMenuEventListener' => 'applications/maniphest/event/ManiphestPeopleMenuEventListener.php',
|
||||||
'ManiphestReplyHandler' => 'applications/maniphest/ManiphestReplyHandler.php',
|
'ManiphestReplyHandler' => 'applications/maniphest/ManiphestReplyHandler.php',
|
||||||
'ManiphestReportController' => 'applications/maniphest/controller/ManiphestReportController.php',
|
'ManiphestReportController' => 'applications/maniphest/controller/ManiphestReportController.php',
|
||||||
'ManiphestSavedQuery' => 'applications/maniphest/storage/ManiphestSavedQuery.php',
|
'ManiphestSavedQuery' => 'applications/maniphest/storage/ManiphestSavedQuery.php',
|
||||||
@@ -695,6 +703,18 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorBarePageExample' => 'applications/uiexample/examples/PhabricatorBarePageExample.php',
|
'PhabricatorBarePageExample' => 'applications/uiexample/examples/PhabricatorBarePageExample.php',
|
||||||
'PhabricatorBarePageView' => 'view/page/PhabricatorBarePageView.php',
|
'PhabricatorBarePageView' => 'view/page/PhabricatorBarePageView.php',
|
||||||
'PhabricatorBaseEnglishTranslation' => 'infrastructure/internationalization/PhabricatorBaseEnglishTranslation.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',
|
'PhabricatorBuiltinPatchList' => 'infrastructure/storage/patch/PhabricatorBuiltinPatchList.php',
|
||||||
'PhabricatorButtonsExample' => 'applications/uiexample/examples/PhabricatorButtonsExample.php',
|
'PhabricatorButtonsExample' => 'applications/uiexample/examples/PhabricatorButtonsExample.php',
|
||||||
'PhabricatorCacheDAO' => 'applications/cache/storage/PhabricatorCacheDAO.php',
|
'PhabricatorCacheDAO' => 'applications/cache/storage/PhabricatorCacheDAO.php',
|
||||||
@@ -707,6 +727,7 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorCalendarHoliday' => 'applications/calendar/storage/PhabricatorCalendarHoliday.php',
|
'PhabricatorCalendarHoliday' => 'applications/calendar/storage/PhabricatorCalendarHoliday.php',
|
||||||
'PhabricatorCalendarHolidayTestCase' => 'applications/calendar/storage/__tests__/PhabricatorCalendarHolidayTestCase.php',
|
'PhabricatorCalendarHolidayTestCase' => 'applications/calendar/storage/__tests__/PhabricatorCalendarHolidayTestCase.php',
|
||||||
'PhabricatorCalendarViewStatusController' => 'applications/calendar/controller/PhabricatorCalendarViewStatusController.php',
|
'PhabricatorCalendarViewStatusController' => 'applications/calendar/controller/PhabricatorCalendarViewStatusController.php',
|
||||||
|
'PhabricatorCampfireProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorCampfireProtocolAdapter.php',
|
||||||
'PhabricatorChangesetResponse' => 'infrastructure/diff/PhabricatorChangesetResponse.php',
|
'PhabricatorChangesetResponse' => 'infrastructure/diff/PhabricatorChangesetResponse.php',
|
||||||
'PhabricatorChatLogChannelListController' => 'applications/chatlog/controller/PhabricatorChatLogChannelListController.php',
|
'PhabricatorChatLogChannelListController' => 'applications/chatlog/controller/PhabricatorChatLogChannelListController.php',
|
||||||
'PhabricatorChatLogChannelLogController' => 'applications/chatlog/controller/PhabricatorChatLogChannelLogController.php',
|
'PhabricatorChatLogChannelLogController' => 'applications/chatlog/controller/PhabricatorChatLogChannelLogController.php',
|
||||||
@@ -877,6 +898,7 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorFileStorageConfigurationException' => 'applications/files/exception/PhabricatorFileStorageConfigurationException.php',
|
'PhabricatorFileStorageConfigurationException' => 'applications/files/exception/PhabricatorFileStorageConfigurationException.php',
|
||||||
'PhabricatorFileStorageEngine' => 'applications/files/engine/PhabricatorFileStorageEngine.php',
|
'PhabricatorFileStorageEngine' => 'applications/files/engine/PhabricatorFileStorageEngine.php',
|
||||||
'PhabricatorFileStorageEngineSelector' => 'applications/files/engineselector/PhabricatorFileStorageEngineSelector.php',
|
'PhabricatorFileStorageEngineSelector' => 'applications/files/engineselector/PhabricatorFileStorageEngineSelector.php',
|
||||||
|
'PhabricatorFileTestCase' => 'applications/files/storage/__tests__/PhabricatorFileTestCase.php',
|
||||||
'PhabricatorFileTransformController' => 'applications/files/controller/PhabricatorFileTransformController.php',
|
'PhabricatorFileTransformController' => 'applications/files/controller/PhabricatorFileTransformController.php',
|
||||||
'PhabricatorFileUploadController' => 'applications/files/controller/PhabricatorFileUploadController.php',
|
'PhabricatorFileUploadController' => 'applications/files/controller/PhabricatorFileUploadController.php',
|
||||||
'PhabricatorFileUploadException' => 'applications/files/exception/PhabricatorFileUploadException.php',
|
'PhabricatorFileUploadException' => 'applications/files/exception/PhabricatorFileUploadException.php',
|
||||||
@@ -910,17 +932,9 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorHeaderView' => 'view/layout/PhabricatorHeaderView.php',
|
'PhabricatorHeaderView' => 'view/layout/PhabricatorHeaderView.php',
|
||||||
'PhabricatorHelpController' => 'applications/help/controller/PhabricatorHelpController.php',
|
'PhabricatorHelpController' => 'applications/help/controller/PhabricatorHelpController.php',
|
||||||
'PhabricatorHelpKeyboardShortcutController' => 'applications/help/controller/PhabricatorHelpKeyboardShortcutController.php',
|
'PhabricatorHelpKeyboardShortcutController' => 'applications/help/controller/PhabricatorHelpKeyboardShortcutController.php',
|
||||||
'PhabricatorIRCBot' => 'infrastructure/daemon/irc/PhabricatorIRCBot.php',
|
'PhabricatorIRCBot' => 'infrastructure/daemon/bot/PhabricatorIRCBot.php',
|
||||||
'PhabricatorIRCDifferentialNotificationHandler' => 'infrastructure/daemon/irc/handler/PhabricatorIRCDifferentialNotificationHandler.php',
|
'PhabricatorIRCProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorIRCProtocolAdapter.php',
|
||||||
'PhabricatorIRCFeedNotificationHandler' => 'infrastructure/daemon/irc/handler/PhabricatorIRCFeedNotificationHandler.php',
|
'PhabricatorIRCProtocolHandler' => 'infrastructure/daemon/bot/handler/PhabricatorIRCProtocolHandler.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',
|
|
||||||
'PhabricatorImageTransformer' => 'applications/files/PhabricatorImageTransformer.php',
|
'PhabricatorImageTransformer' => 'applications/files/PhabricatorImageTransformer.php',
|
||||||
'PhabricatorInfrastructureTestCase' => 'infrastructure/__tests__/PhabricatorInfrastructureTestCase.php',
|
'PhabricatorInfrastructureTestCase' => 'infrastructure/__tests__/PhabricatorInfrastructureTestCase.php',
|
||||||
'PhabricatorInlineCommentController' => 'infrastructure/diff/PhabricatorInlineCommentController.php',
|
'PhabricatorInlineCommentController' => 'infrastructure/diff/PhabricatorInlineCommentController.php',
|
||||||
@@ -1291,6 +1305,7 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorTagView' => 'view/layout/PhabricatorTagView.php',
|
'PhabricatorTagView' => 'view/layout/PhabricatorTagView.php',
|
||||||
'PhabricatorTaskmasterDaemon' => 'infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php',
|
'PhabricatorTaskmasterDaemon' => 'infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php',
|
||||||
'PhabricatorTestCase' => 'infrastructure/testing/PhabricatorTestCase.php',
|
'PhabricatorTestCase' => 'infrastructure/testing/PhabricatorTestCase.php',
|
||||||
|
'PhabricatorTestStorageEngine' => 'applications/files/engine/PhabricatorTestStorageEngine.php',
|
||||||
'PhabricatorTestWorker' => 'infrastructure/daemon/workers/__tests__/PhabricatorTestWorker.php',
|
'PhabricatorTestWorker' => 'infrastructure/daemon/workers/__tests__/PhabricatorTestWorker.php',
|
||||||
'PhabricatorTimelineCursor' => 'infrastructure/daemon/timeline/storage/PhabricatorTimelineCursor.php',
|
'PhabricatorTimelineCursor' => 'infrastructure/daemon/timeline/storage/PhabricatorTimelineCursor.php',
|
||||||
'PhabricatorTimelineDAO' => 'infrastructure/daemon/timeline/storage/PhabricatorTimelineDAO.php',
|
'PhabricatorTimelineDAO' => 'infrastructure/daemon/timeline/storage/PhabricatorTimelineDAO.php',
|
||||||
@@ -1394,6 +1409,7 @@ phutil_register_library_map(array(
|
|||||||
'PholioController' => 'applications/pholio/controller/PholioController.php',
|
'PholioController' => 'applications/pholio/controller/PholioController.php',
|
||||||
'PholioDAO' => 'applications/pholio/storage/PholioDAO.php',
|
'PholioDAO' => 'applications/pholio/storage/PholioDAO.php',
|
||||||
'PholioImage' => 'applications/pholio/storage/PholioImage.php',
|
'PholioImage' => 'applications/pholio/storage/PholioImage.php',
|
||||||
|
'PholioInlineSaveController' => 'applications/pholio/controller/PholioInlineSaveController.php',
|
||||||
'PholioMock' => 'applications/pholio/storage/PholioMock.php',
|
'PholioMock' => 'applications/pholio/storage/PholioMock.php',
|
||||||
'PholioMockCommentController' => 'applications/pholio/controller/PholioMockCommentController.php',
|
'PholioMockCommentController' => 'applications/pholio/controller/PholioMockCommentController.php',
|
||||||
'PholioMockEditController' => 'applications/pholio/controller/PholioMockEditController.php',
|
'PholioMockEditController' => 'applications/pholio/controller/PholioMockEditController.php',
|
||||||
@@ -1521,6 +1537,7 @@ phutil_register_library_map(array(
|
|||||||
'AphrontFileResponse' => 'AphrontResponse',
|
'AphrontFileResponse' => 'AphrontResponse',
|
||||||
'AphrontFormCheckboxControl' => 'AphrontFormControl',
|
'AphrontFormCheckboxControl' => 'AphrontFormControl',
|
||||||
'AphrontFormControl' => 'AphrontView',
|
'AphrontFormControl' => 'AphrontView',
|
||||||
|
'AphrontFormCropControl' => 'AphrontFormControl',
|
||||||
'AphrontFormDateControl' => 'AphrontFormControl',
|
'AphrontFormDateControl' => 'AphrontFormControl',
|
||||||
'AphrontFormDividerControl' => 'AphrontFormControl',
|
'AphrontFormDividerControl' => 'AphrontFormControl',
|
||||||
'AphrontFormDragAndDropUploadControl' => 'AphrontFormControl',
|
'AphrontFormDragAndDropUploadControl' => 'AphrontFormControl',
|
||||||
@@ -1572,6 +1589,7 @@ phutil_register_library_map(array(
|
|||||||
'AphrontUsageException' => 'AphrontException',
|
'AphrontUsageException' => 'AphrontException',
|
||||||
'AphrontView' => 'Phobject',
|
'AphrontView' => 'Phobject',
|
||||||
'AphrontWebpageResponse' => 'AphrontHTMLResponse',
|
'AphrontWebpageResponse' => 'AphrontHTMLResponse',
|
||||||
|
'AuditPeopleMenuEventListener' => 'PhutilEventListener',
|
||||||
'CelerityPhabricatorResourceController' => 'CelerityResourceController',
|
'CelerityPhabricatorResourceController' => 'CelerityResourceController',
|
||||||
'CelerityResourceController' => 'PhabricatorController',
|
'CelerityResourceController' => 'PhabricatorController',
|
||||||
'CelerityResourceGraph' => 'AbstractDirectedGraph',
|
'CelerityResourceGraph' => 'AbstractDirectedGraph',
|
||||||
@@ -1672,12 +1690,15 @@ phutil_register_library_map(array(
|
|||||||
'ConpherenceController' => 'PhabricatorController',
|
'ConpherenceController' => 'PhabricatorController',
|
||||||
'ConpherenceDAO' => 'PhabricatorLiskDAO',
|
'ConpherenceDAO' => 'PhabricatorLiskDAO',
|
||||||
'ConpherenceEditor' => 'PhabricatorApplicationTransactionEditor',
|
'ConpherenceEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||||
|
'ConpherenceFormDragAndDropUploadControl' => 'AphrontFormControl',
|
||||||
|
'ConpherenceImageData' => 'ConpherenceConstants',
|
||||||
'ConpherenceListController' => 'ConpherenceController',
|
'ConpherenceListController' => 'ConpherenceController',
|
||||||
'ConpherenceMenuItemView' => 'AphrontTagView',
|
'ConpherenceMenuItemView' => 'AphrontTagView',
|
||||||
'ConpherenceNewController' => 'ConpherenceController',
|
'ConpherenceNewController' => 'ConpherenceController',
|
||||||
'ConpherenceParticipant' => 'ConpherenceDAO',
|
'ConpherenceParticipant' => 'ConpherenceDAO',
|
||||||
'ConpherenceParticipantQuery' => 'PhabricatorOffsetPagedQuery',
|
'ConpherenceParticipantQuery' => 'PhabricatorOffsetPagedQuery',
|
||||||
'ConpherenceParticipationStatus' => 'ConpherenceConstants',
|
'ConpherenceParticipationStatus' => 'ConpherenceConstants',
|
||||||
|
'ConpherencePeopleMenuEventListener' => 'PhutilEventListener',
|
||||||
'ConpherenceReplyHandler' => 'PhabricatorMailReplyHandler',
|
'ConpherenceReplyHandler' => 'PhabricatorMailReplyHandler',
|
||||||
'ConpherenceThread' =>
|
'ConpherenceThread' =>
|
||||||
array(
|
array(
|
||||||
@@ -1778,6 +1799,7 @@ phutil_register_library_map(array(
|
|||||||
'DifferentialNewDiffMail' => 'DifferentialReviewRequestMail',
|
'DifferentialNewDiffMail' => 'DifferentialReviewRequestMail',
|
||||||
'DifferentialParseRenderTestCase' => 'PhabricatorTestCase',
|
'DifferentialParseRenderTestCase' => 'PhabricatorTestCase',
|
||||||
'DifferentialPathFieldSpecification' => 'DifferentialFieldSpecification',
|
'DifferentialPathFieldSpecification' => 'DifferentialFieldSpecification',
|
||||||
|
'DifferentialPeopleMenuEventListener' => 'PhutilEventListener',
|
||||||
'DifferentialPrimaryPaneView' => 'AphrontView',
|
'DifferentialPrimaryPaneView' => 'AphrontView',
|
||||||
'DifferentialReplyHandler' => 'PhabricatorMailReplyHandler',
|
'DifferentialReplyHandler' => 'PhabricatorMailReplyHandler',
|
||||||
'DifferentialResultsTableView' => 'AphrontView',
|
'DifferentialResultsTableView' => 'AphrontView',
|
||||||
@@ -1873,6 +1895,7 @@ phutil_register_library_map(array(
|
|||||||
'DiffusionPathCompleteController' => 'DiffusionController',
|
'DiffusionPathCompleteController' => 'DiffusionController',
|
||||||
'DiffusionPathQueryTestCase' => 'PhabricatorTestCase',
|
'DiffusionPathQueryTestCase' => 'PhabricatorTestCase',
|
||||||
'DiffusionPathValidateController' => 'DiffusionController',
|
'DiffusionPathValidateController' => 'DiffusionController',
|
||||||
|
'DiffusionPeopleMenuEventListener' => 'PhutilEventListener',
|
||||||
'DiffusionRawDiffQuery' => 'DiffusionQuery',
|
'DiffusionRawDiffQuery' => 'DiffusionQuery',
|
||||||
'DiffusionRepositoryController' => 'DiffusionController',
|
'DiffusionRepositoryController' => 'DiffusionController',
|
||||||
'DiffusionSetupException' => 'AphrontUsageException',
|
'DiffusionSetupException' => 'AphrontUsageException',
|
||||||
@@ -1985,6 +2008,7 @@ phutil_register_library_map(array(
|
|||||||
'ManiphestDefaultTaskExtensions' => 'ManiphestTaskExtensions',
|
'ManiphestDefaultTaskExtensions' => 'ManiphestTaskExtensions',
|
||||||
'ManiphestEdgeEventListener' => 'PhutilEventListener',
|
'ManiphestEdgeEventListener' => 'PhutilEventListener',
|
||||||
'ManiphestExportController' => 'ManiphestController',
|
'ManiphestExportController' => 'ManiphestController',
|
||||||
|
'ManiphestPeopleMenuEventListener' => 'PhutilEventListener',
|
||||||
'ManiphestReplyHandler' => 'PhabricatorMailReplyHandler',
|
'ManiphestReplyHandler' => 'PhabricatorMailReplyHandler',
|
||||||
'ManiphestReportController' => 'ManiphestController',
|
'ManiphestReportController' => 'ManiphestController',
|
||||||
'ManiphestSavedQuery' => 'ManiphestDAO',
|
'ManiphestSavedQuery' => 'ManiphestDAO',
|
||||||
@@ -2131,6 +2155,15 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorBarePageExample' => 'PhabricatorUIExample',
|
'PhabricatorBarePageExample' => 'PhabricatorUIExample',
|
||||||
'PhabricatorBarePageView' => 'AphrontPageView',
|
'PhabricatorBarePageView' => 'AphrontPageView',
|
||||||
'PhabricatorBaseEnglishTranslation' => 'PhabricatorTranslation',
|
'PhabricatorBaseEnglishTranslation' => 'PhabricatorTranslation',
|
||||||
|
'PhabricatorBot' => 'PhabricatorDaemon',
|
||||||
|
'PhabricatorBotDebugLogHandler' => 'PhabricatorBotHandler',
|
||||||
|
'PhabricatorBotDifferentialNotificationHandler' => 'PhabricatorBotHandler',
|
||||||
|
'PhabricatorBotFeedNotificationHandler' => 'PhabricatorBotHandler',
|
||||||
|
'PhabricatorBotLogHandler' => 'PhabricatorBotHandler',
|
||||||
|
'PhabricatorBotMacroHandler' => 'PhabricatorBotHandler',
|
||||||
|
'PhabricatorBotObjectNameHandler' => 'PhabricatorBotHandler',
|
||||||
|
'PhabricatorBotSymbolHandler' => 'PhabricatorBotHandler',
|
||||||
|
'PhabricatorBotWhatsNewHandler' => 'PhabricatorBotHandler',
|
||||||
'PhabricatorBuiltinPatchList' => 'PhabricatorSQLPatchList',
|
'PhabricatorBuiltinPatchList' => 'PhabricatorSQLPatchList',
|
||||||
'PhabricatorButtonsExample' => 'PhabricatorUIExample',
|
'PhabricatorButtonsExample' => 'PhabricatorUIExample',
|
||||||
'PhabricatorCacheDAO' => 'PhabricatorLiskDAO',
|
'PhabricatorCacheDAO' => 'PhabricatorLiskDAO',
|
||||||
@@ -2142,6 +2175,7 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorCalendarHoliday' => 'PhabricatorCalendarDAO',
|
'PhabricatorCalendarHoliday' => 'PhabricatorCalendarDAO',
|
||||||
'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase',
|
'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase',
|
||||||
'PhabricatorCalendarViewStatusController' => 'PhabricatorCalendarController',
|
'PhabricatorCalendarViewStatusController' => 'PhabricatorCalendarController',
|
||||||
|
'PhabricatorCampfireProtocolAdapter' => 'PhabricatorBaseProtocolAdapter',
|
||||||
'PhabricatorChangesetResponse' => 'AphrontProxyResponse',
|
'PhabricatorChangesetResponse' => 'AphrontProxyResponse',
|
||||||
'PhabricatorChatLogChannelListController' => 'PhabricatorChatLogController',
|
'PhabricatorChatLogChannelListController' => 'PhabricatorChatLogController',
|
||||||
'PhabricatorChatLogChannelLogController' => 'PhabricatorChatLogController',
|
'PhabricatorChatLogChannelLogController' => 'PhabricatorChatLogController',
|
||||||
@@ -2311,6 +2345,7 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorFileShortcutController' => 'PhabricatorFileController',
|
'PhabricatorFileShortcutController' => 'PhabricatorFileController',
|
||||||
'PhabricatorFileStorageBlob' => 'PhabricatorFileDAO',
|
'PhabricatorFileStorageBlob' => 'PhabricatorFileDAO',
|
||||||
'PhabricatorFileStorageConfigurationException' => 'Exception',
|
'PhabricatorFileStorageConfigurationException' => 'Exception',
|
||||||
|
'PhabricatorFileTestCase' => 'PhabricatorTestCase',
|
||||||
'PhabricatorFileTransformController' => 'PhabricatorFileController',
|
'PhabricatorFileTransformController' => 'PhabricatorFileController',
|
||||||
'PhabricatorFileUploadController' => 'PhabricatorFileController',
|
'PhabricatorFileUploadController' => 'PhabricatorFileController',
|
||||||
'PhabricatorFileUploadException' => 'Exception',
|
'PhabricatorFileUploadException' => 'Exception',
|
||||||
@@ -2340,14 +2375,8 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorHelpController' => 'PhabricatorController',
|
'PhabricatorHelpController' => 'PhabricatorController',
|
||||||
'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController',
|
'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController',
|
||||||
'PhabricatorIRCBot' => 'PhabricatorDaemon',
|
'PhabricatorIRCBot' => 'PhabricatorDaemon',
|
||||||
'PhabricatorIRCDifferentialNotificationHandler' => 'PhabricatorIRCHandler',
|
'PhabricatorIRCProtocolAdapter' => 'PhabricatorBaseProtocolAdapter',
|
||||||
'PhabricatorIRCFeedNotificationHandler' => 'PhabricatorIRCHandler',
|
'PhabricatorIRCProtocolHandler' => 'PhabricatorBotHandler',
|
||||||
'PhabricatorIRCLogHandler' => 'PhabricatorIRCHandler',
|
|
||||||
'PhabricatorIRCMacroHandler' => 'PhabricatorIRCHandler',
|
|
||||||
'PhabricatorIRCObjectNameHandler' => 'PhabricatorIRCHandler',
|
|
||||||
'PhabricatorIRCProtocolHandler' => 'PhabricatorIRCHandler',
|
|
||||||
'PhabricatorIRCSymbolHandler' => 'PhabricatorIRCHandler',
|
|
||||||
'PhabricatorIRCWhatsNewHandler' => 'PhabricatorIRCHandler',
|
|
||||||
'PhabricatorInfrastructureTestCase' => 'PhabricatorTestCase',
|
'PhabricatorInfrastructureTestCase' => 'PhabricatorTestCase',
|
||||||
'PhabricatorInlineCommentController' => 'PhabricatorController',
|
'PhabricatorInlineCommentController' => 'PhabricatorController',
|
||||||
'PhabricatorInlineCommentInterface' => 'PhabricatorMarkupInterface',
|
'PhabricatorInlineCommentInterface' => 'PhabricatorMarkupInterface',
|
||||||
@@ -2683,6 +2712,7 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorTagView' => 'AphrontView',
|
'PhabricatorTagView' => 'AphrontView',
|
||||||
'PhabricatorTaskmasterDaemon' => 'PhabricatorDaemon',
|
'PhabricatorTaskmasterDaemon' => 'PhabricatorDaemon',
|
||||||
'PhabricatorTestCase' => 'ArcanistPhutilTestCase',
|
'PhabricatorTestCase' => 'ArcanistPhutilTestCase',
|
||||||
|
'PhabricatorTestStorageEngine' => 'PhabricatorFileStorageEngine',
|
||||||
'PhabricatorTestWorker' => 'PhabricatorWorker',
|
'PhabricatorTestWorker' => 'PhabricatorWorker',
|
||||||
'PhabricatorTimelineCursor' => 'PhabricatorTimelineDAO',
|
'PhabricatorTimelineCursor' => 'PhabricatorTimelineDAO',
|
||||||
'PhabricatorTimelineDAO' => 'PhabricatorLiskDAO',
|
'PhabricatorTimelineDAO' => 'PhabricatorLiskDAO',
|
||||||
@@ -2797,6 +2827,7 @@ phutil_register_library_map(array(
|
|||||||
0 => 'PholioDAO',
|
0 => 'PholioDAO',
|
||||||
1 => 'PhabricatorMarkupInterface',
|
1 => 'PhabricatorMarkupInterface',
|
||||||
),
|
),
|
||||||
|
'PholioInlineSaveController' => 'PholioController',
|
||||||
'PholioMock' =>
|
'PholioMock' =>
|
||||||
array(
|
array(
|
||||||
0 => 'PholioDAO',
|
0 => 'PholioDAO',
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ final class PhabricatorApplicationAudit extends PhabricatorApplication {
|
|||||||
return PhabricatorEnv::getDoclink('article/Audit_User_Guide.html');
|
return PhabricatorEnv::getDoclink('article/Audit_User_Guide.html');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getEventListeners() {
|
||||||
|
return array(
|
||||||
|
new AuditPeopleMenuEventListener()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function getRoutes() {
|
public function getRoutes() {
|
||||||
return array(
|
return array(
|
||||||
'/audit/' => 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;
|
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');
|
$namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace');
|
||||||
|
|
||||||
$databases = queryfx_all($conn_raw, 'SHOW DATABASES');
|
$databases = queryfx_all($conn_raw, 'SHOW DATABASES');
|
||||||
|
|||||||
@@ -24,6 +24,31 @@ final class PhabricatorSetupCheckMySQL extends PhabricatorSetupCheck {
|
|||||||
->setName(pht('Small MySQL "max_allowed_packet"'))
|
->setName(pht('Small MySQL "max_allowed_packet"'))
|
||||||
->setMessage($message);
|
->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;
|
return self::GROUP_COMMUNICATION;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getEventListeners() {
|
||||||
|
return array(
|
||||||
|
new ConpherencePeopleMenuEventListener(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function getRoutes() {
|
public function getRoutes() {
|
||||||
return array(
|
return array(
|
||||||
'/conpherence/' => 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_FILES = 'files';
|
||||||
const TYPE_TITLE = 'title';
|
const TYPE_TITLE = 'title';
|
||||||
const TYPE_PICTURE = 'picture';
|
const TYPE_PICTURE = 'picture';
|
||||||
|
const TYPE_PICTURE_CROP = 'picture-crop';
|
||||||
const TYPE_PARTICIPANTS = 'participants';
|
const TYPE_PARTICIPANTS = 'participants';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,7 +131,10 @@ abstract class ConpherenceController extends PhabricatorController {
|
|||||||
$user = $this->getRequest()->getUser();
|
$user = $this->getRequest()->getUser();
|
||||||
foreach ($conpherences as $conpherence) {
|
foreach ($conpherences as $conpherence) {
|
||||||
$uri = $this->getApplicationURI('view/'.$conpherence->getID().'/');
|
$uri = $this->getApplicationURI('view/'.$conpherence->getID().'/');
|
||||||
$data = $conpherence->getDisplayData($user);
|
$data = $conpherence->getDisplayData(
|
||||||
|
$user,
|
||||||
|
null
|
||||||
|
);
|
||||||
$title = $data['title'];
|
$title = $data['title'];
|
||||||
$subtitle = $data['subtitle'];
|
$subtitle = $data['subtitle'];
|
||||||
$unread_count = $data['unread_count'];
|
$unread_count = $data['unread_count'];
|
||||||
@@ -206,6 +209,7 @@ abstract class ConpherenceController extends PhabricatorController {
|
|||||||
'messages' => 'conpherence-messages',
|
'messages' => 'conpherence-messages',
|
||||||
'widgets_pane' => 'conpherence-widget-pane',
|
'widgets_pane' => 'conpherence-widget-pane',
|
||||||
'form_pane' => 'conpherence-form',
|
'form_pane' => 'conpherence-form',
|
||||||
|
'menu_pane' => 'conpherence-menu',
|
||||||
'fancy_ajax' => (bool) $this->getSelectedConpherencePHID()
|
'fancy_ajax' => (bool) $this->getSelectedConpherencePHID()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -219,6 +223,14 @@ abstract class ConpherenceController extends PhabricatorController {
|
|||||||
'form_pane' => 'conpherence-form'
|
'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())
|
$conpherence = id(new ConpherenceThreadQuery())
|
||||||
->setViewer($user)
|
->setViewer($user)
|
||||||
->withIDs(array($conpherence_id))
|
->withIDs(array($conpherence_id))
|
||||||
|
->needOrigPics(true)
|
||||||
|
->needHeaderPics(true)
|
||||||
->executeOne();
|
->executeOne();
|
||||||
$supported_formats = PhabricatorFile::getTransformableImageFormats();
|
$supported_formats = PhabricatorFile::getTransformableImageFormats();
|
||||||
|
|
||||||
@@ -59,31 +61,63 @@ final class ConpherenceUpdateController extends
|
|||||||
break;
|
break;
|
||||||
case 'metadata':
|
case 'metadata':
|
||||||
$xactions = array();
|
$xactions = array();
|
||||||
$images = $request->getArr('image');
|
$top = $request->getInt('image_y');
|
||||||
if ($images) {
|
$left = $request->getInt('image_x');
|
||||||
// just take the first one
|
$file_id = $request->getInt('file_id');
|
||||||
$file_phid = reset($images);
|
if ($file_id) {
|
||||||
$file = id(new PhabricatorFileQuery())
|
$orig_file = id(new PhabricatorFileQuery())
|
||||||
->setViewer($user)
|
->setViewer($user)
|
||||||
->withPHIDs(array($file_phid))
|
->withIDs(array($file_id))
|
||||||
->executeOne();
|
->executeOne();
|
||||||
$okay = $file->isTransformableImage();
|
$okay = $orig_file->isTransformableImage();
|
||||||
if ($okay) {
|
if ($okay) {
|
||||||
$xformer = new PhabricatorImageTransformer();
|
|
||||||
$xformed = $xformer->executeThumbTransform(
|
|
||||||
$file,
|
|
||||||
$x = 50,
|
|
||||||
$y = 50);
|
|
||||||
$image_phid = $xformed->getPHID();
|
|
||||||
$xactions[] = id(new ConpherenceTransaction())
|
$xactions[] = id(new ConpherenceTransaction())
|
||||||
->setTransactionType(ConpherenceTransactionType::TYPE_PICTURE)
|
->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 {
|
} else {
|
||||||
$e_file[] = $file;
|
$e_file[] = $orig_file;
|
||||||
$errors[] =
|
$errors[] =
|
||||||
pht('This server only supports these image formats: %s.',
|
pht('This server only supports these image formats: %s.',
|
||||||
implode(', ', $supported_formats));
|
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');
|
$title = $request->getStr('title');
|
||||||
if ($title != $conpherence->getTitle()) {
|
if ($title != $conpherence->getTitle()) {
|
||||||
@@ -131,24 +165,42 @@ final class ConpherenceUpdateController extends
|
|||||||
->setLabel(pht('Title'))
|
->setLabel(pht('Title'))
|
||||||
->setName('title')
|
->setName('title')
|
||||||
->setValue($conpherence->getTitle())
|
->setValue($conpherence->getTitle())
|
||||||
)
|
);
|
||||||
->appendChild(
|
|
||||||
id(new AphrontFormMarkupControl())
|
$image = $conpherence->getImage(ConpherenceImageData::SIZE_ORIG);
|
||||||
->setLabel(pht('Image'))
|
if ($image) {
|
||||||
->setValue(phutil_tag(
|
$form
|
||||||
'img',
|
->appendChild(
|
||||||
array(
|
id(new AphrontFormMarkupControl())
|
||||||
'src' => $conpherence->loadImageURI(),
|
->setLabel(pht('Image'))
|
||||||
))
|
->setValue(phutil_tag(
|
||||||
)
|
'img',
|
||||||
)
|
array(
|
||||||
->appendChild(
|
'src' =>
|
||||||
id(new AphrontFormDragAndDropUploadControl())
|
$conpherence->loadImageURI(ConpherenceImageData::SIZE_HEAD),
|
||||||
->setLabel(pht('Change Image'))
|
))
|
||||||
->setName('image')
|
)
|
||||||
->setValue($e_file)
|
)
|
||||||
->setCaption('Supported formats: '.implode(', ', $supported_formats))
|
->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');
|
require_celerity_resource('conpherence-update-css');
|
||||||
return id(new AphrontDialogResponse())
|
return id(new AphrontDialogResponse())
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ final class ConpherenceViewController extends
|
|||||||
->setViewer($user)
|
->setViewer($user)
|
||||||
->withIDs(array($conpherence_id))
|
->withIDs(array($conpherence_id))
|
||||||
->needWidgetData(true)
|
->needWidgetData(true)
|
||||||
|
->needHeaderPics(true)
|
||||||
->executeOne();
|
->executeOne();
|
||||||
$this->setConpherence($conpherence);
|
$this->setConpherence($conpherence);
|
||||||
|
|
||||||
@@ -67,23 +68,34 @@ final class ConpherenceViewController extends
|
|||||||
require_celerity_resource('conpherence-header-pane-css');
|
require_celerity_resource('conpherence-header-pane-css');
|
||||||
$user = $this->getRequest()->getUser();
|
$user = $this->getRequest()->getUser();
|
||||||
$conpherence = $this->getConpherence();
|
$conpherence = $this->getConpherence();
|
||||||
$display_data = $conpherence->getDisplayData($user);
|
$display_data = $conpherence->getDisplayData(
|
||||||
|
$user,
|
||||||
|
ConpherenceImageData::SIZE_HEAD
|
||||||
|
);
|
||||||
$edit_href = $this->getApplicationURI('update/'.$conpherence->getID().'/');
|
$edit_href = $this->getApplicationURI('update/'.$conpherence->getID().'/');
|
||||||
|
$class_mod = $display_data['image_class'];
|
||||||
|
|
||||||
$header =
|
$header =
|
||||||
|
phutil_tag(
|
||||||
|
'div',
|
||||||
|
array(
|
||||||
|
'class' => 'upload-photo'
|
||||||
|
),
|
||||||
|
pht('Drop photo here to change this Conpherence photo.')
|
||||||
|
).
|
||||||
javelin_tag(
|
javelin_tag(
|
||||||
'a',
|
'a',
|
||||||
array(
|
array(
|
||||||
'class' => 'edit',
|
'class' => 'edit',
|
||||||
'href' => $edit_href,
|
'href' => $edit_href,
|
||||||
'sigil' => 'workflow',
|
'sigil' => 'workflow edit-action',
|
||||||
),
|
),
|
||||||
''
|
''
|
||||||
).
|
).
|
||||||
phutil_tag(
|
phutil_tag(
|
||||||
'div',
|
'div',
|
||||||
array(
|
array(
|
||||||
'class' => 'header-image',
|
'class' => $class_mod.'header-image',
|
||||||
'style' => 'background-image: url('.$display_data['image'].');'
|
'style' => 'background-image: url('.$display_data['image'].');'
|
||||||
),
|
),
|
||||||
''
|
''
|
||||||
@@ -91,14 +103,14 @@ final class ConpherenceViewController extends
|
|||||||
phutil_tag(
|
phutil_tag(
|
||||||
'div',
|
'div',
|
||||||
array(
|
array(
|
||||||
'class' => 'title',
|
'class' => $class_mod.'title',
|
||||||
),
|
),
|
||||||
$display_data['title']
|
$display_data['title']
|
||||||
).
|
).
|
||||||
phutil_tag(
|
phutil_tag(
|
||||||
'div',
|
'div',
|
||||||
array(
|
array(
|
||||||
'class' => 'subtitle',
|
'class' => $class_mod.'subtitle',
|
||||||
),
|
),
|
||||||
$display_data['subtitle']
|
$display_data['subtitle']
|
||||||
);
|
);
|
||||||
@@ -114,6 +126,18 @@ final class ConpherenceViewController extends
|
|||||||
$rendered_transactions = array();
|
$rendered_transactions = array();
|
||||||
|
|
||||||
$transactions = $conpherence->getTransactions();
|
$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) {
|
foreach ($transactions as $transaction) {
|
||||||
if ($transaction->shouldHide()) {
|
if ($transaction->shouldHide()) {
|
||||||
continue;
|
continue;
|
||||||
@@ -122,6 +146,7 @@ final class ConpherenceViewController extends
|
|||||||
->setUser($user)
|
->setUser($user)
|
||||||
->setConpherenceTransaction($transaction)
|
->setConpherenceTransaction($transaction)
|
||||||
->setHandles($handles)
|
->setHandles($handles)
|
||||||
|
->setMarkupEngine($engine)
|
||||||
->render();
|
->render();
|
||||||
}
|
}
|
||||||
$transactions = implode(' ', $rendered_transactions);
|
$transactions = implode(' ', $rendered_transactions);
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
|
|||||||
|
|
||||||
$types[] = ConpherenceTransactionType::TYPE_TITLE;
|
$types[] = ConpherenceTransactionType::TYPE_TITLE;
|
||||||
$types[] = ConpherenceTransactionType::TYPE_PICTURE;
|
$types[] = ConpherenceTransactionType::TYPE_PICTURE;
|
||||||
|
$types[] = ConpherenceTransactionType::TYPE_PICTURE_CROP;
|
||||||
$types[] = ConpherenceTransactionType::TYPE_PARTICIPANTS;
|
$types[] = ConpherenceTransactionType::TYPE_PARTICIPANTS;
|
||||||
$types[] = ConpherenceTransactionType::TYPE_FILES;
|
$types[] = ConpherenceTransactionType::TYPE_FILES;
|
||||||
|
|
||||||
@@ -62,7 +63,9 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
|
|||||||
case ConpherenceTransactionType::TYPE_TITLE:
|
case ConpherenceTransactionType::TYPE_TITLE:
|
||||||
return $object->getTitle();
|
return $object->getTitle();
|
||||||
case ConpherenceTransactionType::TYPE_PICTURE:
|
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:
|
case ConpherenceTransactionType::TYPE_PARTICIPANTS:
|
||||||
return $object->getParticipantPHIDs();
|
return $object->getParticipantPHIDs();
|
||||||
case ConpherenceTransactionType::TYPE_FILES:
|
case ConpherenceTransactionType::TYPE_FILES:
|
||||||
@@ -77,6 +80,7 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
|
|||||||
switch ($xaction->getTransactionType()) {
|
switch ($xaction->getTransactionType()) {
|
||||||
case ConpherenceTransactionType::TYPE_TITLE:
|
case ConpherenceTransactionType::TYPE_TITLE:
|
||||||
case ConpherenceTransactionType::TYPE_PICTURE:
|
case ConpherenceTransactionType::TYPE_PICTURE:
|
||||||
|
case ConpherenceTransactionType::TYPE_PICTURE_CROP:
|
||||||
return $xaction->getNewValue();
|
return $xaction->getNewValue();
|
||||||
case ConpherenceTransactionType::TYPE_PARTICIPANTS:
|
case ConpherenceTransactionType::TYPE_PARTICIPANTS:
|
||||||
case ConpherenceTransactionType::TYPE_FILES:
|
case ConpherenceTransactionType::TYPE_FILES:
|
||||||
@@ -93,7 +97,16 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
|
|||||||
$object->setTitle($xaction->getNewValue());
|
$object->setTitle($xaction->getNewValue());
|
||||||
break;
|
break;
|
||||||
case ConpherenceTransactionType::TYPE_PICTURE:
|
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;
|
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 $phids;
|
||||||
private $ids;
|
private $ids;
|
||||||
private $needWidgetData;
|
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) {
|
public function needWidgetData($need_widget_data) {
|
||||||
$this->needWidgetData = $need_widget_data;
|
$this->needWidgetData = $need_widget_data;
|
||||||
@@ -47,6 +59,12 @@ final class ConpherenceThreadQuery
|
|||||||
if ($this->needWidgetData) {
|
if ($this->needWidgetData) {
|
||||||
$this->loadWidgetData($conpherences);
|
$this->loadWidgetData($conpherences);
|
||||||
}
|
}
|
||||||
|
if ($this->needOrigPics) {
|
||||||
|
$this->loadOrigPics($conpherences);
|
||||||
|
}
|
||||||
|
if ($this->needHeaderPics) {
|
||||||
|
$this->loadHeaderPics($conpherences);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $conpherences;
|
return $conpherences;
|
||||||
@@ -187,4 +205,39 @@ final class ConpherenceThreadQuery
|
|||||||
return $this;
|
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 $id;
|
||||||
protected $phid;
|
protected $phid;
|
||||||
protected $title;
|
protected $title;
|
||||||
protected $imagePHID;
|
protected $imagePHIDs = array();
|
||||||
protected $mailKey;
|
protected $mailKey;
|
||||||
|
|
||||||
private $participants;
|
private $participants;
|
||||||
@@ -17,10 +17,14 @@ final class ConpherenceThread extends ConpherenceDAO
|
|||||||
private $handles;
|
private $handles;
|
||||||
private $filePHIDs;
|
private $filePHIDs;
|
||||||
private $widgetData;
|
private $widgetData;
|
||||||
|
private $images = array();
|
||||||
|
|
||||||
public function getConfiguration() {
|
public function getConfiguration() {
|
||||||
return array(
|
return array(
|
||||||
self::CONFIG_AUX_PHID => true,
|
self::CONFIG_AUX_PHID => true,
|
||||||
|
self::CONFIG_SERIALIZATION => array(
|
||||||
|
'imagePHIDs' => self::SERIALIZATION_JSON,
|
||||||
|
),
|
||||||
) + parent::getConfiguration();
|
) + parent::getConfiguration();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,6 +40,33 @@ final class ConpherenceThread extends ConpherenceDAO
|
|||||||
return parent::save();
|
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) {
|
public function attachParticipants(array $participants) {
|
||||||
assert_instances_of($participants, 'ConpherenceParticipant');
|
assert_instances_of($participants, 'ConpherenceParticipant');
|
||||||
$this->participants = $participants;
|
$this->participants = $participants;
|
||||||
@@ -112,59 +143,36 @@ final class ConpherenceThread extends ConpherenceDAO
|
|||||||
return $this->widgetData;
|
return $this->widgetData;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function loadImageURI() {
|
public function loadImageURI($size) {
|
||||||
$src_phid = $this->getImagePHID();
|
$file = $this->getImage($size);
|
||||||
|
|
||||||
if ($src_phid) {
|
if ($file) {
|
||||||
$file = id(new PhabricatorFile())->loadOneWhere('phid = %s', $src_phid);
|
return $file->getBestURI();
|
||||||
if ($file) {
|
|
||||||
return $file->getBestURI();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return PhabricatorUser::getDefaultProfileImageURI();
|
return PhabricatorUser::getDefaultProfileImageURI();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDisplayData(PhabricatorUser $user) {
|
public function getDisplayData(PhabricatorUser $user, $size) {
|
||||||
$transactions = $this->getTransactions();
|
$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 = '';
|
$handles = $this->getHandles();
|
||||||
$count = 0;
|
// we don't want to show the user unless they are babbling to themselves
|
||||||
$final = false;
|
if (count($handles) > 1) {
|
||||||
foreach ($handles as $handle) {
|
unset($handles[$user->getPHID()]);
|
||||||
if ($handle->getType() != PhabricatorPHIDConstants::PHID_TYPE_USER) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if ($subtitle) {
|
|
||||||
if ($final) {
|
|
||||||
$subtitle .= '...';
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
$subtitle .= ', ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$subtitle .= $handle->getName();
|
|
||||||
$count++;
|
|
||||||
$final = $count == 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$participants = $this->getParticipants();
|
$participants = $this->getParticipants();
|
||||||
$user_participation = $participants[$user->getPHID()];
|
$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;
|
$unread_count = 0;
|
||||||
$max_count = 10;
|
$max_count = 10;
|
||||||
$snippet = null;
|
$snippet = null;
|
||||||
@@ -186,9 +194,28 @@ final class ConpherenceThread extends ConpherenceDAO
|
|||||||
$transaction->getComment()->getContent(),
|
$transaction->getComment()->getContent(),
|
||||||
48
|
48
|
||||||
);
|
);
|
||||||
|
if ($transaction->getAuthorPHID() == $user->getPHID()) {
|
||||||
|
$snippet = "\xE2\x86\xB0 " . $snippet;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// fallthrough intentionally here
|
// fallthrough intentionally here
|
||||||
case ConpherenceTransactionType::TYPE_FILES:
|
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) {
|
if ($behind_transaction_phid) {
|
||||||
$unread_count++;
|
$unread_count++;
|
||||||
if ($transaction->getPHID() == $behind_transaction_phid) {
|
if ($transaction->getPHID() == $behind_transaction_phid) {
|
||||||
@@ -210,12 +237,45 @@ final class ConpherenceThread extends ConpherenceDAO
|
|||||||
$unread_count = $max_count.'+';
|
$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(
|
return array(
|
||||||
'title' => $title,
|
'title' => $title,
|
||||||
'subtitle' => $subtitle,
|
'subtitle' => $subtitle,
|
||||||
'unread_count' => $unread_count,
|
'unread_count' => $unread_count,
|
||||||
'epoch' => $latest_transaction->getDateCreated(),
|
'epoch' => $latest_transaction->getDateCreated(),
|
||||||
'image' => $img_src,
|
'image' => $img_src,
|
||||||
|
'image_class' => $img_class,
|
||||||
'snippet' => $snippet,
|
'snippet' => $snippet,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction {
|
|||||||
case ConpherenceTransactionType::TYPE_PICTURE:
|
case ConpherenceTransactionType::TYPE_PICTURE:
|
||||||
return false;
|
return false;
|
||||||
case ConpherenceTransactionType::TYPE_FILES:
|
case ConpherenceTransactionType::TYPE_FILES:
|
||||||
|
case ConpherenceTransactionType::TYPE_PICTURE_CROP:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,12 +108,10 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction {
|
|||||||
$phids[] = $this->getAuthorPHID();
|
$phids[] = $this->getAuthorPHID();
|
||||||
switch ($this->getTransactionType()) {
|
switch ($this->getTransactionType()) {
|
||||||
case ConpherenceTransactionType::TYPE_PICTURE:
|
case ConpherenceTransactionType::TYPE_PICTURE:
|
||||||
$phids[] = $new;
|
|
||||||
break;
|
|
||||||
case ConpherenceTransactionType::TYPE_TITLE:
|
case ConpherenceTransactionType::TYPE_TITLE:
|
||||||
|
case ConpherenceTransactionType::TYPE_FILES:
|
||||||
break;
|
break;
|
||||||
case ConpherenceTransactionType::TYPE_PARTICIPANTS:
|
case ConpherenceTransactionType::TYPE_PARTICIPANTS:
|
||||||
case ConpherenceTransactionType::TYPE_FILES:
|
|
||||||
$phids = array_merge($phids, $this->getOldValue());
|
$phids = array_merge($phids, $this->getOldValue());
|
||||||
$phids = array_merge($phids, $this->getNewValue());
|
$phids = array_merge($phids, $this->getNewValue());
|
||||||
break;
|
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 $conpherenceTransaction;
|
||||||
private $handles;
|
private $handles;
|
||||||
|
private $markupEngine;
|
||||||
|
|
||||||
|
public function setMarkupEngine(PhabricatorMarkupEngine $markup_engine) {
|
||||||
|
$this->markupEngine = $markup_engine;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function setHandles(array $handles) {
|
public function setHandles(array $handles) {
|
||||||
assert_instances_of($handles, 'PhabricatorObjectHandle');
|
assert_instances_of($handles, 'PhabricatorObjectHandle');
|
||||||
@@ -37,8 +43,11 @@ final class ConpherenceTransactionView extends AphrontView {
|
|||||||
|
|
||||||
$content = null;
|
$content = null;
|
||||||
$content_class = null;
|
$content_class = null;
|
||||||
|
$content = null;
|
||||||
switch ($transaction->getTransactionType()) {
|
switch ($transaction->getTransactionType()) {
|
||||||
case ConpherenceTransactionType::TYPE_TITLE:
|
case ConpherenceTransactionType::TYPE_TITLE:
|
||||||
|
case ConpherenceTransactionType::TYPE_PICTURE:
|
||||||
|
case ConpherenceTransactionType::TYPE_PICTURE_CROP:
|
||||||
$content = $transaction->getTitle();
|
$content = $transaction->getTitle();
|
||||||
$transaction_view->addClass('conpherence-edited');
|
$transaction_view->addClass('conpherence-edited');
|
||||||
break;
|
break;
|
||||||
@@ -47,12 +56,13 @@ final class ConpherenceTransactionView extends AphrontView {
|
|||||||
break;
|
break;
|
||||||
case ConpherenceTransactionType::TYPE_PICTURE:
|
case ConpherenceTransactionType::TYPE_PICTURE:
|
||||||
$img = $transaction->getHandle($transaction->getNewValue());
|
$img = $transaction->getHandle($transaction->getNewValue());
|
||||||
$content = $transaction->getTitle() .
|
$content = array(
|
||||||
|
$transaction->getTitle(),
|
||||||
phutil_tag(
|
phutil_tag(
|
||||||
'img',
|
'img',
|
||||||
array(
|
array(
|
||||||
'src' => $img->getImageURI()
|
'src' => $img->getImageURI()
|
||||||
));
|
)));
|
||||||
$transaction_view->addClass('conpherence-edited');
|
$transaction_view->addClass('conpherence-edited');
|
||||||
break;
|
break;
|
||||||
case ConpherenceTransactionType::TYPE_PARTICIPANTS:
|
case ConpherenceTransactionType::TYPE_PARTICIPANTS:
|
||||||
@@ -65,18 +75,9 @@ final class ConpherenceTransactionView extends AphrontView {
|
|||||||
PhabricatorMarkupEngine::extractFilePHIDsFromEmbeddedFiles(
|
PhabricatorMarkupEngine::extractFilePHIDsFromEmbeddedFiles(
|
||||||
array($comment->getContent())
|
array($comment->getContent())
|
||||||
);
|
);
|
||||||
$markup_field = ConpherenceTransactionComment::MARKUP_FIELD_COMMENT;
|
$content = $this->markupEngine->getOutput(
|
||||||
$engine = id(new PhabricatorMarkupEngine())
|
|
||||||
->setViewer($this->getUser());
|
|
||||||
$engine->addObject(
|
|
||||||
$comment,
|
$comment,
|
||||||
$markup_field
|
PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);
|
||||||
);
|
|
||||||
$engine->process();
|
|
||||||
$content = $engine->getOutput(
|
|
||||||
$comment,
|
|
||||||
$markup_field
|
|
||||||
);
|
|
||||||
$content_class = 'conpherence-message phabricator-remarkup';
|
$content_class = 'conpherence-message phabricator-remarkup';
|
||||||
$transaction_view
|
$transaction_view
|
||||||
->setImageURI($author->getImageURI())
|
->setImageURI($author->getImageURI())
|
||||||
@@ -90,7 +91,7 @@ final class ConpherenceTransactionView extends AphrontView {
|
|||||||
array(
|
array(
|
||||||
'class' => $content_class
|
'class' => $content_class
|
||||||
),
|
),
|
||||||
new PhutilSafeHTML($content))
|
$this->renderHTMLView($content))
|
||||||
);
|
);
|
||||||
|
|
||||||
return $transaction_view->render();
|
return $transaction_view->render();
|
||||||
|
|||||||
@@ -28,6 +28,12 @@ final class PhabricatorApplicationDifferential extends PhabricatorApplication {
|
|||||||
return "\xE2\x9A\x99";
|
return "\xE2\x9A\x99";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getEventListeners() {
|
||||||
|
return array(
|
||||||
|
new DifferentialPeopleMenuEventListener()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function getRoutes() {
|
public function getRoutes() {
|
||||||
return array(
|
return array(
|
||||||
'/D(?P<id>[1-9]\d*)' => 'DifferentialRevisionViewController',
|
'/D(?P<id>[1-9]\d*)' => 'DifferentialRevisionViewController',
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ final class ConduitAPI_differential_getdiff_Method extends ConduitAPIMethod {
|
|||||||
$project_name = null;
|
$project_name = null;
|
||||||
}
|
}
|
||||||
$basic_dict['projectName'] = $project_name;
|
$basic_dict['projectName'] = $project_name;
|
||||||
$basic_dict['author'] = $diff->loadAuthorInformation();
|
|
||||||
|
|
||||||
return $basic_dict;
|
return $basic_dict;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,28 +127,6 @@ final class PhabricatorDifferentialConfigOptions
|
|||||||
"If you set this to true, users won't need to login to view ".
|
"If you set this to true, users won't need to login to view ".
|
||||||
"Differential revisions. Anonymous users will have read-only ".
|
"Differential revisions. Anonymous users will have read-only ".
|
||||||
"access and won't be able to interact with the revisions.")),
|
"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())
|
$this->newOption('differential.generated-paths', 'list<string>', array())
|
||||||
->setSummary(pht("File regexps to treat as automatically generated."))
|
->setSummary(pht("File regexps to treat as automatically generated."))
|
||||||
->setDescription(
|
->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());
|
$this->getID());
|
||||||
foreach ($properties as $property) {
|
foreach ($properties as $property) {
|
||||||
$dict['properties'][$property->getName()] = $property->getData();
|
$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;
|
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() {
|
public function getRoutes() {
|
||||||
return array(
|
return array(
|
||||||
'/r(?P<callsign>[A-Z]+)(?P<commit>[a-z0-9]+)'
|
'/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) {
|
private function crudelyCropTo(PhabricatorFile $file, $x, $min_y, $max_y) {
|
||||||
$data = $file->loadFileData();
|
$data = $file->loadFileData();
|
||||||
@@ -80,6 +103,30 @@ final class PhabricatorImageTransformer {
|
|||||||
return $this->saveImageDataInAnyFormat($img, $file->getMimeType());
|
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.
|
* 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());
|
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) {
|
private function applyScaleTo($src, $dx, $dy) {
|
||||||
$x = imagesx($src);
|
$x = imagesx($src);
|
||||||
$y = imagesy($src);
|
$y = imagesy($src);
|
||||||
|
|
||||||
$scale = min(($dx / $x), ($dy / $y), 1);
|
$scale = min(($dx / $x), ($dy / $y), 1);
|
||||||
|
|
||||||
$dst = imagecreatetruecolor($dx, $dy);
|
$dst = $this->getBlankDestinationFile($dx, $dy);
|
||||||
imagesavealpha($dst, true);
|
|
||||||
imagefill($dst, 0, 0, imagecolorallocatealpha($dst, 255, 255, 255, 127));
|
|
||||||
|
|
||||||
$sdx = $scale * $x;
|
$sdx = $scale * $x;
|
||||||
$sdy = $scale * $y;
|
$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) {
|
private function generatePreview(PhabricatorFile $file, $size) {
|
||||||
$data = $file->loadFileData();
|
$data = $file->loadFileData();
|
||||||
$src = imagecreatefromstring($data);
|
$src = imagecreatefromstring($data);
|
||||||
@@ -153,9 +227,7 @@ final class PhabricatorImageTransformer {
|
|||||||
$sdx = $dimensions['sdx'];
|
$sdx = $dimensions['sdx'];
|
||||||
$sdy = $dimensions['sdy'];
|
$sdy = $dimensions['sdy'];
|
||||||
|
|
||||||
$dst = imagecreatetruecolor($dx, $dy);
|
$dst = $this->getBlankDestinationFile($dx, $dy);
|
||||||
imagesavealpha($dst, true);
|
|
||||||
imagefill($dst, 0, 0, imagecolorallocatealpha($dst, 255, 255, 255, 127));
|
|
||||||
|
|
||||||
imagecopyresampled(
|
imagecopyresampled(
|
||||||
$dst,
|
$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()) {
|
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) {
|
if (!$engines) {
|
||||||
throw new Exception("No valid storage engines are available!");
|
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/';
|
return $this->getBaseURI().'task/create/';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getEventListeners() {
|
||||||
|
return array(
|
||||||
|
new ManiphestPeopleMenuEventListener()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function getRoutes() {
|
public function getRoutes() {
|
||||||
return array(
|
return array(
|
||||||
'/T(?P<id>[1-9]\d*)' => 'ManiphestTaskDetailController',
|
'/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();
|
$current_user = $request->getUser();
|
||||||
$server = new PhabricatorOAuthServer();
|
$server = new PhabricatorOAuthServer();
|
||||||
$client_phid = $request->getStr('client_id');
|
$client_phid = $request->getStr('client_id');
|
||||||
$scope = $request->getStr('scope');
|
$scope = $request->getStr('scope', array());
|
||||||
$redirect_uri = $request->getStr('redirect_uri');
|
$redirect_uri = $request->getStr('redirect_uri');
|
||||||
$state = $request->getStr('state');
|
$state = $request->getStr('state');
|
||||||
$response_type = $request->getStr('response_type');
|
$response_type = $request->getStr('response_type');
|
||||||
@@ -63,10 +63,8 @@ extends PhabricatorAuthController {
|
|||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
$uri = $redirect_uri;
|
$uri = $redirect_uri;
|
||||||
$access_token_uri = $uri;
|
|
||||||
} else {
|
} else {
|
||||||
$uri = new PhutilURI($client->getRedirectURI());
|
$uri = new PhutilURI($client->getRedirectURI());
|
||||||
$access_token_uri = null;
|
|
||||||
}
|
}
|
||||||
// we've now validated this request enough overall such that we
|
// we've now validated this request enough overall such that we
|
||||||
// can safely redirect to the client with the response
|
// can safely redirect to the client with the response
|
||||||
@@ -121,7 +119,7 @@ extends PhabricatorAuthController {
|
|||||||
if ($return_auth_code) {
|
if ($return_auth_code) {
|
||||||
// step 1 -- generate authorization code
|
// step 1 -- generate authorization code
|
||||||
$auth_code =
|
$auth_code =
|
||||||
$server->generateAuthorizationCode($access_token_uri);
|
$server->generateAuthorizationCode($uri);
|
||||||
|
|
||||||
// step 2 return it
|
// step 2 return it
|
||||||
$content = array(
|
$content = array(
|
||||||
|
|||||||
@@ -16,6 +16,21 @@ final class PhabricatorPeopleProfileController
|
|||||||
return $this->profileUser;
|
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() {
|
public function processRequest() {
|
||||||
|
|
||||||
$viewer = $this->getRequest()->getUser();
|
$viewer = $this->getRequest()->getUser();
|
||||||
@@ -39,40 +54,14 @@ final class PhabricatorPeopleProfileController
|
|||||||
}
|
}
|
||||||
$username = phutil_escape_uri($user->getUserName());
|
$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 =
|
$menu->newLabel(pht('Activity'), 'activity');
|
||||||
new PhutilURI('/conpherence/new/?participant='.$user->getPHID());
|
// NOTE: applications install the various links through PhabricatorEvent
|
||||||
$nav = new AphrontSideNavFilterView();
|
// listeners
|
||||||
$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());
|
|
||||||
|
|
||||||
$oauths = id(new PhabricatorUserOAuthInfo())->loadAllWhere(
|
$oauths = id(new PhabricatorUserOAuthInfo())->loadAllWhere(
|
||||||
'userID = %d',
|
'userID = %d',
|
||||||
@@ -92,18 +81,34 @@ final class PhabricatorPeopleProfileController
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$name = $provider->getProviderName().' Profile';
|
$name = pht('%s Profile', $provider->getProviderName());
|
||||||
$href = $oauths[$provider_key]->getAccountURI();
|
$href = $oauths[$provider_key]->getAccountURI();
|
||||||
|
|
||||||
if ($href) {
|
if ($href) {
|
||||||
if (!$added_label) {
|
if (!$added_label) {
|
||||||
$nav->addLabel('Linked Accounts');
|
$menu->newLabel(pht('Linked Accounts'), 'linked_accounts');
|
||||||
$added_label = true;
|
$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');
|
$this->page = $nav->selectFilter($this->page, 'feed');
|
||||||
|
|
||||||
switch ($this->page) {
|
switch ($this->page) {
|
||||||
@@ -141,14 +146,19 @@ final class PhabricatorPeopleProfileController
|
|||||||
$header->appendChild($content);
|
$header->appendChild($content);
|
||||||
|
|
||||||
if ($user->getPHID() == $viewer->getPHID()) {
|
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()) {
|
if ($viewer->getIsAdmin()) {
|
||||||
$nav->addFilter(
|
$nav->addFilter(
|
||||||
null,
|
null,
|
||||||
'Administrate User...',
|
pht('Administrate User...'),
|
||||||
'/people/edit/'.$user->getID().'/');
|
'/people/edit/'.$user->getID().'/'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->buildApplicationPage(
|
return $this->buildApplicationPage(
|
||||||
@@ -162,7 +172,10 @@ final class PhabricatorPeopleProfileController
|
|||||||
|
|
||||||
$blurb = nonempty(
|
$blurb = nonempty(
|
||||||
$profile->getBlurb(),
|
$profile->getBlurb(),
|
||||||
'//Nothing is known about this rare specimen.//');
|
'//'.
|
||||||
|
pht('Nothing is known about this rare specimen.')
|
||||||
|
.'//'
|
||||||
|
);
|
||||||
|
|
||||||
$engine = PhabricatorMarkupEngine::newProfileMarkupEngine();
|
$engine = PhabricatorMarkupEngine::newProfileMarkupEngine();
|
||||||
$blurb = phutil_safe_html($engine->markupText($blurb));
|
$blurb = phutil_safe_html($engine->markupText($blurb));
|
||||||
|
|||||||
@@ -413,9 +413,6 @@ final class PhabricatorObjectHandleData {
|
|||||||
$handle->setName($file->getName());
|
$handle->setName($file->getName());
|
||||||
$handle->setURI($file->getBestURI());
|
$handle->setURI($file->getBestURI());
|
||||||
$handle->setComplete(true);
|
$handle->setComplete(true);
|
||||||
if ($file->isViewableImage()) {
|
|
||||||
$handle->setImageURI($file->getBestURI());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$handles[$phid] = $handle;
|
$handles[$phid] = $handle;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ final class PhabricatorApplicationPholio extends PhabricatorApplication {
|
|||||||
'new/' => 'PholioMockEditController',
|
'new/' => 'PholioMockEditController',
|
||||||
'edit/(?P<id>\d+)/' => 'PholioMockEditController',
|
'edit/(?P<id>\d+)/' => 'PholioMockEditController',
|
||||||
'comment/(?P<id>\d+)/' => 'PholioMockCommentController',
|
'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_NAME = 'name';
|
||||||
const TYPE_DESCRIPTION = 'description';
|
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())
|
$mock = id(new PholioMockQuery())
|
||||||
->setViewer($user)
|
->setViewer($user)
|
||||||
->withIDs(array($this->id))
|
->withIDs(array($this->id))
|
||||||
|
->needImages(true)
|
||||||
->executeOne();
|
->executeOne();
|
||||||
|
|
||||||
if (!$mock) {
|
if (!$mock) {
|
||||||
@@ -49,6 +50,18 @@ final class PholioMockCommentController extends PholioController {
|
|||||||
id(new PholioTransactionComment())
|
id(new PholioTransactionComment())
|
||||||
->setContent($comment));
|
->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())
|
$editor = id(new PholioMockEditor())
|
||||||
->setActor($user)
|
->setActor($user)
|
||||||
->setContentSource($content_source)
|
->setContentSource($content_source)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor {
|
|||||||
|
|
||||||
$types[] = PholioTransactionType::TYPE_NAME;
|
$types[] = PholioTransactionType::TYPE_NAME;
|
||||||
$types[] = PholioTransactionType::TYPE_DESCRIPTION;
|
$types[] = PholioTransactionType::TYPE_DESCRIPTION;
|
||||||
|
$types[] = PholioTransactionType::TYPE_INLINE;
|
||||||
return $types;
|
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(
|
protected function applyCustomInternalTransaction(
|
||||||
PhabricatorLiskDAO $object,
|
PhabricatorLiskDAO $object,
|
||||||
PhabricatorApplicationTransaction $xaction) {
|
PhabricatorApplicationTransaction $xaction) {
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ final class PholioMockImagesView extends AphrontView {
|
|||||||
|
|
||||||
$main_image_id = celerity_generate_unique_node_id();
|
$main_image_id = celerity_generate_unique_node_id();
|
||||||
require_celerity_resource('javelin-behavior-pholio-mock-view');
|
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);
|
Javelin::initBehavior('pholio-mock-view', $config);
|
||||||
|
|
||||||
$mockview = "";
|
$mockview = "";
|
||||||
|
|||||||
@@ -75,9 +75,12 @@ final class PhabricatorApplicationTransactionCommentEditor
|
|||||||
"Transaction must have a PHID before calling applyEdit()!");
|
"Transaction must have a PHID before calling applyEdit()!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($comment->getPHID()) {
|
$type_comment = PhabricatorTransactions::TYPE_COMMENT;
|
||||||
throw new Exception(
|
if ($xaction->getTransactionType() == $type_comment) {
|
||||||
|
if ($comment->getPHID()) {
|
||||||
|
throw new Exception(
|
||||||
"Transaction comment must not yet have a PHID!");
|
"Transaction comment must not yet have a PHID!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->getContentSource()) {
|
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
|
* @group irc
|
||||||
*/
|
*/
|
||||||
final class PhabricatorIRCDifferentialNotificationHandler
|
final class PhabricatorBotDifferentialNotificationHandler
|
||||||
extends PhabricatorIRCHandler {
|
extends PhabricatorBotHandler {
|
||||||
|
|
||||||
private $skippedOldEvents;
|
private $skippedOldEvents;
|
||||||
|
|
||||||
public function receiveMessage(PhabricatorIRCMessage $message) {
|
public function receiveMessage(PhabricatorBotMessage $message) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,11 +39,16 @@ final class PhabricatorIRCDifferentialNotificationHandler
|
|||||||
$verb = DifferentialAction::getActionPastTenseVerb($data['action']);
|
$verb = DifferentialAction::getActionPastTenseVerb($data['action']);
|
||||||
|
|
||||||
$actor_name = $handles[$actor_phid]->getName();
|
$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());
|
$channels = $this->getConfig('notification.channels', array());
|
||||||
foreach ($channels as $channel) {
|
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
|
* @group irc
|
||||||
*/
|
*/
|
||||||
final class PhabricatorIRCFeedNotificationHandler
|
final class PhabricatorBotFeedNotificationHandler
|
||||||
extends PhabricatorIRCHandler {
|
extends PhabricatorBotHandler {
|
||||||
|
|
||||||
private $startupDelay = 30;
|
private $startupDelay = 30;
|
||||||
private $lastSeenChronoKey = 0;
|
private $lastSeenChronoKey = 0;
|
||||||
@@ -82,7 +82,7 @@ final class PhabricatorIRCFeedNotificationHandler
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function receiveMessage(PhabricatorIRCMessage $message) {
|
public function receiveMessage(PhabricatorBotMessage $message) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +150,11 @@ final class PhabricatorIRCFeedNotificationHandler
|
|||||||
|
|
||||||
$channels = $this->getConfig('join');
|
$channels = $this->getConfig('join');
|
||||||
foreach ($channels as $channel) {
|
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
|
* @group irc
|
||||||
*/
|
*/
|
||||||
final class PhabricatorIRCLogHandler extends PhabricatorIRCHandler {
|
final class PhabricatorBotLogHandler extends PhabricatorBotHandler {
|
||||||
|
|
||||||
private $futures = array();
|
private $futures = array();
|
||||||
|
|
||||||
public function receiveMessage(PhabricatorIRCMessage $message) {
|
public function receiveMessage(PhabricatorBotMessage $message) {
|
||||||
|
|
||||||
switch ($message->getCommand()) {
|
switch ($message->getCommand()) {
|
||||||
case 'PRIVMSG':
|
case 'MESSAGE':
|
||||||
$reply_to = $message->getReplyTo();
|
$reply_to = $message->getReplyTo();
|
||||||
if (!$reply_to) {
|
if (!$reply_to) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!$this->isChannelName($reply_to)) {
|
if (!$message->isPublic()) {
|
||||||
// Don't log private messages, although maybe we should for debugging?
|
// Don't log private messages, although maybe we should for debugging?
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -27,8 +27,8 @@ final class PhabricatorIRCLogHandler extends PhabricatorIRCHandler {
|
|||||||
'channel' => $reply_to,
|
'channel' => $reply_to,
|
||||||
'type' => 'mesg',
|
'type' => 'mesg',
|
||||||
'epoch' => time(),
|
'epoch' => time(),
|
||||||
'author' => $message->getSenderNickname(),
|
'author' => $message->getSender(),
|
||||||
'message' => $message->getMessageText(),
|
'message' => $message->getBody(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ final class PhabricatorIRCLogHandler extends PhabricatorIRCHandler {
|
|||||||
|
|
||||||
$tell = false;
|
$tell = false;
|
||||||
foreach ($prompts as $prompt) {
|
foreach ($prompts as $prompt) {
|
||||||
if (preg_match($prompt, $message->getMessageText())) {
|
if (preg_match($prompt, $message->getBody())) {
|
||||||
$tell = true;
|
$tell = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,8 @@ final class PhabricatorIRCLogHandler extends PhabricatorIRCHandler {
|
|||||||
if ($tell) {
|
if ($tell) {
|
||||||
$response = $this->getURI(
|
$response = $this->getURI(
|
||||||
'/chatlog/channel/'.phutil_escape_uri($reply_to).'/');
|
'/chatlog/channel/'.phutil_escape_uri($reply_to).'/');
|
||||||
$this->write('PRIVMSG', "{$reply_to} :{$response}");
|
|
||||||
|
$this->replyTo($message, $response);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
/**
|
/**
|
||||||
* @group irc
|
* @group irc
|
||||||
*/
|
*/
|
||||||
final class PhabricatorIRCMacroHandler extends PhabricatorIRCHandler {
|
final class PhabricatorBotMacroHandler extends PhabricatorBotHandler {
|
||||||
|
|
||||||
private $macros;
|
private $macros;
|
||||||
private $regexp;
|
private $regexp;
|
||||||
@@ -40,44 +40,44 @@ final class PhabricatorIRCMacroHandler extends PhabricatorIRCHandler {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function receiveMessage(PhabricatorIRCMessage $message) {
|
public function receiveMessage(PhabricatorBotMessage $message) {
|
||||||
if (!$this->init()) {
|
if (!$this->init()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ($message->getCommand()) {
|
switch ($message->getCommand()) {
|
||||||
case 'PRIVMSG':
|
case 'MESSAGE':
|
||||||
$reply_to = $message->getReplyTo();
|
$reply_to = $message->getReplyTo();
|
||||||
if (!$reply_to) {
|
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;
|
|
||||||
}
|
|
||||||
break;
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
foreach ($lines as $key => $line) {
|
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]);
|
unset($this->buffer[$channel][$key]);
|
||||||
break 2;
|
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
|
* @group irc
|
||||||
*/
|
*/
|
||||||
final class PhabricatorIRCWhatsNewHandler extends PhabricatorIRCHandler {
|
final class PhabricatorBotWhatsNewHandler extends PhabricatorBotHandler {
|
||||||
|
|
||||||
private $floodblock = 0;
|
private $floodblock = 0;
|
||||||
|
|
||||||
public function receiveMessage(PhabricatorIRCMessage $message) {
|
public function receiveMessage(PhabricatorBotMessage $message) {
|
||||||
|
|
||||||
switch ($message->getCommand()) {
|
switch ($message->getCommand()) {
|
||||||
case 'PRIVMSG':
|
case 'MESSAGE':
|
||||||
$reply_to = $message->getReplyTo();
|
$reply_to = $message->getReplyTo();
|
||||||
if (!$reply_to) {
|
if (!$reply_to) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$message = $message->getMessageText();
|
$message_body = $message->getBody();
|
||||||
|
|
||||||
$prompt = '~what( i|\')?s new\?~i';
|
$prompt = '~what( i|\')?s new\?~i';
|
||||||
if (preg_match($prompt, $message)) {
|
if (preg_match($prompt, $message_body)) {
|
||||||
if (time() < $this->floodblock) {
|
if (time() < $this->floodblock) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -108,9 +108,15 @@ final class PhabricatorIRCWhatsNewHandler extends PhabricatorIRCHandler {
|
|||||||
$gray = $color.'15';
|
$gray = $color.'15';
|
||||||
$bold = chr(2);
|
$bold = chr(2);
|
||||||
$reset = chr(15);
|
$reset = chr(15);
|
||||||
$content = "{$bold}{$user}{$reset} {$gray}{$action} {$blue}{$bold}".
|
// Disabling irc-specific styling, at least for now
|
||||||
"{$title}{$reset} - {$gray}{$uri}{$reset}";
|
// $content = "{$bold}{$user}{$reset} {$gray}{$action} {$blue}{$bold}".
|
||||||
$this->write('PRIVMSG',"{$reply_to} :{$content}");
|
// "{$title}{$reset} - {$gray}{$uri}{$reset}";
|
||||||
|
$content = "{$user} {$action} {$title} - {$uri}";
|
||||||
|
$this->writeMessage(
|
||||||
|
id(new PhabricatorBotMessage())
|
||||||
|
->setCommand('MESSAGE')
|
||||||
|
->setTarget($reply_to)
|
||||||
|
->setBody($content));
|
||||||
}
|
}
|
||||||
return;
|
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_DDIDRENDEROBJECT = 'ui.didRenderObject';
|
||||||
const TYPE_UI_DIDRENDEROBJECTS = 'ui.didRenderObjects';
|
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());
|
$file_name = coalesce($options['name'], $file->getName());
|
||||||
$options['name'] = $file_name;
|
$options['name'] = $file_name;
|
||||||
|
|
||||||
|
$is_viewable_image = $file->isViewableImage();
|
||||||
|
|
||||||
$attrs = array();
|
$attrs = array();
|
||||||
switch ((string)$options['size']) {
|
if ($is_viewable_image) {
|
||||||
case 'full':
|
switch ((string)$options['size']) {
|
||||||
$attrs['src'] = $file->getBestURI();
|
case 'full':
|
||||||
$options['image_class'] = null;
|
$attrs['src'] = $file->getBestURI();
|
||||||
$file_data = $file->getMetadata();
|
$options['image_class'] = null;
|
||||||
$height = idx($file_data, PhabricatorFile::METADATA_IMAGE_HEIGHT);
|
$file_data = $file->getMetadata();
|
||||||
if ($height) {
|
$height = idx($file_data, PhabricatorFile::METADATA_IMAGE_HEIGHT);
|
||||||
$attrs['height'] = $height;
|
if ($height) {
|
||||||
}
|
$attrs['height'] = $height;
|
||||||
$width = idx($file_data, PhabricatorFile::METADATA_IMAGE_WIDTH);
|
}
|
||||||
if ($width) {
|
$width = idx($file_data, PhabricatorFile::METADATA_IMAGE_WIDTH);
|
||||||
$attrs['width'] = $width;
|
if ($width) {
|
||||||
}
|
$attrs['width'] = $width;
|
||||||
break;
|
}
|
||||||
case 'thumb':
|
break;
|
||||||
default:
|
case 'thumb':
|
||||||
$attrs['src'] = $file->getPreview220URI();
|
default:
|
||||||
$dimensions =
|
$attrs['src'] = $file->getPreview220URI();
|
||||||
PhabricatorImageTransformer::getPreviewDimensions($file, 220);
|
$dimensions =
|
||||||
$attrs['width'] = $dimensions['sdx'];
|
PhabricatorImageTransformer::getPreviewDimensions($file, 220);
|
||||||
$attrs['height'] = $dimensions['sdy'];
|
$attrs['width'] = $dimensions['sdx'];
|
||||||
$options['image_class'] = 'phabricator-remarkup-embed-image';
|
$attrs['height'] = $dimensions['sdy'];
|
||||||
break;
|
$options['image_class'] = 'phabricator-remarkup-embed-image';
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$bundle['attrs'] = $attrs;
|
$bundle['attrs'] = $attrs;
|
||||||
$bundle['options'] = $options;
|
$bundle['options'] = $options;
|
||||||
|
|
||||||
$bundle['meta'] = array(
|
$bundle['meta'] = array(
|
||||||
'phid' => $file->getPHID(),
|
'phid' => $file->getPHID(),
|
||||||
'viewable' => $file->isViewableImage(),
|
'viewable' => $is_viewable_image,
|
||||||
'uri' => $file->getBestURI(),
|
'uri' => $file->getBestURI(),
|
||||||
'dUri' => $file->getDownloadURI(),
|
'dUri' => $file->getDownloadURI(),
|
||||||
'name' => $options['name'],
|
'name' => $options['name'],
|
||||||
|
|||||||
@@ -1105,6 +1105,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
|
|||||||
'type' => 'sql',
|
'type' => 'sql',
|
||||||
'name' => $this->getPatchPath('20130201.revisionunsubscribed.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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function newLabel($name) {
|
public function newLabel($name, $key = null) {
|
||||||
$item = id(new PhabricatorMenuItemView())
|
$item = id(new PhabricatorMenuItemView())
|
||||||
->setType(PhabricatorMenuItemView::TYPE_LABEL)
|
->setType(PhabricatorMenuItemView::TYPE_LABEL)
|
||||||
->setName($name);
|
->setName($name);
|
||||||
|
|
||||||
|
if ($key !== null) {
|
||||||
|
$item->setKey($key);
|
||||||
|
}
|
||||||
|
|
||||||
$this->addMenuItem($item);
|
$this->addMenuItem($item);
|
||||||
|
|
||||||
return $item;
|
return $item;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function newLink($name, $href) {
|
public function newLink($name, $href, $key = null) {
|
||||||
$item = id(new PhabricatorMenuItemView())
|
$item = id(new PhabricatorMenuItemView())
|
||||||
->setType(PhabricatorMenuItemView::TYPE_LINK)
|
->setType(PhabricatorMenuItemView::TYPE_LINK)
|
||||||
->setName($name)
|
->setName($name)
|
||||||
->setHref($href);
|
->setHref($href);
|
||||||
|
|
||||||
|
if ($key !== null) {
|
||||||
|
$item->setKey($key);
|
||||||
|
}
|
||||||
|
|
||||||
$this->addMenuItem($item);
|
$this->addMenuItem($item);
|
||||||
|
|
||||||
return $item;
|
return $item;
|
||||||
|
|||||||
@@ -231,6 +231,18 @@ table.aphront-form-control-checkbox-layout th {
|
|||||||
border-color: #669966;
|
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 {
|
.calendar-button {
|
||||||
display: inline;
|
display: inline;
|
||||||
background: url(/rsrc/image/icon/fatcow/calendar_edit.png)
|
background: url(/rsrc/image/icon/fatcow/calendar_edit.png)
|
||||||
|
|||||||
@@ -29,6 +29,14 @@
|
|||||||
width: 50px;
|
width: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.conpherence-header-pane .custom-header-image {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
height: 80px;
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
.conpherence-header-pane .title {
|
.conpherence-header-pane .title {
|
||||||
position: relative;
|
position: relative;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
@@ -39,6 +47,16 @@
|
|||||||
overflow-x: auto;
|
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 {
|
.conpherence-header-pane .subtitle {
|
||||||
position: relative;
|
position: relative;
|
||||||
left: 77px;
|
left: 77px;
|
||||||
@@ -47,4 +65,34 @@
|
|||||||
max-width: 80%;
|
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 {
|
.phabricator-standard-page-body .aphront-dialog-view {
|
||||||
margin: 20px auto 0px auto;
|
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;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pholio-mock-select {
|
.pholio-mock-select-border {
|
||||||
border: 1px solid #FF0000;
|
position: absolute;
|
||||||
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 {
|
.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 messagesRoot = JX.$(config.messages);
|
||||||
var formRoot = JX.$(config.form_pane);
|
var formRoot = JX.$(config.form_pane);
|
||||||
var widgetsRoot = JX.$(config.widgets_pane);
|
var widgetsRoot = JX.$(config.widgets_pane);
|
||||||
|
var menuRoot = JX.$(config.menu_pane);
|
||||||
JX.DOM.setContent(headerRoot, header);
|
JX.DOM.setContent(headerRoot, header);
|
||||||
JX.DOM.setContent(messagesRoot, messages);
|
JX.DOM.setContent(messagesRoot, messages);
|
||||||
messagesRoot.scrollTop = messagesRoot.scrollHeight;
|
messagesRoot.scrollTop = messagesRoot.scrollHeight;
|
||||||
JX.DOM.setContent(formRoot, form);
|
JX.DOM.setContent(formRoot, form);
|
||||||
JX.DOM.setContent(widgetsRoot, widgets);
|
JX.DOM.setContent(widgetsRoot, widgets);
|
||||||
|
|
||||||
for (var i = 0; i < context.parentNode.childNodes.length; i++) {
|
var conpherences = JX.DOM.scry(
|
||||||
var current = context.parentNode.childNodes[i];
|
menuRoot,
|
||||||
|
'a',
|
||||||
|
'conpherence-menu-click'
|
||||||
|
);
|
||||||
|
|
||||||
|
for (var i = 0; i < conpherences.length; i++) {
|
||||||
|
var current = conpherences[i];
|
||||||
if (current.id == context.id) {
|
if (current.id == context.id) {
|
||||||
JX.DOM.alterClass(current, 'conpherence-selected', true);
|
JX.DOM.alterClass(current, 'conpherence-selected', true);
|
||||||
JX.DOM.alterClass(current, 'hide-unread-count', 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-stratcom
|
||||||
* javelin-dom
|
* javelin-dom
|
||||||
* javelin-vector
|
* javelin-vector
|
||||||
* javelin-event
|
* javelin-magical-init
|
||||||
|
* javelin-request
|
||||||
*/
|
*/
|
||||||
JX.behavior('pholio-mock-view', function(config) {
|
JX.behavior('pholio-mock-view', function(config) {
|
||||||
var is_dragging = false;
|
var is_dragging = false;
|
||||||
@@ -13,7 +14,9 @@ JX.behavior('pholio-mock-view', function(config) {
|
|||||||
var imageData;
|
var imageData;
|
||||||
var startPos;
|
var startPos;
|
||||||
var endPos;
|
var endPos;
|
||||||
var selection;
|
|
||||||
|
var selection_border;
|
||||||
|
var selection_fill;
|
||||||
|
|
||||||
JX.Stratcom.listen(
|
JX.Stratcom.listen(
|
||||||
'click', // Listen for clicks...
|
'click', // Listen for clicks...
|
||||||
@@ -35,26 +38,28 @@ JX.behavior('pholio-mock-view', function(config) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
function draw_rectangle(node, current, init) {
|
function draw_rectangle(nodes, current, init) {
|
||||||
JX.$V(
|
for (var ii = 0; ii < nodes.length; ii++) {
|
||||||
Math.abs(current.x-init.x),
|
var node = nodes[ii];
|
||||||
Math.abs(current.y-init.y))
|
|
||||||
.setDim(node);
|
|
||||||
|
|
||||||
JX.$V(
|
JX.$V(
|
||||||
(current.x-init.x < 0) ? current.x:init.x,
|
Math.abs(current.x-init.x),
|
||||||
(current.y-init.y < 0) ? current.y:init.y)
|
Math.abs(current.y-init.y))
|
||||||
.setPos(node);
|
.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) {
|
function getRealXY(parent, point) {
|
||||||
var pos = {x: (point.x - parent.x), y: (point.y - parent.y)};
|
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;
|
pos.x = Math.max(0, Math.min(pos.x, dim.x));
|
||||||
else if (pos.x > image.clientWidth) pos.x = image.clientWidth - 1;
|
pos.y = Math.max(0, Math.min(pos.y, dim.y));
|
||||||
|
|
||||||
if (pos.y < 0) pos.y = 0;
|
|
||||||
else if (pos.y > image.clientHeight) pos.y = image.clientHeight - 2;
|
|
||||||
|
|
||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
@@ -72,17 +77,18 @@ JX.behavior('pholio-mock-view', function(config) {
|
|||||||
|
|
||||||
startPos = getRealXY(JX.$V(wrapper),JX.$V(e));
|
startPos = getRealXY(JX.$V(wrapper),JX.$V(e));
|
||||||
|
|
||||||
selection = JX.$N(
|
selection_border = JX.$N(
|
||||||
'div',
|
'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.$V(startPos.x, startPos.y).setPos(selection_border);
|
||||||
|
JX.$V(startPos.x, startPos.y).setPos(selection_fill);
|
||||||
JX.DOM.appendContent(wrapper, selection);
|
|
||||||
|
|
||||||
|
|
||||||
|
JX.DOM.appendContent(wrapper, [selection_border, selection_fill]);
|
||||||
});
|
});
|
||||||
|
|
||||||
JX.enableDispatch(document.body, 'mousemove');
|
JX.enableDispatch(document.body, 'mousemove');
|
||||||
@@ -91,7 +97,10 @@ JX.behavior('pholio-mock-view', function(config) {
|
|||||||
return;
|
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(
|
JX.Stratcom.listen(
|
||||||
@@ -107,17 +116,31 @@ JX.behavior('pholio-mock-view', function(config) {
|
|||||||
|
|
||||||
comment = window.prompt("Add your comment");
|
comment = window.prompt("Add your comment");
|
||||||
if (comment == null || comment == "") {
|
if (comment == null || comment == "") {
|
||||||
selection.remove();
|
JX.DOM.remove(selection_border);
|
||||||
|
JX.DOM.remove(selection_fill);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
selection.title = comment;
|
selection_fill.title = comment;
|
||||||
|
|
||||||
console.log("ImageID: " + imageData['imageID'] +
|
var saveURL = "/pholio/inline/" + imageData['imageID'] + "/";
|
||||||
", coords: (" + Math.min(startPos.x, endPos.x) + "," +
|
|
||||||
Math.min(startPos.y, endPos.y) + ") -> (" +
|
var inlineComment = new JX.Request(saveURL, function(r) {
|
||||||
Math.max(startPos.x,endPos.x) + "," + Math.max(startPos.y,endPos.y) +
|
|
||||||
"), comment: " + comment);
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user