mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-30 02:32:42 +01:00
Merge branch 'master' of github.com:facebook/phabricator
This commit is contained in:
commit
ce3a3f1d14
111 changed files with 3117 additions and 586 deletions
|
@ -23,7 +23,8 @@
|
|||
"search" : "Search",
|
||||
"daemon" : "Daemons, Tasks and Workers",
|
||||
"irc" : "IRC",
|
||||
"markup" : "Remarkup Extensions"
|
||||
"markup" : "Remarkup Extensions",
|
||||
"metamta" : "MetaMTA (Mail)"
|
||||
},
|
||||
"engines" : [
|
||||
["DivinerArticleEngine", {}],
|
||||
|
|
|
@ -396,6 +396,26 @@ return array(
|
|||
// affects Diffusion.
|
||||
'metamta.diffusion.reply-handler' => 'PhabricatorAuditReplyHandler',
|
||||
|
||||
// Set this to true if you want patches to be attached to commit notifications
|
||||
// from Diffusion. This won't work with SendGrid.
|
||||
'metamta.diffusion.attach-patches' => false,
|
||||
|
||||
// To include patches in Diffusion email bodies, set this to a positive
|
||||
// integer. Patches will be inlined if they are at most that many lines.
|
||||
// By default, patches are not inlined.
|
||||
'metamta.diffusion.inline-patches' => 0,
|
||||
|
||||
// If you've enabled attached patches or inline patches for commit emails, you
|
||||
// can establish a hard byte limit on their size. You should generally set
|
||||
// reasonable byte and time limits (defaults are 1MB and 60 seconds) to avoid
|
||||
// sending ridiculously enormous email for changes like "importing an external
|
||||
// library" or "accidentally committed this full-length movie as text".
|
||||
'metamta.diffusion.byte-limit' => 1024 * 1024,
|
||||
|
||||
// If you've enabled attached patches or inline patches for commit emails, you
|
||||
// can establish a hard time limit on generating them.
|
||||
'metamta.diffusion.time-limit' => 60,
|
||||
|
||||
// Prefix prepended to mail sent by Package.
|
||||
'metamta.package.subject-prefix' => '[Package]',
|
||||
|
||||
|
@ -446,6 +466,20 @@ return array(
|
|||
// address will be stored in an 'From Email' field on the task.
|
||||
'metamta.maniphest.default-public-author' => null,
|
||||
|
||||
// You can disable the Herald hints in email if users prefer smaller messages.
|
||||
// These are the links under the headers "MANAGE HERALD RULES" and
|
||||
// "WHY DID I GET THIS EMAIL?". If you set this to true, they will not appear
|
||||
// in any mail. Users can still navigate to the links via the web interface.
|
||||
'metamta.herald.show-hints' => true,
|
||||
|
||||
// You can disable the hints under "REPLY HANDLER ACTIONS" if users prefer
|
||||
// smaller messages. The actions themselves will still work properly.
|
||||
'metamta.reply.show-hints' => true,
|
||||
|
||||
// You can disable the "To:" and "Cc:" footers in mail if users prefer
|
||||
// smaller messages.
|
||||
'metamta.recipients.show-hints' => true,
|
||||
|
||||
// If this option is enabled, Phabricator will add a "Precedence: bulk"
|
||||
// header to transactional mail (e.g., Differential, Maniphest and Herald
|
||||
// notifications). This may improve the behavior of some auto-responder
|
||||
|
@ -548,6 +582,9 @@ return array(
|
|||
// The Facebook "Application Secret" to use for Facebook API access.
|
||||
'facebook.application-secret' => null,
|
||||
|
||||
// Should Phabricator reject requests made by users with
|
||||
// Secure Browsing disabled?
|
||||
'facebook.require-https-auth' => false,
|
||||
|
||||
// -- GitHub OAuth ---------------------------------------------------------- //
|
||||
|
||||
|
@ -604,6 +641,10 @@ return array(
|
|||
// the array will be joined
|
||||
'ldap.real_name_attributes' => array(),
|
||||
|
||||
// A domain name to use when authenticating against Active Directory
|
||||
// (e.g. 'example.com')
|
||||
'ldap.activedirectory_domain' => '',
|
||||
|
||||
// The LDAP version
|
||||
'ldap.version' => 3,
|
||||
|
||||
|
@ -1007,6 +1048,7 @@ return array(
|
|||
'gcdaemon.ttl.herald-transcripts' => 30 * (24 * 60 * 60),
|
||||
'gcdaemon.ttl.daemon-logs' => 7 * (24 * 60 * 60),
|
||||
'gcdaemon.ttl.differential-parse-cache' => 14 * (24 * 60 * 60),
|
||||
'gcdaemon.ttl.markup-cache' => 30 * (24 * 60 * 60),
|
||||
|
||||
|
||||
// -- Feed ------------------------------------------------------------------ //
|
||||
|
@ -1152,6 +1194,29 @@ return array(
|
|||
// '@\\.([^.]+)\\.bak$@' => 1,
|
||||
|
||||
'@\.arcconfig$@' => 'js',
|
||||
'@\.divinerconfig$@' => 'js',
|
||||
),
|
||||
|
||||
// Set the default monospaced font style for users who haven't set a custom
|
||||
// style.
|
||||
'style.monospace' => '10px "Menlo", "Consolas", "Monaco", monospace',
|
||||
|
||||
|
||||
// -- Debugging ------------------------------------------------------------- //
|
||||
|
||||
// Enable this to change HTTP redirects into normal pages with a link to the
|
||||
// redirection target. For example, after you submit a form you'll get a page
|
||||
// saying "normally, you'd be redirected...". This is useful to examine
|
||||
// service or profiler information on write pathways, or debug redirects. It
|
||||
// also makes the UX horrible for normal use, so you should enable it only
|
||||
// when debugging.
|
||||
//
|
||||
// NOTE: This does not currently work for forms with Javascript "workflow",
|
||||
// since the redirect happens in Javascript.
|
||||
'debug.stop-on-redirect' => false,
|
||||
|
||||
// Enable this to always profile every page. This is very slow! You should
|
||||
// only enable it when debugging.
|
||||
'debug.profile-every-request' => false,
|
||||
|
||||
);
|
||||
|
|
23
resources/sql/patches/harbormasterobject.sql
Normal file
23
resources/sql/patches/harbormasterobject.sql
Normal file
|
@ -0,0 +1,23 @@
|
|||
CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_object (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
|
||||
name VARCHAR(255) COLLATE utf8_general_ci,
|
||||
dateCreated INT UNSIGNED NOT NULL,
|
||||
dateModified INT UNSIGNED NOT NULL
|
||||
) ENGINE=InnoDB, COLLATE utf8_general_ci;
|
||||
|
||||
CREATE TABLE {$NAMESPACE}_harbormaster.edge (
|
||||
src VARCHAR(64) NOT NULL COLLATE utf8_bin,
|
||||
type VARCHAR(64) NOT NULL COLLATE utf8_bin,
|
||||
dst VARCHAR(64) NOT NULL COLLATE utf8_bin,
|
||||
dateCreated INT UNSIGNED NOT NULL,
|
||||
seq INT UNSIGNED NOT NULL,
|
||||
dataID INT UNSIGNED,
|
||||
PRIMARY KEY (src, type, dst),
|
||||
KEY (src, type, dateCreated, seq)
|
||||
) ENGINE=InnoDB, COLLATE utf8_general_ci;
|
||||
|
||||
CREATE TABLE {$NAMESPACE}_harbormaster.edgedata (
|
||||
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
data LONGTEXT NOT NULL COLLATE utf8_bin
|
||||
) ENGINE=InnoDB, COLLATE utf8_general_ci;
|
2
resources/sql/patches/maniphestxcache.sql
Normal file
2
resources/sql/patches/maniphestxcache.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE `{$NAMESPACE}_maniphest`.`maniphest_transaction`
|
||||
DROP `cache`;
|
10
resources/sql/patches/markupcache.sql
Normal file
10
resources/sql/patches/markupcache.sql
Normal file
|
@ -0,0 +1,10 @@
|
|||
CREATE TABLE {$NAMESPACE}_cache.cache_markupcache (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
cacheKey VARCHAR(128) NOT NULL collate utf8_bin,
|
||||
cacheData LONGTEXT NOT NULL COLLATE utf8_bin,
|
||||
metadata LONGTEXT NOT NULL COLLATE utf8_bin,
|
||||
dateCreated INT UNSIGNED NOT NULL,
|
||||
dateModified INT UNSIGNED NOT NULL,
|
||||
UNIQUE KEY (cacheKey),
|
||||
KEY (dateCreated)
|
||||
) ENGINE=InnoDB, COLLATE utf8_general_ci;
|
|
@ -166,7 +166,8 @@ $user->openTransaction();
|
|||
$editor->makeAdminUser($user, $set_admin);
|
||||
|
||||
if ($changed_pass !== false) {
|
||||
$editor->changePassword($user, $changed_pass);
|
||||
$envelope = new PhutilOpaqueEnvelope($changed_pass);
|
||||
$editor->changePassword($user, $envelope);
|
||||
}
|
||||
|
||||
$user->saveTransaction();
|
||||
|
|
|
@ -22,7 +22,6 @@ require_once $root.'/scripts/__init_script__.php';
|
|||
|
||||
$purge_changesets = false;
|
||||
$purge_differential = false;
|
||||
$purge_maniphest = false;
|
||||
|
||||
$args = array_slice($argv, 1);
|
||||
if (!$args) {
|
||||
|
@ -36,7 +35,6 @@ for ($ii = 0; $ii < $len; $ii++) {
|
|||
case '--all':
|
||||
$purge_changesets = true;
|
||||
$purge_differential = true;
|
||||
$purge_maniphest = true;
|
||||
break;
|
||||
case '--changesets':
|
||||
$purge_changesets = true;
|
||||
|
@ -52,9 +50,6 @@ for ($ii = 0; $ii < $len; $ii++) {
|
|||
case '--differential':
|
||||
$purge_differential = true;
|
||||
break;
|
||||
case '--maniphest':
|
||||
$purge_maniphest = true;
|
||||
break;
|
||||
case '--help':
|
||||
return help();
|
||||
default:
|
||||
|
@ -98,16 +93,6 @@ if ($purge_differential) {
|
|||
echo "Done.\n";
|
||||
}
|
||||
|
||||
if ($purge_maniphest) {
|
||||
echo "Purging Maniphest comment cache...\n";
|
||||
$table = new ManiphestTransaction();
|
||||
queryfx(
|
||||
$table->establishConnection('w'),
|
||||
'UPDATE %T SET cache = NULL',
|
||||
$table->getTableName());
|
||||
echo "Done.\n";
|
||||
}
|
||||
|
||||
echo "Ok, caches purged.\n";
|
||||
|
||||
function usage($message) {
|
||||
|
@ -122,7 +107,6 @@ function help() {
|
|||
**SUMMARY**
|
||||
|
||||
**purge_cache.php**
|
||||
[--maniphest]
|
||||
[--differential]
|
||||
[--changesets [changeset_id ...]]
|
||||
**purge_cache.php** --all
|
||||
|
@ -136,9 +120,8 @@ function help() {
|
|||
syntax highlighting, you may want to purge the changeset cache (with
|
||||
"--changesets") so your changes are reflected in older diffs.
|
||||
|
||||
If you change Remarkup rules, you may want to purge the Maniphest or
|
||||
Differential comment caches ("--maniphest", "--differential") so older
|
||||
comments pick up the new rules.
|
||||
If you change Remarkup rules, you may want to purge the Differential
|
||||
comment caches ("--differential") so older comments pick up the new rules.
|
||||
|
||||
__--all__
|
||||
Purge all long-lived caches.
|
||||
|
@ -151,9 +134,6 @@ function help() {
|
|||
__--differential__
|
||||
Purge Differential comment formatting cache.
|
||||
|
||||
__--maniphest__: show this help
|
||||
Purge Maniphest comment formatting cache.
|
||||
|
||||
__--help__: show this help
|
||||
|
||||
|
||||
|
|
|
@ -135,6 +135,7 @@ phutil_register_library_map(array(
|
|||
'ConduitAPI_differential_close_Method' => 'applications/conduit/method/differential/ConduitAPI_differential_close_Method.php',
|
||||
'ConduitAPI_differential_createcomment_Method' => 'applications/conduit/method/differential/ConduitAPI_differential_createcomment_Method.php',
|
||||
'ConduitAPI_differential_creatediff_Method' => 'applications/conduit/method/differential/ConduitAPI_differential_creatediff_Method.php',
|
||||
'ConduitAPI_differential_createinline_Method' => 'applications/conduit/method/differential/ConduitAPI_differential_createinline_Method.php',
|
||||
'ConduitAPI_differential_createrawdiff_Method' => 'applications/conduit/method/differential/ConduitAPI_differential_createrawdiff_Method.php',
|
||||
'ConduitAPI_differential_createrevision_Method' => 'applications/conduit/method/differential/ConduitAPI_differential_createrevision_Method.php',
|
||||
'ConduitAPI_differential_find_Method' => 'applications/conduit/method/differential/ConduitAPI_differential_find_Method.php',
|
||||
|
@ -439,6 +440,7 @@ phutil_register_library_map(array(
|
|||
'DrydockSSHCommandInterface' => 'applications/drydock/interface/command/DrydockSSHCommandInterface.php',
|
||||
'DrydockWebrootInterface' => 'applications/drydock/interface/webroot/DrydockWebrootInterface.php',
|
||||
'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php',
|
||||
'HarbormasterObject' => 'applications/harbormaster/storage/HarbormasterObject.php',
|
||||
'HarbormasterScratchTable' => 'applications/harbormaster/storage/HarbormasterScratchTable.php',
|
||||
'HeraldAction' => 'applications/herald/storage/HeraldAction.php',
|
||||
'HeraldActionConfig' => 'applications/herald/config/HeraldActionConfig.php',
|
||||
|
@ -562,6 +564,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorAuthController' => 'applications/auth/controller/PhabricatorAuthController.php',
|
||||
'PhabricatorBaseEnglishTranslation' => 'infrastructure/internationalization/PhabricatorBaseEnglishTranslation.php',
|
||||
'PhabricatorBuiltinPatchList' => 'infrastructure/storage/patch/PhabricatorBuiltinPatchList.php',
|
||||
'PhabricatorCacheDAO' => 'applications/cache/storage/PhabricatorCacheDAO.php',
|
||||
'PhabricatorCalendarBrowseController' => 'applications/calendar/controller/PhabricatorCalendarBrowseController.php',
|
||||
'PhabricatorCalendarController' => 'applications/calendar/controller/PhabricatorCalendarController.php',
|
||||
'PhabricatorCalendarDAO' => 'applications/calendar/storage/PhabricatorCalendarDAO.php',
|
||||
|
@ -620,8 +623,11 @@ phutil_register_library_map(array(
|
|||
'PhabricatorDraftDAO' => 'applications/draft/storage/PhabricatorDraftDAO.php',
|
||||
'PhabricatorEdgeConfig' => 'infrastructure/edges/constants/PhabricatorEdgeConfig.php',
|
||||
'PhabricatorEdgeConstants' => 'infrastructure/edges/constants/PhabricatorEdgeConstants.php',
|
||||
'PhabricatorEdgeCycleException' => 'infrastructure/edges/exception/PhabricatorEdgeCycleException.php',
|
||||
'PhabricatorEdgeEditor' => 'infrastructure/edges/editor/PhabricatorEdgeEditor.php',
|
||||
'PhabricatorEdgeGraph' => 'infrastructure/edges/util/PhabricatorEdgeGraph.php',
|
||||
'PhabricatorEdgeQuery' => 'infrastructure/edges/query/PhabricatorEdgeQuery.php',
|
||||
'PhabricatorEdgeTestCase' => 'infrastructure/edges/__tests__/PhabricatorEdgeTestCase.php',
|
||||
'PhabricatorEmailLoginController' => 'applications/auth/controller/PhabricatorEmailLoginController.php',
|
||||
'PhabricatorEmailTokenController' => 'applications/auth/controller/PhabricatorEmailTokenController.php',
|
||||
'PhabricatorEmailVerificationController' => 'applications/people/controller/PhabricatorEmailVerificationController.php',
|
||||
|
@ -734,7 +740,9 @@ phutil_register_library_map(array(
|
|||
'PhabricatorMailImplementationSendGridAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationSendGridAdapter.php',
|
||||
'PhabricatorMailImplementationTestAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationTestAdapter.php',
|
||||
'PhabricatorMailReplyHandler' => 'applications/metamta/replyhandler/PhabricatorMailReplyHandler.php',
|
||||
'PhabricatorMarkupCache' => 'applications/cache/storage/PhabricatorMarkupCache.php',
|
||||
'PhabricatorMarkupEngine' => 'infrastructure/markup/PhabricatorMarkupEngine.php',
|
||||
'PhabricatorMarkupInterface' => 'infrastructure/markup/PhabricatorMarkupInterface.php',
|
||||
'PhabricatorMercurialGraphStream' => 'applications/repository/daemon/PhabricatorMercurialGraphStream.php',
|
||||
'PhabricatorMetaMTAAttachment' => 'applications/metamta/storage/PhabricatorMetaMTAAttachment.php',
|
||||
'PhabricatorMetaMTAController' => 'applications/metamta/controller/PhabricatorMetaMTAController.php',
|
||||
|
@ -743,6 +751,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorMetaMTAEmailBodyParserTestCase' => 'applications/metamta/__tests__/PhabricatorMetaMTAEmailBodyParserTestCase.php',
|
||||
'PhabricatorMetaMTAListController' => 'applications/metamta/controller/PhabricatorMetaMTAListController.php',
|
||||
'PhabricatorMetaMTAMail' => 'applications/metamta/storage/PhabricatorMetaMTAMail.php',
|
||||
'PhabricatorMetaMTAMailBody' => 'applications/metamta/view/PhabricatorMetaMTAMailBody.php',
|
||||
'PhabricatorMetaMTAMailBodyTestCase' => 'applications/metamta/view/__tests__/PhabricatorMetaMTAMailBodyTestCase.php',
|
||||
'PhabricatorMetaMTAMailTestCase' => 'applications/metamta/storage/__tests__/PhabricatorMetaMTAMailTestCase.php',
|
||||
'PhabricatorMetaMTAMailingList' => 'applications/metamta/storage/PhabricatorMetaMTAMailingList.php',
|
||||
'PhabricatorMetaMTAMailingListEditController' => 'applications/metamta/controller/PhabricatorMetaMTAMailingListEditController.php',
|
||||
|
@ -833,6 +843,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorPasteViewController' => 'applications/paste/controller/PhabricatorPasteViewController.php',
|
||||
'PhabricatorPeopleController' => 'applications/people/controller/PhabricatorPeopleController.php',
|
||||
'PhabricatorPeopleEditController' => 'applications/people/controller/PhabricatorPeopleEditController.php',
|
||||
'PhabricatorPeopleLdapController' => 'applications/people/controller/PhabricatorPeopleLdapController.php',
|
||||
'PhabricatorPeopleListController' => 'applications/people/controller/PhabricatorPeopleListController.php',
|
||||
'PhabricatorPeopleLogsController' => 'applications/people/controller/PhabricatorPeopleLogsController.php',
|
||||
'PhabricatorPeopleProfileController' => 'applications/people/controller/PhabricatorPeopleProfileController.php',
|
||||
|
@ -1040,6 +1051,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorXHProfProfileSymbolView' => 'applications/xhprof/view/PhabricatorXHProfProfileSymbolView.php',
|
||||
'PhabricatorXHProfProfileTopLevelView' => 'applications/xhprof/view/PhabricatorXHProfProfileTopLevelView.php',
|
||||
'PhabricatorXHProfProfileView' => 'applications/xhprof/view/PhabricatorXHProfProfileView.php',
|
||||
'PhameAllBloggersPostListController' => 'applications/phame/controller/post/list/PhameAllBloggersPostListController.php',
|
||||
'PhameBloggerPostListController' => 'applications/phame/controller/post/list/PhameBloggerPostListController.php',
|
||||
'PhameController' => 'applications/phame/controller/PhameController.php',
|
||||
'PhameDAO' => 'applications/phame/storage/PhameDAO.php',
|
||||
'PhameDraftListController' => 'applications/phame/controller/post/list/PhameDraftListController.php',
|
||||
|
@ -1048,11 +1061,11 @@ phutil_register_library_map(array(
|
|||
'PhamePostDetailView' => 'applications/phame/view/PhamePostDetailView.php',
|
||||
'PhamePostEditController' => 'applications/phame/controller/post/PhamePostEditController.php',
|
||||
'PhamePostListBaseController' => 'applications/phame/controller/post/list/PhamePostListBaseController.php',
|
||||
'PhamePostListController' => 'applications/phame/controller/post/list/PhamePostListController.php',
|
||||
'PhamePostListView' => 'applications/phame/view/PhamePostListView.php',
|
||||
'PhamePostPreviewController' => 'applications/phame/controller/post/PhamePostPreviewController.php',
|
||||
'PhamePostQuery' => 'applications/phame/query/PhamePostQuery.php',
|
||||
'PhamePostViewController' => 'applications/phame/controller/post/PhamePostViewController.php',
|
||||
'PhameUserPostListController' => 'applications/phame/controller/post/list/PhameUserPostListController.php',
|
||||
'PhortuneMonthYearExpiryControl' => 'applications/phortune/control/PhortuneMonthYearExpiryControl.php',
|
||||
'PhortuneStripeBaseController' => 'applications/phortune/stripe/controller/PhortuneStripeBaseController.php',
|
||||
'PhortuneStripePaymentFormView' => 'applications/phortune/stripe/view/PhortuneStripePaymentFormView.php',
|
||||
|
@ -1218,6 +1231,7 @@ phutil_register_library_map(array(
|
|||
'ConduitAPI_differential_close_Method' => 'ConduitAPIMethod',
|
||||
'ConduitAPI_differential_createcomment_Method' => 'ConduitAPIMethod',
|
||||
'ConduitAPI_differential_creatediff_Method' => 'ConduitAPIMethod',
|
||||
'ConduitAPI_differential_createinline_Method' => 'ConduitAPIMethod',
|
||||
'ConduitAPI_differential_createrawdiff_Method' => 'ConduitAPI_differential_Method',
|
||||
'ConduitAPI_differential_createrevision_Method' => 'ConduitAPIMethod',
|
||||
'ConduitAPI_differential_find_Method' => 'ConduitAPIMethod',
|
||||
|
@ -1486,6 +1500,7 @@ phutil_register_library_map(array(
|
|||
'DrydockSSHCommandInterface' => 'DrydockCommandInterface',
|
||||
'DrydockWebrootInterface' => 'DrydockInterface',
|
||||
'HarbormasterDAO' => 'PhabricatorLiskDAO',
|
||||
'HarbormasterObject' => 'HarbormasterDAO',
|
||||
'HarbormasterScratchTable' => 'HarbormasterDAO',
|
||||
'HeraldAction' => 'HeraldDAO',
|
||||
'HeraldApplyTranscript' => 'HeraldDAO',
|
||||
|
@ -1538,7 +1553,11 @@ phutil_register_library_map(array(
|
|||
'ManiphestSavedQueryEditController' => 'ManiphestController',
|
||||
'ManiphestSavedQueryListController' => 'ManiphestController',
|
||||
'ManiphestSubpriorityController' => 'ManiphestController',
|
||||
'ManiphestTask' => 'ManiphestDAO',
|
||||
'ManiphestTask' =>
|
||||
array(
|
||||
0 => 'ManiphestDAO',
|
||||
1 => 'PhabricatorMarkupInterface',
|
||||
),
|
||||
'ManiphestTaskAuxiliaryStorage' => 'ManiphestDAO',
|
||||
'ManiphestTaskDescriptionChangeController' => 'ManiphestController',
|
||||
'ManiphestTaskDescriptionPreviewController' => 'ManiphestController',
|
||||
|
@ -1553,7 +1572,11 @@ phutil_register_library_map(array(
|
|||
'ManiphestTaskStatus' => 'ManiphestConstants',
|
||||
'ManiphestTaskSubscriber' => 'ManiphestDAO',
|
||||
'ManiphestTaskSummaryView' => 'ManiphestView',
|
||||
'ManiphestTransaction' => 'ManiphestDAO',
|
||||
'ManiphestTransaction' =>
|
||||
array(
|
||||
0 => 'ManiphestDAO',
|
||||
1 => 'PhabricatorMarkupInterface',
|
||||
),
|
||||
'ManiphestTransactionDetailView' => 'ManiphestView',
|
||||
'ManiphestTransactionListView' => 'ManiphestView',
|
||||
'ManiphestTransactionPreviewController' => 'ManiphestController',
|
||||
|
@ -1583,6 +1606,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorAuthController' => 'PhabricatorController',
|
||||
'PhabricatorBaseEnglishTranslation' => 'PhabricatorTranslation',
|
||||
'PhabricatorBuiltinPatchList' => 'PhabricatorSQLPatchList',
|
||||
'PhabricatorCacheDAO' => 'PhabricatorLiskDAO',
|
||||
'PhabricatorCalendarBrowseController' => 'PhabricatorCalendarController',
|
||||
'PhabricatorCalendarController' => 'PhabricatorController',
|
||||
'PhabricatorCalendarDAO' => 'PhabricatorLiskDAO',
|
||||
|
@ -1639,7 +1663,10 @@ phutil_register_library_map(array(
|
|||
'PhabricatorDraft' => 'PhabricatorDraftDAO',
|
||||
'PhabricatorDraftDAO' => 'PhabricatorLiskDAO',
|
||||
'PhabricatorEdgeConfig' => 'PhabricatorEdgeConstants',
|
||||
'PhabricatorEdgeCycleException' => 'Exception',
|
||||
'PhabricatorEdgeGraph' => 'AbstractDirectedGraph',
|
||||
'PhabricatorEdgeQuery' => 'PhabricatorQuery',
|
||||
'PhabricatorEdgeTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorEmailLoginController' => 'PhabricatorAuthController',
|
||||
'PhabricatorEmailTokenController' => 'PhabricatorAuthController',
|
||||
'PhabricatorEmailVerificationController' => 'PhabricatorPeopleController',
|
||||
|
@ -1732,11 +1759,13 @@ phutil_register_library_map(array(
|
|||
'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'PhabricatorMailImplementationAdapter',
|
||||
'PhabricatorMailImplementationSendGridAdapter' => 'PhabricatorMailImplementationAdapter',
|
||||
'PhabricatorMailImplementationTestAdapter' => 'PhabricatorMailImplementationAdapter',
|
||||
'PhabricatorMarkupCache' => 'PhabricatorCacheDAO',
|
||||
'PhabricatorMetaMTAController' => 'PhabricatorController',
|
||||
'PhabricatorMetaMTADAO' => 'PhabricatorLiskDAO',
|
||||
'PhabricatorMetaMTAEmailBodyParserTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorMetaMTAListController' => 'PhabricatorMetaMTAController',
|
||||
'PhabricatorMetaMTAMail' => 'PhabricatorMetaMTADAO',
|
||||
'PhabricatorMetaMTAMailBodyTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorMetaMTAMailTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorMetaMTAMailingList' => 'PhabricatorMetaMTADAO',
|
||||
'PhabricatorMetaMTAMailingListEditController' => 'PhabricatorMetaMTAController',
|
||||
|
@ -1819,6 +1848,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorPasteViewController' => 'PhabricatorPasteController',
|
||||
'PhabricatorPeopleController' => 'PhabricatorController',
|
||||
'PhabricatorPeopleEditController' => 'PhabricatorPeopleController',
|
||||
'PhabricatorPeopleLdapController' => 'PhabricatorPeopleController',
|
||||
'PhabricatorPeopleListController' => 'PhabricatorPeopleController',
|
||||
'PhabricatorPeopleLogsController' => 'PhabricatorPeopleController',
|
||||
'PhabricatorPeopleProfileController' => 'PhabricatorPeopleController',
|
||||
|
@ -2001,6 +2031,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorXHProfProfileSymbolView' => 'PhabricatorXHProfProfileView',
|
||||
'PhabricatorXHProfProfileTopLevelView' => 'PhabricatorXHProfProfileView',
|
||||
'PhabricatorXHProfProfileView' => 'AphrontView',
|
||||
'PhameAllBloggersPostListController' => 'PhamePostListBaseController',
|
||||
'PhameBloggerPostListController' => 'PhamePostListBaseController',
|
||||
'PhameController' => 'PhabricatorController',
|
||||
'PhameDAO' => 'PhabricatorLiskDAO',
|
||||
'PhameDraftListController' => 'PhamePostListBaseController',
|
||||
|
@ -2009,18 +2041,22 @@ phutil_register_library_map(array(
|
|||
'PhamePostDetailView' => 'AphrontView',
|
||||
'PhamePostEditController' => 'PhameController',
|
||||
'PhamePostListBaseController' => 'PhameController',
|
||||
'PhamePostListController' => 'PhamePostListBaseController',
|
||||
'PhamePostListView' => 'AphrontView',
|
||||
'PhamePostPreviewController' => 'PhameController',
|
||||
'PhamePostQuery' => 'PhabricatorOffsetPagedQuery',
|
||||
'PhamePostViewController' => 'PhameController',
|
||||
'PhameUserPostListController' => 'PhamePostListBaseController',
|
||||
'PhortuneMonthYearExpiryControl' => 'AphrontFormControl',
|
||||
'PhortuneStripeBaseController' => 'PhabricatorController',
|
||||
'PhortuneStripePaymentFormView' => 'AphrontView',
|
||||
'PhortuneStripeTestPaymentFormController' => 'PhortuneStripeBaseController',
|
||||
'PhrictionActionConstants' => 'PhrictionConstants',
|
||||
'PhrictionChangeType' => 'PhrictionConstants',
|
||||
'PhrictionContent' => 'PhrictionDAO',
|
||||
'PhrictionContent' =>
|
||||
array(
|
||||
0 => 'PhrictionDAO',
|
||||
1 => 'PhabricatorMarkupInterface',
|
||||
),
|
||||
'PhrictionController' => 'PhabricatorController',
|
||||
'PhrictionDAO' => 'PhabricatorLiskDAO',
|
||||
'PhrictionDeleteController' => 'PhrictionController',
|
||||
|
|
|
@ -72,6 +72,7 @@ class AphrontDefaultApplicationConfiguration
|
|||
'logs/' => 'PhabricatorPeopleLogsController',
|
||||
'edit/(?:(?P<id>\d+)/(?:(?P<view>\w+)/)?)?'
|
||||
=> 'PhabricatorPeopleEditController',
|
||||
'ldap/' => 'PhabricatorPeopleLdapController',
|
||||
),
|
||||
'/p/(?P<username>[\w._-]+)/(?:(?P<page>\w+)/)?'
|
||||
=> 'PhabricatorPeopleProfileController',
|
||||
|
@ -381,9 +382,9 @@ class AphrontDefaultApplicationConfiguration
|
|||
),
|
||||
|
||||
'/phame/' => array(
|
||||
'' => 'PhamePostListController',
|
||||
'' => 'PhameAllBloggersPostListController',
|
||||
'post/' => array(
|
||||
'' => 'PhamePostListController',
|
||||
'' => 'PhameUserPostListController',
|
||||
'delete/(?P<phid>[^/]+)/' => 'PhamePostDeleteController',
|
||||
'edit/(?P<phid>[^/]+)/' => 'PhamePostEditController',
|
||||
'new/' => 'PhamePostEditController',
|
||||
|
@ -395,8 +396,8 @@ class AphrontDefaultApplicationConfiguration
|
|||
'new/' => 'PhamePostEditController',
|
||||
),
|
||||
'posts/' => array(
|
||||
'' => 'PhamePostListController',
|
||||
'(?P<bloggername>\w+)/' => 'PhamePostListController',
|
||||
'' => 'PhameUserPostListController',
|
||||
'(?P<bloggername>\w+)/' => 'PhameBloggerPostListController',
|
||||
'(?P<bloggername>\w+)/(?P<phametitle>.+/)'
|
||||
=> 'PhamePostViewController',
|
||||
),
|
||||
|
|
|
@ -103,8 +103,8 @@ final class DarkConsoleXHProfPlugin extends DarkConsolePlugin {
|
|||
|
||||
|
||||
public function willShutdown() {
|
||||
if (isset($_REQUEST['__profile__']) &&
|
||||
$_REQUEST['__profile__'] != 'all') {
|
||||
if (DarkConsoleXHProfPluginAPI::isProfilerRequested() &&
|
||||
(DarkConsoleXHProfPluginAPI::isProfilerRequested() !== 'all')) {
|
||||
$this->xhprofID = DarkConsoleXHProfPluginAPI::stopProfiler();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,10 @@ final class DarkConsoleErrorLogPluginAPI {
|
|||
self::$discardMode = true;
|
||||
}
|
||||
|
||||
public static function disableDiscardMode() {
|
||||
self::$discardMode = false;
|
||||
}
|
||||
|
||||
public static function getErrors() {
|
||||
return self::$errors;
|
||||
}
|
||||
|
|
|
@ -29,6 +29,18 @@ final class DarkConsoleXHProfPluginAPI {
|
|||
return extension_loaded('xhprof');
|
||||
}
|
||||
|
||||
public static function isProfilerRequested() {
|
||||
if (!empty($_REQUEST['__profile__'])) {
|
||||
return $_REQUEST['__profile__'];
|
||||
}
|
||||
|
||||
if (PhabricatorEnv::getEnvConfig('debug.profile-every-request')) {
|
||||
return PhabricatorEnv::getEnvConfig('debug.profile-every-request');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function includeXHProfLib() {
|
||||
// TODO: this is incredibly stupid, but we may not have Phutil metamodule
|
||||
// stuff loaded yet so we can't just phutil_get_library_root() our way
|
||||
|
@ -41,8 +53,9 @@ final class DarkConsoleXHProfPluginAPI {
|
|||
require_once $root.'/externals/xhprof/xhprof_lib.php';
|
||||
}
|
||||
|
||||
|
||||
public static function hookProfiler() {
|
||||
if (empty($_REQUEST['__profile__'])) {
|
||||
if (!self::isProfilerRequested()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -71,17 +84,41 @@ final class DarkConsoleXHProfPluginAPI {
|
|||
$data = serialize($data);
|
||||
$file_class = 'PhabricatorFile';
|
||||
|
||||
// Since these happen on GET we can't do guarded writes.
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
// Since these happen on GET we can't do guarded writes. These also
|
||||
// sometimes happen after we've disposed of the write guard; in this
|
||||
// case we need to disable the whole mechanism.
|
||||
|
||||
$file = call_user_func(
|
||||
array($file_class, 'newFromFileData'),
|
||||
$data,
|
||||
array(
|
||||
'mime-type' => 'application/xhprof',
|
||||
'name' => 'profile.xhprof',
|
||||
));
|
||||
return $file->getPHID();
|
||||
$use_scope = AphrontWriteGuard::isGuardActive();
|
||||
if ($use_scope) {
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
} else {
|
||||
AphrontWriteGuard::allowDangerousUnguardedWrites(true);
|
||||
}
|
||||
|
||||
$caught = null;
|
||||
try {
|
||||
$file = call_user_func(
|
||||
array($file_class, 'newFromFileData'),
|
||||
$data,
|
||||
array(
|
||||
'mime-type' => 'application/xhprof',
|
||||
'name' => 'profile.xhprof',
|
||||
));
|
||||
} catch (Exception $ex) {
|
||||
$caught = $ex;
|
||||
}
|
||||
|
||||
if ($use_scope) {
|
||||
unset($unguarded);
|
||||
} else {
|
||||
AphrontWriteGuard::allowDangerousUnguardedWrites(false);
|
||||
}
|
||||
|
||||
if ($caught) {
|
||||
throw $caught;
|
||||
} else {
|
||||
return $file->getPHID();
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -34,15 +34,47 @@ class AphrontRedirectResponse extends AphrontResponse {
|
|||
return (string)$this->uri;
|
||||
}
|
||||
|
||||
public function shouldStopForDebugging() {
|
||||
return PhabricatorEnv::getEnvConfig('debug.stop-on-redirect');
|
||||
}
|
||||
|
||||
public function getHeaders() {
|
||||
$headers = array(
|
||||
array('Location', $this->uri),
|
||||
);
|
||||
$headers = array();
|
||||
if (!$this->shouldStopForDebugging()) {
|
||||
$headers[] = array('Location', $this->uri);
|
||||
}
|
||||
$headers = array_merge(parent::getHeaders(), $headers);
|
||||
return $headers;
|
||||
}
|
||||
|
||||
public function buildResponseString() {
|
||||
if ($this->shouldStopForDebugging()) {
|
||||
$view = new PhabricatorStandardPageView();
|
||||
$view->setRequest($this->getRequest());
|
||||
$view->setApplicationName('Debug');
|
||||
$view->setTitle('Stopped on Redirect');
|
||||
|
||||
$error = new AphrontErrorView();
|
||||
$error->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
|
||||
$error->setTitle('Stopped on Redirect');
|
||||
|
||||
$link = phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $this->getURI(),
|
||||
),
|
||||
'Continue to: '.phutil_escape_html($this->getURI()));
|
||||
|
||||
$error->appendChild(
|
||||
'<p>You were stopped here because <tt>debug.stop-on-redirect</tt> '.
|
||||
'is set in your configuration.</p>'.
|
||||
'<p>'.$link.'</p>');
|
||||
|
||||
$view->appendChild($error);
|
||||
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
|
|
|
@ -108,6 +108,17 @@ final class AphrontWriteGuard {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine if there is an active write guard.
|
||||
*
|
||||
* @return bool
|
||||
* @task manage
|
||||
*/
|
||||
public static function isGuardActive() {
|
||||
return (bool)self::$instance;
|
||||
}
|
||||
|
||||
|
||||
/* -( Protecting Writes )-------------------------------------------------- */
|
||||
|
||||
|
||||
|
|
|
@ -486,12 +486,9 @@ final class PhabricatorAuditCommentEditor {
|
|||
$verb = PhabricatorAuditActionConstants::getActionPastTenseVerb(
|
||||
$comment->getAction());
|
||||
|
||||
$body = array();
|
||||
$body[] = "{$name} {$verb} commit {$cname}.";
|
||||
|
||||
if ($comment->getContent()) {
|
||||
$body[] = $comment->getContent();
|
||||
}
|
||||
$body = new PhabricatorMetaMTAMailBody();
|
||||
$body->addRawSection("{$name} {$verb} commit {$cname}.");
|
||||
$body->addRawSection($comment->getContent());
|
||||
|
||||
if ($inline_comments) {
|
||||
$block = array();
|
||||
|
@ -518,18 +515,16 @@ final class PhabricatorAuditCommentEditor {
|
|||
$content = $inline->getContent();
|
||||
$block[] = "{$path}:{$range} {$content}";
|
||||
}
|
||||
$body[] = "INLINE COMMENTS\n ".implode("\n ", $block);
|
||||
|
||||
$body->addTextSection(pht('INLINE COMMENTS'), implode("\n", $block));
|
||||
}
|
||||
|
||||
$body[] = "COMMIT\n ".PhabricatorEnv::getProductionURI($handle->getURI());
|
||||
$body->addTextSection(
|
||||
pht('COMMIT'),
|
||||
PhabricatorEnv::getProductionURI($handle->getURI()));
|
||||
$body->addReplySection($reply_handler->getReplyHandlerInstructions());
|
||||
|
||||
|
||||
$reply_instructions = $reply_handler->getReplyHandlerInstructions();
|
||||
if ($reply_instructions) {
|
||||
$body[] = "REPLY HANDLER ACTIONS\n ".$reply_instructions;
|
||||
}
|
||||
|
||||
return implode("\n\n", $body)."\n";
|
||||
return $body->render();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -35,11 +35,12 @@ final class PhabricatorLDAPLoginController extends PhabricatorAuthController {
|
|||
$current_user = $this->getRequest()->getUser();
|
||||
$request = $this->getRequest();
|
||||
|
||||
$ldap_username = $request->getCookie('phusr');
|
||||
if ($request->isFormPost()) {
|
||||
$ldap_username = $request->getStr('username');
|
||||
try {
|
||||
$this->provider->auth($request->getStr('username'),
|
||||
$request->getStr('password'));
|
||||
|
||||
$envelope = new PhutilOpaqueEnvelope($request->getStr('password'));
|
||||
$this->provider->auth($ldap_username, $envelope);
|
||||
} catch (Exception $e) {
|
||||
$errors[] = $e->getMessage();
|
||||
}
|
||||
|
@ -125,7 +126,6 @@ final class PhabricatorLDAPLoginController extends PhabricatorAuthController {
|
|||
}
|
||||
}
|
||||
|
||||
$ldap_username = $request->getCookie('phusr');
|
||||
$ldap_form = new AphrontFormView();
|
||||
$ldap_form
|
||||
->setUser($request->getUser())
|
||||
|
|
|
@ -119,7 +119,10 @@ final class PhabricatorLoginController
|
|||
if (!$errors) {
|
||||
// Perform username/password tests only if we didn't get rate limited
|
||||
// by the CAPTCHA.
|
||||
if (!$user || !$user->comparePassword($request->getStr('password'))) {
|
||||
|
||||
$envelope = new PhutilOpaqueEnvelope($request->getStr('password'));
|
||||
|
||||
if (!$user || !$user->comparePassword($envelope)) {
|
||||
$errors[] = 'Bad username/password.';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ final class PhabricatorOAuthLoginController
|
|||
}
|
||||
$provider->setUserData($user_data);
|
||||
} catch (PhabricatorOAuthProviderException $e) {
|
||||
return $this->buildErrorResponse(new PhabricatorOAuthFailureView());
|
||||
return $this->buildErrorResponse(new PhabricatorOAuthFailureView(), $e);
|
||||
}
|
||||
$provider->setAccessToken($this->accessToken);
|
||||
|
||||
|
@ -243,12 +243,18 @@ final class PhabricatorOAuthLoginController
|
|||
return $this->delegateToController($controller);
|
||||
}
|
||||
|
||||
private function buildErrorResponse(PhabricatorOAuthFailureView $view) {
|
||||
private function buildErrorResponse(PhabricatorOAuthFailureView $view,
|
||||
Exception $e = null) {
|
||||
|
||||
$provider = $this->provider;
|
||||
|
||||
$provider_name = $provider->getProviderName();
|
||||
$view->setOAuthProvider($provider);
|
||||
|
||||
if ($e) {
|
||||
$view->setException($e);
|
||||
}
|
||||
|
||||
return $this->buildStandardPageResponse(
|
||||
$view,
|
||||
array(
|
||||
|
|
|
@ -55,20 +55,24 @@ final class PhabricatorLDAPProvider {
|
|||
}
|
||||
|
||||
public function retrieveUserRealName() {
|
||||
return $this->retrieveUserRealNameFromData($this->userData);
|
||||
}
|
||||
|
||||
public function retrieveUserRealNameFromData($data) {
|
||||
$name_attributes = PhabricatorEnv::getEnvConfig(
|
||||
'ldap.real_name_attributes');
|
||||
|
||||
$real_name = '';
|
||||
if (is_array($name_attributes)) {
|
||||
foreach ($name_attributes AS $attribute) {
|
||||
if (isset($this->userData[$attribute][0])) {
|
||||
$real_name .= $this->userData[$attribute][0] . ' ';
|
||||
if (isset($data[$attribute][0])) {
|
||||
$real_name .= $data[$attribute][0] . ' ';
|
||||
}
|
||||
}
|
||||
|
||||
trim($real_name);
|
||||
} else if (isset($this->userData[$name_attributes][0])) {
|
||||
$real_name = $this->userData[$name_attributes][0];
|
||||
} else if (isset($data[$name_attributes][0])) {
|
||||
$real_name = $data[$name_attributes][0];
|
||||
}
|
||||
|
||||
if ($real_name == '') {
|
||||
|
@ -102,18 +106,35 @@ final class PhabricatorLDAPProvider {
|
|||
return $this->userData;
|
||||
}
|
||||
|
||||
public function auth($username, $password) {
|
||||
if (strlen(trim($username)) == 0 || strlen(trim($password)) == 0) {
|
||||
throw new Exception('Username and/or password can not be empty');
|
||||
public function auth($username, PhutilOpaqueEnvelope $password) {
|
||||
if (strlen(trim($username)) == 0) {
|
||||
throw new Exception('Username can not be empty');
|
||||
}
|
||||
|
||||
$result = ldap_bind($this->getConnection(),
|
||||
$this->getSearchAttribute() . '=' . $username . ',' .
|
||||
$this->getBaseDN(),
|
||||
$password);
|
||||
$activeDirectoryDomain =
|
||||
PhabricatorEnv::getEnvConfig('ldap.activedirectory_domain');
|
||||
|
||||
if ($activeDirectoryDomain) {
|
||||
$dn = $username . '@' . $activeDirectoryDomain;
|
||||
} else {
|
||||
$dn = ldap_sprintf(
|
||||
'%Q=%s,%Q',
|
||||
$this->getSearchAttribute(),
|
||||
$username,
|
||||
$this->getBaseDN());
|
||||
}
|
||||
|
||||
$conn = $this->getConnection();
|
||||
|
||||
// NOTE: It is very important we suppress any messages that occur here,
|
||||
// because it logs passwords if it reaches an error log of any sort.
|
||||
DarkConsoleErrorLogPluginAPI::enableDiscardMode();
|
||||
$result = @ldap_bind($conn, $dn, $password->openEnvelope());
|
||||
DarkConsoleErrorLogPluginAPI::disableDiscardMode();
|
||||
|
||||
if (!$result) {
|
||||
throw new Exception('Bad username/password.');
|
||||
throw new Exception(
|
||||
"LDAP Error #".ldap_errno($conn).": ".ldap_error($conn));
|
||||
}
|
||||
|
||||
$this->userData = $this->getUser($username);
|
||||
|
@ -121,15 +142,21 @@ final class PhabricatorLDAPProvider {
|
|||
}
|
||||
|
||||
private function getUser($username) {
|
||||
$result = ldap_search($this->getConnection(), $this->getBaseDN(),
|
||||
$this->getSearchAttribute() . '=' . $username);
|
||||
$conn = $this->getConnection();
|
||||
|
||||
$query = ldap_sprintf(
|
||||
'%Q=%S',
|
||||
$this->getSearchAttribute(),
|
||||
$username);
|
||||
|
||||
$result = ldap_search($conn, $this->getBaseDN(), $query);
|
||||
|
||||
if (!$result) {
|
||||
throw new Exception('Search failed. Please check your LDAP and HTTP '.
|
||||
'logs for more information.');
|
||||
}
|
||||
|
||||
$entries = ldap_get_entries($this->getConnection(), $result);
|
||||
$entries = ldap_get_entries($conn, $result);
|
||||
|
||||
if ($entries === false) {
|
||||
throw new Exception('Could not get entries');
|
||||
|
@ -146,4 +173,47 @@ final class PhabricatorLDAPProvider {
|
|||
|
||||
return $entries[0];
|
||||
}
|
||||
|
||||
public function search($query) {
|
||||
$result = ldap_search($this->getConnection(), $this->getBaseDN(),
|
||||
$query);
|
||||
|
||||
if (!$result) {
|
||||
throw new Exception('Search failed. Please check your LDAP and HTTP '.
|
||||
'logs for more information.');
|
||||
}
|
||||
|
||||
$entries = ldap_get_entries($this->getConnection(), $result);
|
||||
|
||||
if ($entries === false) {
|
||||
throw new Exception('Could not get entries');
|
||||
}
|
||||
|
||||
if ($entries['count'] == 0) {
|
||||
throw new Exception('No results found');
|
||||
}
|
||||
|
||||
|
||||
$rows = array();
|
||||
|
||||
for($i = 0; $i < $entries['count']; $i++) {
|
||||
$row = array();
|
||||
$entry = $entries[$i];
|
||||
|
||||
// Get username, email and realname
|
||||
$username = $entry[$this->getSearchAttribute()][0];
|
||||
if(empty($username)) {
|
||||
continue;
|
||||
}
|
||||
$row[] = $username;
|
||||
$row[] = $entry['mail'][0];
|
||||
$row[] = $this->retrieveUserRealNameFromData($entry);
|
||||
|
||||
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
return $rows;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,7 +78,9 @@ final class PhabricatorOAuthProviderFacebook extends PhabricatorOAuthProvider {
|
|||
}
|
||||
|
||||
public function getUserInfoURI() {
|
||||
return 'https://graph.facebook.com/me';
|
||||
$fields = array('id', 'name', 'email', 'link', 'security_settings');
|
||||
return 'https://graph.facebook.com/me?fields='.
|
||||
implode(',', $fields);
|
||||
}
|
||||
|
||||
public function getMinimumScope() {
|
||||
|
@ -88,6 +90,17 @@ final class PhabricatorOAuthProviderFacebook extends PhabricatorOAuthProvider {
|
|||
public function setUserData($data) {
|
||||
$data = json_decode($data, true);
|
||||
$this->validateUserData($data);
|
||||
|
||||
if (PhabricatorEnv::getEnvConfig('facebook.require-https-auth')) {
|
||||
if (!$data['security_settings']['secure_browsing']['enabled']) {
|
||||
throw new PhabricatorOAuthProviderException(
|
||||
'You must enable Secure Browsing on your Facebook account in'.
|
||||
' order to log in to Phabricator. For more information, check'.
|
||||
' out http://www.facebook.com/help/?faq=215897678434749'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->userData = $data;
|
||||
return $this;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ final class PhabricatorOAuthFailureView extends AphrontView {
|
|||
|
||||
private $request;
|
||||
private $provider;
|
||||
private $exception;
|
||||
|
||||
public function setRequest(AphrontRequest $request) {
|
||||
$this->request = $request;
|
||||
|
@ -31,6 +32,11 @@ final class PhabricatorOAuthFailureView extends AphrontView {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setException(Exception $e) {
|
||||
$this->exception = $e;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$request = $this->request;
|
||||
$provider = $this->provider;
|
||||
|
@ -53,6 +59,11 @@ final class PhabricatorOAuthFailureView extends AphrontView {
|
|||
hsprintf(
|
||||
'<p><strong>Error Reason:</strong> %s</p>',
|
||||
$request->getStr('error_reason')));
|
||||
} else if ($this->exception) {
|
||||
$view->appendChild(
|
||||
hsprintf(
|
||||
'<p><strong>Error Details:</strong> %s</p>',
|
||||
$this->exception->getMessage()));
|
||||
} else {
|
||||
// TODO: We can probably refine this.
|
||||
$view->appendChild(
|
||||
|
|
|
@ -16,14 +16,10 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @group phame
|
||||
*/
|
||||
final class PhamePostListController
|
||||
extends PhamePostListBaseController {
|
||||
abstract class PhabricatorCacheDAO extends PhabricatorLiskDAO {
|
||||
|
||||
public function processRequest() {
|
||||
$this->setIsDraft(false);
|
||||
return parent::processRequest();
|
||||
public function getApplicationName() {
|
||||
return 'cache';
|
||||
}
|
||||
|
||||
}
|
34
src/applications/cache/storage/PhabricatorMarkupCache.php
vendored
Normal file
34
src/applications/cache/storage/PhabricatorMarkupCache.php
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
final class PhabricatorMarkupCache extends PhabricatorCacheDAO {
|
||||
|
||||
protected $cacheKey;
|
||||
protected $cacheData;
|
||||
protected $metadata;
|
||||
|
||||
public function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_SERIALIZATION => array(
|
||||
'cacheData' => self::SERIALIZATION_PHP,
|
||||
'metadata' => self::SERIALIZATION_JSON,
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
}
|
|
@ -32,4 +32,31 @@ abstract class ConduitAPI_differential_Method extends ConduitAPIMethod {
|
|||
}
|
||||
|
||||
|
||||
protected function buildInlineInfoDictionary(
|
||||
DifferentialInlineComment $inline,
|
||||
DifferentialChangeset $changeset = null) {
|
||||
|
||||
$file_path = null;
|
||||
$diff_id = null;
|
||||
if ($changeset) {
|
||||
$file_path = $inline->getIsNewFile()
|
||||
? $changeset->getFilename()
|
||||
: $changeset->getOldFile();
|
||||
|
||||
$diff_id = $changeset->getDiffID();
|
||||
}
|
||||
|
||||
return array(
|
||||
'id' => $inline->getID(),
|
||||
'authorPHID' => $inline->getAuthorPHID(),
|
||||
'filePath' => $file_path,
|
||||
'isNewFile' => $inline->getIsNewFile(),
|
||||
'lineNumber' => $inline->getLineNumber(),
|
||||
'lineLength' => $inline->getLineLength(),
|
||||
'diffID' => $diff_id,
|
||||
'content' => $inline->getContent(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -29,7 +29,8 @@ final class ConduitAPI_differential_createcomment_Method
|
|||
public function defineParamTypes() {
|
||||
return array(
|
||||
'revision_id' => 'required revisionid',
|
||||
'message' => 'required string',
|
||||
'message' => 'optional string',
|
||||
'action' => 'optional string',
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -54,10 +55,15 @@ final class ConduitAPI_differential_createcomment_Method
|
|||
PhabricatorContentSource::SOURCE_CONDUIT,
|
||||
array());
|
||||
|
||||
$action = $request->getValue('action');
|
||||
if (!$action) {
|
||||
$action = 'none';
|
||||
}
|
||||
|
||||
$editor = new DifferentialCommentEditor(
|
||||
$revision,
|
||||
$request->getUser()->getPHID(),
|
||||
DifferentialAction::ACTION_COMMENT);
|
||||
$action);
|
||||
$editor->setContentSource($content_source);
|
||||
$editor->setMessage($request->getValue('message'));
|
||||
$editor->save();
|
||||
|
|
|
@ -41,9 +41,9 @@ final class ConduitAPI_differential_creatediff_Method extends ConduitAPIMethod {
|
|||
'arcanistProject' => 'optional string',
|
||||
'repositoryUUID' => 'optional string',
|
||||
'lintStatus' =>
|
||||
'required enum<none, skip, okay, warn, fail>',
|
||||
'required enum<none, skip, okay, warn, fail, postponed>',
|
||||
'unitStatus' =>
|
||||
'required enum<none, skip, okay, warn, fail>',
|
||||
'required enum<none, skip, okay, warn, fail, postponed>',
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -121,6 +121,9 @@ final class ConduitAPI_differential_creatediff_Method extends ConduitAPIMethod {
|
|||
case 'fail':
|
||||
$diff->setLintStatus(DifferentialLintStatus::LINT_FAIL);
|
||||
break;
|
||||
case 'postponed':
|
||||
$diff->setLintStatus(DifferentialLintStatus::LINT_POSTPONED);
|
||||
break;
|
||||
case 'none':
|
||||
default:
|
||||
$diff->setLintStatus(DifferentialLintStatus::LINT_NONE);
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @group conduit
|
||||
*/
|
||||
final class ConduitAPI_differential_createinline_Method
|
||||
extends ConduitAPI_differential_Method {
|
||||
|
||||
public function getMethodDescription() {
|
||||
return "Add an inline comment to a Differential revision.";
|
||||
}
|
||||
|
||||
public function defineParamTypes() {
|
||||
return array(
|
||||
'revisionID' => 'optional revisionid',
|
||||
'diffID' => 'optional diffid',
|
||||
'filePath' => 'required string',
|
||||
'isNewFile' => 'required bool',
|
||||
'lineNumber' => 'required int',
|
||||
'lineLength' => 'optional int',
|
||||
'content' => 'required string',
|
||||
);
|
||||
}
|
||||
|
||||
public function defineReturnType() {
|
||||
return 'nonempty dict';
|
||||
}
|
||||
|
||||
public function defineErrorTypes() {
|
||||
return array(
|
||||
'ERR-BAD-REVISION' => 'Bad revision ID.',
|
||||
'ERR-BAD-DIFF' => 'Bad diff ID, or diff does not belong to revision.',
|
||||
'ERR-NEED-DIFF' => 'Neither revision ID nor diff ID was provided.',
|
||||
'ERR-NEED-FILE' => 'A file path was not provided.',
|
||||
'ERR-BAD-FILE' => "Requested file doesn't exist in this revision."
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(ConduitAPIRequest $request) {
|
||||
$rid = $request->getValue('revisionID');
|
||||
$did = $request->getValue('diffID');
|
||||
|
||||
if ($rid) {
|
||||
// Given both a revision and a diff, check that they match.
|
||||
// Given only a revision, find the active diff.
|
||||
$revision = id(new DifferentialRevision())->load($rid);
|
||||
if (!$revision) {
|
||||
throw new ConduitException('ERR-BAD-REVISION');
|
||||
}
|
||||
|
||||
if (!$did) { // did not!
|
||||
$diff = $revision->loadActiveDiff();
|
||||
$did = $diff->getID();
|
||||
} else { // did too!
|
||||
$diff = id(new DifferentialDiff())->load($did);
|
||||
if (!$diff || $diff->getRevisionID() != $rid) {
|
||||
throw new ConduitException('ERR-BAD-DIFF');
|
||||
}
|
||||
}
|
||||
} else if ($did) {
|
||||
// Given only a diff, find the parent revision.
|
||||
$diff = id(new DifferentialDiff())->load($did);
|
||||
if (!$diff) {
|
||||
throw new ConduitException('ERR-BAD-DIFF');
|
||||
}
|
||||
$rid = $diff->getRevisionID();
|
||||
} else {
|
||||
// Given neither, bail.
|
||||
throw new ConduitException('ERR-NEED-DIFF');
|
||||
}
|
||||
|
||||
$file = $request->getValue('filePath');
|
||||
if (!$file) {
|
||||
throw new ConduitException('ERR-NEED-FILE');
|
||||
}
|
||||
$changes = id(new DifferentialChangeset())->loadAllWhere(
|
||||
'diffID = %d',
|
||||
$did);
|
||||
$cid = null;
|
||||
foreach ($changes as $id => $change) {
|
||||
if ($file == $change->getFilename()) {
|
||||
$cid = $id;
|
||||
}
|
||||
}
|
||||
if ($cid == null) {
|
||||
throw new ConduitException('ERR-BAD-FILE');
|
||||
}
|
||||
|
||||
$inline = id(new DifferentialInlineComment())
|
||||
->setRevisionID($rid)
|
||||
->setChangesetID($cid)
|
||||
->setAuthorPHID($request->getUser()->getPHID())
|
||||
->setContent($request->getValue('content'))
|
||||
->setIsNewFile($request->getValue('isNewFile'))
|
||||
->setLineNumber($request->getValue('lineNumber'))
|
||||
->setLineLength($request->getValue('lineLength', 0))
|
||||
->save();
|
||||
|
||||
// Load everything again, just to be safe.
|
||||
$changeset = id(new DifferentialChangeset())
|
||||
->load($inline->getChangesetID());
|
||||
return $this->buildInlineInfoDictionary($inline, $changeset);
|
||||
}
|
||||
|
||||
}
|
|
@ -20,7 +20,7 @@
|
|||
* @group conduit
|
||||
*/
|
||||
final class ConduitAPI_differential_getrevisioncomments_Method
|
||||
extends ConduitAPIMethod {
|
||||
extends ConduitAPI_differential_Method {
|
||||
|
||||
public function getMethodDescription() {
|
||||
return "Retrieve Differential Revision Comments.";
|
||||
|
@ -81,23 +81,10 @@ final class ConduitAPI_differential_getrevisioncomments_Method
|
|||
if ($with_inlines) {
|
||||
$result['inlines'] = array();
|
||||
foreach (idx($inlines, $comment->getID(), array()) as $inline) {
|
||||
$file_path = null;
|
||||
$diff_id = null;
|
||||
$changeset = idx($changesets, $inline->getChangesetID());
|
||||
if ($changeset) {
|
||||
$file_path = ($inline->getIsNewFile() ?
|
||||
$changeset->getFilename() :
|
||||
$changeset->getOldFile());
|
||||
$diff_id = $changeset->getDiffID();
|
||||
}
|
||||
$result['inlines'][] = array(
|
||||
'filePath' => $file_path,
|
||||
'isNewFile' => $inline->getIsNewFile(),
|
||||
'lineNumber' => $inline->getLineNumber(),
|
||||
'lineLength' => $inline->getLineLength(),
|
||||
'diffID' => $diff_id,
|
||||
'content' => $inline->getContent(),
|
||||
);
|
||||
$result['inlines'][] = $this->buildInlineInfoDictionary(
|
||||
$inline,
|
||||
$changeset);
|
||||
}
|
||||
// TODO: Put synthetic inlines without an attached comment somewhere.
|
||||
}
|
||||
|
|
|
@ -70,15 +70,21 @@ final class ConduitAPI_diffusion_findsymbols_Method
|
|||
|
||||
$results = $query->execute();
|
||||
|
||||
|
||||
$response = array();
|
||||
foreach ($results as $result) {
|
||||
$uri = $result->getURI();
|
||||
if ($uri) {
|
||||
$uri = PhabricatorEnv::getProductionURI($uri);
|
||||
}
|
||||
|
||||
$response[] = array(
|
||||
'name' => $result->getSymbolName(),
|
||||
'type' => $result->getSymbolType(),
|
||||
'language' => $result->getSymbolLanguage(),
|
||||
'path' => $result->getPath(),
|
||||
'line' => $result->getLineNumber(),
|
||||
'uri' => PhabricatorEnv::getProductionURI($result->getURI()),
|
||||
'uri' => $uri,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
final class ConduitAPI_diffusion_getrecentcommitsbypath_Method
|
||||
extends ConduitAPIMethod {
|
||||
|
||||
const RESULT_LIMIT = 10;
|
||||
const DEFAULT_LIMIT = 10;
|
||||
|
||||
public function getMethodDescription() {
|
||||
return "Get commit identifiers for recent commits affecting a given path.";
|
||||
|
@ -32,6 +32,7 @@ final class ConduitAPI_diffusion_getrecentcommitsbypath_Method
|
|||
return array(
|
||||
'callsign' => 'required string',
|
||||
'path' => 'required string',
|
||||
'limit' => 'optional int',
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -51,8 +52,13 @@ final class ConduitAPI_diffusion_getrecentcommitsbypath_Method
|
|||
'path' => $request->getValue('path'),
|
||||
));
|
||||
|
||||
$limit = nonempty(
|
||||
$request->getValue('limit'),
|
||||
self::DEFAULT_LIMIT
|
||||
);
|
||||
|
||||
$history = DiffusionHistoryQuery::newFromDiffusionRequest($drequest)
|
||||
->setLimit(self::RESULT_LIMIT)
|
||||
->setLimit($limit)
|
||||
->needDirectChanges(true)
|
||||
->needChildChanges(true)
|
||||
->loadHistory();
|
||||
|
|
|
@ -162,7 +162,7 @@ class DifferentialReplyHandler extends PhabricatorMailReplyHandler {
|
|||
$exception_mail = new DifferentialExceptionMail(
|
||||
$this->getMailReceiver(),
|
||||
$ex,
|
||||
$body);
|
||||
$this->receivedMail->getRawTextBody());
|
||||
|
||||
$exception_mail->setToPHIDs(array($this->getActor()->getPHID()));
|
||||
$exception_mail->send();
|
||||
|
|
|
@ -67,6 +67,20 @@ final class DifferentialRevisionStatsController extends DifferentialController {
|
|||
|
||||
return $table->loadAllFromArray($rows);
|
||||
}
|
||||
|
||||
private function loadDiffs(array $revisions) {
|
||||
if (!$revisions) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$diff_teml = new DifferentialDiff();
|
||||
$diffs = $diff_teml->loadAllWhere(
|
||||
'revisionID in (%Ld)',
|
||||
array_keys($revisions)
|
||||
);
|
||||
return $diffs;
|
||||
}
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->filter = idx($data, 'filter');
|
||||
}
|
||||
|
@ -127,13 +141,16 @@ final class DifferentialRevisionStatsController extends DifferentialController {
|
|||
|
||||
$comments = $this->loadComments($params['phid']);
|
||||
$revisions = $this->loadRevisions($params['phid']);
|
||||
$diffs = $this->loadDiffs($revisions);
|
||||
|
||||
$panel = new AphrontPanelView();
|
||||
$panel->setHeader('Differential rate analysis');
|
||||
$panel->appendChild(
|
||||
id(new DifferentialRevisionStatsView())
|
||||
->setComments($comments)
|
||||
->setFilter($this->filter)
|
||||
->setRevisions($revisions)
|
||||
->setDiffs($diffs)
|
||||
->setUser($user));
|
||||
$panels[] = $panel;
|
||||
|
||||
|
|
|
@ -940,49 +940,36 @@ final class DifferentialRevisionViewController extends DifferentialController {
|
|||
$vcs = $repository ? $repository->getVersionControlSystem() : null;
|
||||
switch ($vcs) {
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
|
||||
$raw_diff = $bundle->toGitPatch();
|
||||
break;
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
|
||||
default:
|
||||
$raw_diff = $bundle->toUnifiedDiff();
|
||||
break;
|
||||
}
|
||||
|
||||
$hash = PhabricatorHash::digest($raw_diff);
|
||||
$request_uri = $this->getRequest()->getRequestURI();
|
||||
|
||||
$file = id(new PhabricatorFile())->loadOneWhere(
|
||||
'contentHash = %s LIMIT 1',
|
||||
$hash);
|
||||
|
||||
if (!$file) {
|
||||
$request_uri = $this->getRequest()->getRequestURI();
|
||||
|
||||
// this ends up being something like
|
||||
// D123.diff
|
||||
// or the verbose
|
||||
// D123.vs123.id123.whitespaceignore-all.diff
|
||||
// lame but nice to include these options
|
||||
$file_name = ltrim($request_uri->getPath(), '/') . '.';
|
||||
foreach ($request_uri->getQueryParams() as $key => $value) {
|
||||
if ($key == 'download') {
|
||||
continue;
|
||||
}
|
||||
$file_name .= $key . $value . '.';
|
||||
// this ends up being something like
|
||||
// D123.diff
|
||||
// or the verbose
|
||||
// D123.vs123.id123.whitespaceignore-all.diff
|
||||
// lame but nice to include these options
|
||||
$file_name = ltrim($request_uri->getPath(), '/').'.';
|
||||
foreach ($request_uri->getQueryParams() as $key => $value) {
|
||||
if ($key == 'download') {
|
||||
continue;
|
||||
}
|
||||
$file_name .= 'diff';
|
||||
|
||||
// We're just caching the data; this is always safe.
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
|
||||
$file = PhabricatorFile::newFromFileData(
|
||||
$raw_diff,
|
||||
array(
|
||||
'name' => $file_name,
|
||||
));
|
||||
|
||||
unset($unguarded);
|
||||
$file_name .= $key.$value.'.';
|
||||
}
|
||||
$file_name .= 'diff';
|
||||
|
||||
$file = PhabricatorFile::buildFromFileDataOrHash(
|
||||
$raw_diff,
|
||||
array(
|
||||
'name' => $file_name,
|
||||
));
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($file->getBestURI());
|
||||
|
||||
|
|
|
@ -251,8 +251,7 @@ final class DifferentialRevisionEditor {
|
|||
$adapter->setForbiddenCCs($revision->getUnsubscribedPHIDs());
|
||||
|
||||
$xscript = HeraldEngine::loadAndApplyRules($adapter);
|
||||
$xscript_uri = PhabricatorEnv::getProductionURI(
|
||||
'/herald/transcript/'.$xscript->getID().'/');
|
||||
$xscript_uri = '/herald/transcript/'.$xscript->getID().'/';
|
||||
$xscript_phid = $xscript->getPHID();
|
||||
$xscript_header = $xscript->getXHeraldRulesHeader();
|
||||
|
||||
|
|
|
@ -45,16 +45,13 @@ final class DifferentialExceptionMail extends DifferentialMail {
|
|||
$original_body = $this->originalBody;
|
||||
|
||||
$message = $exception->getMessage();
|
||||
$trace = $exception->getTraceAsString();
|
||||
|
||||
return <<<EOBODY
|
||||
Your request failed because an exception was encoutered while processing it:
|
||||
|
||||
EXCEPTION: {$message}
|
||||
|
||||
{$trace}
|
||||
|
||||
-- Original Body --------------------------
|
||||
-- Original Body -------------------------------------------------------------
|
||||
|
||||
{$original_body}
|
||||
|
||||
|
|
|
@ -124,7 +124,6 @@ abstract class DifferentialMail {
|
|||
'<'.implode('>, <', $reviewer_phids).'>');
|
||||
}
|
||||
|
||||
$cc_phids = $revision->getCCPHIDs();
|
||||
if ($cc_phids) {
|
||||
$template->addPHIDHeaders('X-Differential-CC', $cc_phids);
|
||||
$template->addHeader(
|
||||
|
@ -261,34 +260,21 @@ abstract class DifferentialMail {
|
|||
}
|
||||
|
||||
protected function buildBody() {
|
||||
$main_body = $this->renderBody();
|
||||
|
||||
$body = $this->renderBody();
|
||||
$body = new PhabricatorMetaMTAMailBody();
|
||||
$body->addRawSection($main_body);
|
||||
|
||||
$reply_handler = $this->getReplyHandler();
|
||||
$reply_instructions = $reply_handler->getReplyHandlerInstructions();
|
||||
if ($reply_instructions) {
|
||||
$body .=
|
||||
"\nREPLY HANDLER ACTIONS\n".
|
||||
" {$reply_instructions}\n";
|
||||
}
|
||||
$body->addReplySection($reply_handler->getReplyHandlerInstructions());
|
||||
|
||||
if ($this->getHeraldTranscriptURI() && $this->isFirstMailToRecipients()) {
|
||||
$manage_uri = PhabricatorEnv::getProductionURI(
|
||||
'/herald/view/differential/');
|
||||
|
||||
$manage_uri = '/herald/view/differential/';
|
||||
$xscript_uri = $this->getHeraldTranscriptURI();
|
||||
$body .= <<<EOTEXT
|
||||
|
||||
MANAGE HERALD DIFFERENTIAL RULES
|
||||
{$manage_uri}
|
||||
|
||||
WHY DID I GET THIS EMAIL?
|
||||
{$xscript_uri}
|
||||
|
||||
EOTEXT;
|
||||
$body->addHeraldSection($manage_uri, $xscript_uri);
|
||||
}
|
||||
|
||||
return $body;
|
||||
return $body->render();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -22,7 +22,9 @@
|
|||
final class DifferentialRevisionStatsView extends AphrontView {
|
||||
private $comments;
|
||||
private $revisions;
|
||||
private $diffs;
|
||||
private $user;
|
||||
private $filter;
|
||||
|
||||
public function setRevisions(array $revisions) {
|
||||
assert_instances_of($revisions, 'DifferentialRevision');
|
||||
|
@ -36,6 +38,17 @@ final class DifferentialRevisionStatsView extends AphrontView {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setDiffs(array $diffs) {
|
||||
assert_instances_of($diffs, 'DifferentialDiff');
|
||||
$this->diffs = $diffs;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setFilter($filter) {
|
||||
$this->filter = $filter;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setUser($user) {
|
||||
$this->user = $user;
|
||||
return $this;
|
||||
|
@ -56,9 +69,10 @@ final class DifferentialRevisionStatsView extends AphrontView {
|
|||
$dates = array();
|
||||
$counts = array();
|
||||
$lines = array();
|
||||
$boosts = array();
|
||||
$days_with_diffs = array();
|
||||
$count_active = array();
|
||||
$response_time = array();
|
||||
$response_count = array();
|
||||
$now = time();
|
||||
$row_array = array();
|
||||
|
||||
|
@ -72,33 +86,52 @@ final class DifferentialRevisionStatsView extends AphrontView {
|
|||
$counts[$age] = 0;
|
||||
$lines[$age] = 0;
|
||||
$count_active[$age] = 0;
|
||||
$response_time[$age] = array();
|
||||
}
|
||||
|
||||
$revision_diffs_map = mgroup($this->diffs, 'getRevisionID');
|
||||
foreach ($revision_diffs_map as $revision_id => $diffs) {
|
||||
$revision_diffs_map[$revision_id] = msort($diffs, 'getID');
|
||||
}
|
||||
|
||||
foreach ($this->comments as $comment) {
|
||||
$rev_date = $comment->getDateCreated();
|
||||
$comment_date = $comment->getDateCreated();
|
||||
|
||||
$day = phabricator_date($rev_date, $user);
|
||||
$day = phabricator_date($comment_date, $user);
|
||||
$old_daycount = idx($days_with_diffs, $day, 0);
|
||||
$days_with_diffs[$day] = $old_daycount + 1;
|
||||
|
||||
$rev_id = $comment->getRevisionID();
|
||||
|
||||
if (idx($revisions_seen, $rev_id)) {
|
||||
continue;
|
||||
$revision_seen = true;
|
||||
$rev = null;
|
||||
} else {
|
||||
$revision_seen = false;
|
||||
$rev = $id_to_revision_map[$rev_id];
|
||||
$revisions_seen[$rev_id] = true;
|
||||
}
|
||||
$rev = $id_to_revision_map[$rev_id];
|
||||
$revisions_seen[$rev_id] = true;
|
||||
|
||||
foreach ($dates as $age => $cutoff) {
|
||||
if ($cutoff >= $rev_date) {
|
||||
if ($cutoff >= $comment_date) {
|
||||
continue;
|
||||
}
|
||||
if ($rev) {
|
||||
$lines[$age] += $rev->getLineCount();
|
||||
|
||||
if (!$revision_seen) {
|
||||
if ($rev) {
|
||||
$lines[$age] += $rev->getLineCount();
|
||||
}
|
||||
$counts[$age]++;
|
||||
if (!$old_daycount) {
|
||||
$count_active[$age]++;
|
||||
}
|
||||
}
|
||||
$counts[$age]++;
|
||||
if (!$old_daycount) {
|
||||
$count_active[$age]++;
|
||||
|
||||
$diffs = $revision_diffs_map[$rev_id];
|
||||
$target_diff = $this->findTargetDiff($diffs, $comment);
|
||||
if ($target_diff) {
|
||||
$response_time[$age][] =
|
||||
$comment_date - $target_diff->getDateCreated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -123,6 +156,30 @@ final class DifferentialRevisionStatsView extends AphrontView {
|
|||
($counts[$age] + 0.0001)),
|
||||
'Active days' => number_format($count_active[$age]),
|
||||
);
|
||||
|
||||
switch ($this->filter) {
|
||||
case DifferentialAction::ACTION_CLOSE:
|
||||
case DifferentialAction::ACTION_UPDATE:
|
||||
case DifferentialAction::ACTION_COMMENT:
|
||||
break;
|
||||
case DifferentialAction::ACTION_ACCEPT:
|
||||
case DifferentialAction::ACTION_REJECT:
|
||||
$count = count($response_time[$age]);
|
||||
if ($count) {
|
||||
rsort($response_time[$age]);
|
||||
$median = $response_time[$age][round($count / 2) - 1];
|
||||
$average = array_sum($response_time[$age]) / $count;
|
||||
} else {
|
||||
$median = 0;
|
||||
$average = 0;
|
||||
}
|
||||
|
||||
$row_array[$age]['Response hours (median|average)'] =
|
||||
number_format($median / 3600, 1).
|
||||
' | '.
|
||||
number_format($average / 3600, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$rows = array();
|
||||
|
@ -153,4 +210,25 @@ final class DifferentialRevisionStatsView extends AphrontView {
|
|||
|
||||
return $table->render();
|
||||
}
|
||||
|
||||
private function findTargetDiff(array $diffs,
|
||||
DifferentialComment $comment) {
|
||||
switch ($this->filter) {
|
||||
case DifferentialAction::ACTION_CLOSE:
|
||||
case DifferentialAction::ACTION_UPDATE:
|
||||
case DifferentialAction::ACTION_COMMENT:
|
||||
return null;
|
||||
case DifferentialAction::ACTION_ACCEPT:
|
||||
case DifferentialAction::ACTION_REJECT:
|
||||
$result = head($diffs);
|
||||
foreach ($diffs as $diff) {
|
||||
if ($diff->getDateCreated() >= $comment->getDateCreated()) {
|
||||
break;
|
||||
}
|
||||
$result = $diff;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,11 +31,20 @@ final class DiffusionBrowseFileController extends DiffusionController {
|
|||
}
|
||||
|
||||
$path = $drequest->getPath();
|
||||
|
||||
$selected = $request->getStr('view');
|
||||
// If requested without a view, assume that blame is required (see T1278).
|
||||
$preferences = $request->getUser()->loadPreferences();
|
||||
if (!$selected) {
|
||||
$selected = 'blame';
|
||||
$selected = $preferences->getPreference(
|
||||
PhabricatorUserPreferences::PREFERENCE_DIFFUSION_VIEW,
|
||||
'highlighted');
|
||||
} else if ($request->isFormPost() && $selected != 'raw') {
|
||||
$preferences->setPreference(
|
||||
PhabricatorUserPreferences::PREFERENCE_DIFFUSION_VIEW,
|
||||
$selected);
|
||||
$preferences->save();
|
||||
}
|
||||
|
||||
$needs_blame = ($selected == 'blame' || $selected == 'plainblame');
|
||||
|
||||
$file_query = DiffusionFileContentQuery::newFromDiffusionRequest(
|
||||
|
@ -60,7 +69,7 @@ final class DiffusionBrowseFileController extends DiffusionController {
|
|||
require_celerity_resource('diffusion-source-css');
|
||||
|
||||
if ($this->corpusType == 'text') {
|
||||
$view_select_panel = $this->renderViewSelectPanel();
|
||||
$view_select_panel = $this->renderViewSelectPanel($selected);
|
||||
} else {
|
||||
$view_select_panel = null;
|
||||
}
|
||||
|
@ -217,17 +226,17 @@ final class DiffusionBrowseFileController extends DiffusionController {
|
|||
return $corpus;
|
||||
}
|
||||
|
||||
private function renderViewSelectPanel() {
|
||||
private function renderViewSelectPanel($selected) {
|
||||
|
||||
$request = $this->getRequest();
|
||||
|
||||
$select = AphrontFormSelectControl::renderSelectTag(
|
||||
$request->getStr('view'),
|
||||
$selected,
|
||||
array(
|
||||
'blame' => 'View as Highlighted Text with Blame',
|
||||
'highlighted' => 'View as Highlighted Text',
|
||||
'plainblame' => 'View as Plain Text with Blame',
|
||||
'blame' => 'View as Highlighted Text with Blame',
|
||||
'plain' => 'View as Plain Text',
|
||||
'plainblame' => 'View as Plain Text with Blame',
|
||||
'raw' => 'View as raw document',
|
||||
),
|
||||
array(
|
||||
|
@ -235,11 +244,11 @@ final class DiffusionBrowseFileController extends DiffusionController {
|
|||
));
|
||||
|
||||
$view_select_panel = new AphrontPanelView();
|
||||
$view_select_form = phutil_render_tag(
|
||||
'form',
|
||||
$view_select_form = phabricator_render_form(
|
||||
$request->getUser(),
|
||||
array(
|
||||
'action' => $request->getRequestURI(),
|
||||
'method' => 'get',
|
||||
'action' => $request->getRequestURI()->alter('view', null),
|
||||
'method' => 'post',
|
||||
'class' => 'diffusion-browse-type-form',
|
||||
),
|
||||
$select.
|
||||
|
@ -293,15 +302,22 @@ final class DiffusionBrowseFileController extends DiffusionController {
|
|||
$epoch_range = ($epoch_max - $epoch_min) + 1;
|
||||
}
|
||||
|
||||
$min_line = 0;
|
||||
$line = $drequest->getLine();
|
||||
if (strpos($line, '-') !== false) {
|
||||
list($min, $max) = explode('-', $line, 2);
|
||||
$min_line = min($min, $max);
|
||||
$max_line = max($min, $max);
|
||||
} else if (strlen($line)) {
|
||||
$min_line = $line;
|
||||
$max_line = $line;
|
||||
$line_arr = array();
|
||||
$line_str = $drequest->getLine();
|
||||
$ranges = explode(',', $line_str);
|
||||
foreach ($ranges as $range) {
|
||||
if (strpos($range, '-') !== false) {
|
||||
list($min, $max) = explode('-', $range, 2);
|
||||
$line_arr[] = array(
|
||||
'min' => min($min, $max),
|
||||
'max' => max($min, $max),
|
||||
);
|
||||
} else if (strlen($range)) {
|
||||
$line_arr[] = array(
|
||||
'min' => $range,
|
||||
'max' => $range,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$display = array();
|
||||
|
@ -366,12 +382,15 @@ final class DiffusionBrowseFileController extends DiffusionController {
|
|||
}
|
||||
}
|
||||
|
||||
if ($min_line) {
|
||||
if ($line_number == $min_line) {
|
||||
if ($line_arr) {
|
||||
if ($line_number == $line_arr[0]['min']) {
|
||||
$display_line['target'] = true;
|
||||
}
|
||||
if ($line_number >= $min_line && $line_number <= $max_line) {
|
||||
$display_line['highlighted'] = true;
|
||||
foreach ($line_arr as $range) {
|
||||
if ($line_number >= $range['min'] &&
|
||||
$line_number <= $range['max']) {
|
||||
$display_line['highlighted'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -410,10 +429,9 @@ final class DiffusionBrowseFileController extends DiffusionController {
|
|||
'action' => 'browse',
|
||||
'line' => $line['line'],
|
||||
'stable' => true,
|
||||
'params' => array('view' => $selected),
|
||||
));
|
||||
|
||||
$line_href->setQueryParams($request->getRequestURI()->getQueryParams());
|
||||
|
||||
$blame = array();
|
||||
if ($line['color']) {
|
||||
$color = $line['color'];
|
||||
|
@ -453,7 +471,11 @@ final class DiffusionBrowseFileController extends DiffusionController {
|
|||
),
|
||||
phutil_escape_html(phutil_utf8_shorten($line['commit'], 9, '')));
|
||||
|
||||
$revision_id = idx($revision_ids, $commits[$commit]->getPHID());
|
||||
$revision_id = null;
|
||||
if (idx($commits, $commit)) {
|
||||
$revision_id = idx($revision_ids, $commits[$commit]->getPHID());
|
||||
}
|
||||
|
||||
if ($revision_id) {
|
||||
$revision = idx($revisions, $revision_id);
|
||||
if (!$revision) {
|
||||
|
@ -629,25 +651,11 @@ final class DiffusionBrowseFileController extends DiffusionController {
|
|||
}
|
||||
|
||||
private function loadFileForData($path, $data) {
|
||||
$hash = PhabricatorHash::digest($data);
|
||||
|
||||
$file = id(new PhabricatorFile())->loadOneWhere(
|
||||
'contentHash = %s LIMIT 1',
|
||||
$hash);
|
||||
if (!$file) {
|
||||
// We're just caching the data; this is always safe.
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
|
||||
$file = PhabricatorFile::newFromFileData(
|
||||
$data,
|
||||
array(
|
||||
'name' => basename($path),
|
||||
));
|
||||
|
||||
unset($unguarded);
|
||||
}
|
||||
|
||||
return $file;
|
||||
return PhabricatorFile::buildFromFileDataOrHash(
|
||||
$data,
|
||||
array(
|
||||
'name' => basename($path),
|
||||
));
|
||||
}
|
||||
|
||||
private function buildRawResponse($path, $data) {
|
||||
|
|
|
@ -872,23 +872,11 @@ final class DiffusionCommitController extends DiffusionController {
|
|||
$raw_query = DiffusionRawDiffQuery::newFromDiffusionRequest($drequest);
|
||||
$raw_diff = $raw_query->loadRawDiff();
|
||||
|
||||
$hash = PhabricatorHash::digest($raw_diff);
|
||||
|
||||
$file = id(new PhabricatorFile())->loadOneWhere(
|
||||
'contentHash = %s LIMIT 1',
|
||||
$hash);
|
||||
if (!$file) {
|
||||
// We're just caching the data; this is always safe.
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
|
||||
$file = PhabricatorFile::newFromFileData(
|
||||
$raw_diff,
|
||||
array(
|
||||
'name' => $drequest->getCommit().'.diff',
|
||||
));
|
||||
|
||||
unset($unguarded);
|
||||
}
|
||||
$file = PhabricatorFile::buildFromFileDataOrHash(
|
||||
$raw_diff,
|
||||
array(
|
||||
'name' => $drequest->getCommit().'.diff',
|
||||
));
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($file->getBestURI());
|
||||
}
|
||||
|
|
|
@ -34,15 +34,12 @@ final class DiffusionPathCompleteController extends DiffusionController {
|
|||
}
|
||||
|
||||
$query_path = $request->getStr('q');
|
||||
$query_path = ltrim($query_path, '/');
|
||||
if (preg_match('@/$@', $query_path)) {
|
||||
$query_dir = $query_path;
|
||||
} else {
|
||||
$query_dir = dirname($query_path);
|
||||
if ($query_dir == '.') {
|
||||
$query_dir = '';
|
||||
}
|
||||
$query_dir = dirname($query_path).'/';
|
||||
}
|
||||
$query_dir = ltrim($query_dir, '/');
|
||||
|
||||
$drequest = DiffusionRequest::newFromDictionary(
|
||||
array(
|
||||
|
|
|
@ -26,20 +26,36 @@ final class DiffusionMercurialHistoryQuery extends DiffusionHistoryQuery {
|
|||
$commit_hash = $drequest->getStableCommitName();
|
||||
|
||||
$path = DiffusionPathIDQuery::normalizePath($path);
|
||||
$path = ltrim($path, '/');
|
||||
|
||||
// NOTE: Using '' as a default path produces the correct behavior if HEAD
|
||||
// is a merge commit; using '.' does not (the merge commit is not included
|
||||
// in the log).
|
||||
$default_path = '';
|
||||
// NOTE: Older versions of Mercurial give different results for these
|
||||
// commands (see T1268):
|
||||
//
|
||||
// $ hg log -- ''
|
||||
// $ hg log
|
||||
//
|
||||
// All versions of Mercurial give different results for these commands
|
||||
// (merge commits are excluded with the "." version):
|
||||
//
|
||||
// $ hg log -- .
|
||||
// $ hg log
|
||||
//
|
||||
// If we don't have a path component in the query, omit it from the command
|
||||
// entirely to avoid these inconsistencies.
|
||||
|
||||
$path_arg = '';
|
||||
if (strlen($path)) {
|
||||
$path_arg = csprintf('-- %s', $path);
|
||||
}
|
||||
|
||||
// NOTE: --branch used to be called --only-branch; use -b for compatibility.
|
||||
list($stdout) = $repository->execxLocalCommand(
|
||||
'log --debug --template %s --limit %d -b %s --rev %s:0 -- %s',
|
||||
'log --debug --template %s --limit %d -b %s --rev %s:0 %C',
|
||||
'{node};{parents}\\n',
|
||||
($this->getOffset() + $this->getLimit()), // No '--skip' in Mercurial.
|
||||
$drequest->getBranch(),
|
||||
$commit_hash,
|
||||
nonempty(ltrim($path, '/'), $default_path));
|
||||
$path_arg);
|
||||
|
||||
$lines = explode("\n", trim($stdout));
|
||||
$lines = array_slice($lines, $this->getOffset());
|
||||
|
|
|
@ -59,8 +59,22 @@ final class DiffusionMercurialRequest extends DiffusionRequest {
|
|||
if ($this->commit) {
|
||||
$this->stableCommitName = $this->commit;
|
||||
} else {
|
||||
|
||||
// NOTE: For branches with spaces in their name like "a b", this
|
||||
// does not work properly:
|
||||
//
|
||||
// $ hg log --rev 'a b'
|
||||
//
|
||||
// We can use revsets instead:
|
||||
//
|
||||
// $ hg log --rev branch('a b')
|
||||
//
|
||||
// ...but they require a somewhat newer version of Mercurial. Instead,
|
||||
// use "-b" flag with limit 1 for greatest compatibility across
|
||||
// versions.
|
||||
|
||||
list($this->stableCommitName) = $this->repository->execxLocalCommand(
|
||||
'log --template=%s --rev %s',
|
||||
'log --template=%s -b %s --limit 1',
|
||||
'{node}',
|
||||
$this->getBranch());
|
||||
}
|
||||
|
|
|
@ -475,7 +475,7 @@ abstract class DiffusionRequest {
|
|||
// Consume the back part of the URI, up to the first "$". Use a negative
|
||||
// lookbehind to prevent matching '$$'. We double the '$' symbol when
|
||||
// encoding so that files with names like "money/$100" will survive.
|
||||
$pattern = '@(?:(?:^|[^$])(?:[$][$])*)[$]([\d-]+)$@';
|
||||
$pattern = '@(?:(?:^|[^$])(?:[$][$])*)[$]([\d-,]+)$@';
|
||||
if (preg_match($pattern, $blob, $matches)) {
|
||||
$result['line'] = $matches[1];
|
||||
$blob = substr($blob, 0, -(strlen($matches[1]) + 1));
|
||||
|
|
|
@ -55,6 +55,12 @@ final class DiffusionURITestCase extends ArcanistPhutilTestCase {
|
|||
'commit' => '$;;semicolon;;$$',
|
||||
'line' => '100',
|
||||
),
|
||||
'branch/path.ext;abc$3-5,7-12,14' => array(
|
||||
'branch' => 'branch',
|
||||
'path' => 'path.ext',
|
||||
'commit' => 'abc',
|
||||
'line' => '3-5,7-12,14',
|
||||
),
|
||||
);
|
||||
|
||||
foreach ($map as $input => $expect) {
|
||||
|
@ -140,6 +146,13 @@ final class DiffusionURITestCase extends ArcanistPhutilTestCase {
|
|||
'path' => 'path/to/file.ext',
|
||||
'commit' => 'abc',
|
||||
),
|
||||
'/diffusion/A/browse/branch/path.ext$3-5%2C7-12%2C14' => array(
|
||||
'action' => 'browse',
|
||||
'callsign' => 'A',
|
||||
'branch' => 'branch',
|
||||
'path' => 'path.ext',
|
||||
'line' => '3-5,7-12,14',
|
||||
),
|
||||
);
|
||||
|
||||
foreach ($map as $expect => $input) {
|
||||
|
|
|
@ -101,6 +101,45 @@ final class PhabricatorFile extends PhabricatorFileDAO {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Given a block of data, try to load an existing file with the same content
|
||||
* if one exists. If it does not, build a new file.
|
||||
*
|
||||
* This method is generally used when we have some piece of semi-trusted data
|
||||
* like a diff or a file from a repository that we want to show to the user.
|
||||
* We can't just dump it out because it may be dangerous for any number of
|
||||
* reasons; instead, we need to serve it through the File abstraction so it
|
||||
* ends up on the CDN domain if one is configured and so on. However, if we
|
||||
* simply wrote a new file every time we'd potentially end up with a lot
|
||||
* of redundant data in file storage.
|
||||
*
|
||||
* To solve these problems, we use file storage as a cache and reuse the
|
||||
* same file again if we've previously written it.
|
||||
*
|
||||
* NOTE: This method unguards writes.
|
||||
*
|
||||
* @param string Raw file data.
|
||||
* @param dict Dictionary of file information.
|
||||
*/
|
||||
public static function buildFromFileDataOrHash(
|
||||
$data,
|
||||
array $params = array()) {
|
||||
|
||||
$file = id(new PhabricatorFile())->loadOneWhere(
|
||||
'contentHash = %s LIMIT 1',
|
||||
PhabricatorHash::digest($data));
|
||||
|
||||
if (!$file) {
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
$file = PhabricatorFile::newFromFileData($data, $params);
|
||||
unset($unguarded);
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
|
||||
public static function newFromFileData($data, array $params = array()) {
|
||||
$selector = PhabricatorEnv::newObjectFromConfig('storage.engine-selector');
|
||||
|
||||
|
|
35
src/applications/harbormaster/storage/HarbormasterObject.php
Normal file
35
src/applications/harbormaster/storage/HarbormasterObject.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
final class HarbormasterObject extends HarbormasterDAO {
|
||||
|
||||
protected $phid;
|
||||
protected $name;
|
||||
|
||||
public function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_AUX_PHID => true,
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
public function generatePHID() {
|
||||
return PhabricatorPHID::generateNewPHID(
|
||||
PhabricatorPHIDConstants::PHID_TYPE_TOBJ);
|
||||
}
|
||||
|
||||
}
|
|
@ -56,14 +56,16 @@ final class ManiphestTaskDescriptionChangeController
|
|||
$transactions = array($transaction);
|
||||
|
||||
$phids = array();
|
||||
foreach ($transactions as $transaction) {
|
||||
foreach ($transaction->extractPHIDs() as $phid) {
|
||||
foreach ($transactions as $xaction) {
|
||||
foreach ($xaction->extractPHIDs() as $phid) {
|
||||
$phids[$phid] = $phid;
|
||||
}
|
||||
}
|
||||
$handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
|
||||
|
||||
$engine = PhabricatorMarkupEngine::newManiphestMarkupEngine();
|
||||
$engine = new PhabricatorMarkupEngine();
|
||||
$engine->addObject($transaction, ManiphestTransaction::MARKUP_FIELD_BODY);
|
||||
$engine->process();
|
||||
|
||||
$view = new ManiphestTransactionDetailView();
|
||||
$view->setTransactionGroup($transactions);
|
||||
|
|
|
@ -27,11 +27,16 @@ final class ManiphestTaskDescriptionPreviewController
|
|||
$request = $this->getRequest();
|
||||
$description = $request->getStr('description');
|
||||
|
||||
$engine = PhabricatorMarkupEngine::newManiphestMarkupEngine();
|
||||
$task = new ManiphestTask();
|
||||
$task->setDescription($description);
|
||||
|
||||
$output = PhabricatorMarkupEngine::renderOneObject(
|
||||
$task,
|
||||
ManiphestTask::MARKUP_FIELD_DESCRIPTION);
|
||||
|
||||
$content =
|
||||
'<div class="phabricator-remarkup">'.
|
||||
$engine->markupText($description).
|
||||
$output.
|
||||
'</div>';
|
||||
|
||||
return id(new AphrontAjaxResponse())
|
||||
|
|
|
@ -88,8 +88,6 @@ final class ManiphestTaskDetailController extends ManiphestController {
|
|||
$handles = id(new PhabricatorObjectHandleData($phids))
|
||||
->loadHandles();
|
||||
|
||||
$engine = PhabricatorMarkupEngine::newManiphestMarkupEngine();
|
||||
|
||||
$dict = array();
|
||||
$dict['Status'] =
|
||||
'<strong>'.
|
||||
|
@ -305,9 +303,18 @@ final class ManiphestTaskDetailController extends ManiphestController {
|
|||
$headsup_panel->setActionList($action_list);
|
||||
$headsup_panel->setProperties($dict);
|
||||
|
||||
$engine = new PhabricatorMarkupEngine();
|
||||
$engine->addObject($task, ManiphestTask::MARKUP_FIELD_DESCRIPTION);
|
||||
foreach ($transactions as $xaction) {
|
||||
if ($xaction->hasComments()) {
|
||||
$engine->addObject($xaction, ManiphestTransaction::MARKUP_FIELD_BODY);
|
||||
}
|
||||
}
|
||||
$engine->process();
|
||||
|
||||
$headsup_panel->appendChild(
|
||||
'<div class="phabricator-remarkup">'.
|
||||
$engine->markupText($task->getDescription()).
|
||||
$engine->getOutput($task, ManiphestTask::MARKUP_FIELD_DESCRIPTION).
|
||||
'</div>');
|
||||
|
||||
$transaction_types = ManiphestTransactionType::getTransactionTypeMap();
|
||||
|
|
|
@ -119,7 +119,9 @@ final class ManiphestTransactionPreviewController extends ManiphestController {
|
|||
$transactions = array();
|
||||
$transactions[] = $transaction;
|
||||
|
||||
$engine = PhabricatorMarkupEngine::newManiphestMarkupEngine();
|
||||
$engine = new PhabricatorMarkupEngine();
|
||||
$engine->addObject($transaction, ManiphestTransaction::MARKUP_FIELD_BODY);
|
||||
$engine->process();
|
||||
|
||||
$transaction_view = new ManiphestTransactionListView();
|
||||
$transaction_view->setTransactions($transactions);
|
||||
|
|
|
@ -245,7 +245,7 @@ final class ManiphestTransactionEditor {
|
|||
$view->setTransactionGroup($transactions);
|
||||
$view->setHandles($handles);
|
||||
$view->setAuxiliaryFields($this->auxiliaryFields);
|
||||
list($action, $body) = $view->renderForEmail($with_date = false);
|
||||
list($action, $main_body) = $view->renderForEmail($with_date = false);
|
||||
|
||||
$is_create = $this->isCreate($transactions);
|
||||
|
||||
|
@ -253,25 +253,13 @@ final class ManiphestTransactionEditor {
|
|||
|
||||
$reply_handler = $this->buildReplyHandler($task);
|
||||
|
||||
$body = new PhabricatorMetaMTAMailBody();
|
||||
$body->addRawSection($main_body);
|
||||
if ($is_create) {
|
||||
$body .=
|
||||
"\n\n".
|
||||
"TASK DESCRIPTION\n".
|
||||
" ".$task->getDescription();
|
||||
}
|
||||
|
||||
$body .=
|
||||
"\n\n".
|
||||
"TASK DETAIL\n".
|
||||
" ".$task_uri."\n";
|
||||
|
||||
$reply_instructions = $reply_handler->getReplyHandlerInstructions();
|
||||
if ($reply_instructions) {
|
||||
$body .=
|
||||
"\n".
|
||||
"REPLY HANDLER ACTIONS\n".
|
||||
" ".$reply_instructions."\n";
|
||||
$body->addTextSection(pht('TASK DESCRIPTION'), $task->getDescription());
|
||||
}
|
||||
$body->addTextSection(pht('TASK DETAIL'), $task_uri);
|
||||
$body->addReplySection($reply_handler->getReplyHandlerInstructions());
|
||||
|
||||
$thread_id = 'maniphest-task-'.$task->getPHID();
|
||||
$task_id = $task->getID();
|
||||
|
@ -290,7 +278,7 @@ final class ManiphestTransactionEditor {
|
|||
->setRelatedPHID($task->getPHID())
|
||||
->setIsBulk(true)
|
||||
->setMailTags($mailtags)
|
||||
->setBody($body);
|
||||
->setBody($body->render());
|
||||
|
||||
$mails = $reply_handler->multiplexMail(
|
||||
$template,
|
||||
|
|
|
@ -19,7 +19,10 @@
|
|||
/**
|
||||
* @group maniphest
|
||||
*/
|
||||
final class ManiphestTask extends ManiphestDAO {
|
||||
final class ManiphestTask extends ManiphestDAO
|
||||
implements PhabricatorMarkupInterface {
|
||||
|
||||
const MARKUP_FIELD_DESCRIPTION = 'markup:desc';
|
||||
|
||||
protected $phid;
|
||||
protected $authorPHID;
|
||||
|
@ -214,4 +217,52 @@ final class ManiphestTask extends ManiphestDAO {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/* -( Markup Interface )--------------------------------------------------- */
|
||||
|
||||
|
||||
/**
|
||||
* @task markup
|
||||
*/
|
||||
public function getMarkupFieldKey($field) {
|
||||
$hash = PhabricatorHash::digest($this->getMarkupText($field));
|
||||
$id = $this->getID();
|
||||
return "maniphest:T{$id}:{$field}:{$hash}";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task markup
|
||||
*/
|
||||
public function getMarkupText($field) {
|
||||
return $this->getDescription();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task markup
|
||||
*/
|
||||
public function newMarkupEngine($field) {
|
||||
return PhabricatorMarkupEngine::newManiphestMarkupEngine();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task markup
|
||||
*/
|
||||
public function didMarkupText(
|
||||
$field,
|
||||
$output,
|
||||
PhutilMarkupEngine $engine) {
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task markup
|
||||
*/
|
||||
public function shouldUseMarkupCache($field) {
|
||||
return (bool)$this->getID();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,9 +17,13 @@
|
|||
*/
|
||||
|
||||
/**
|
||||
* @task markup Markup Interface
|
||||
* @group maniphest
|
||||
*/
|
||||
final class ManiphestTransaction extends ManiphestDAO {
|
||||
final class ManiphestTransaction extends ManiphestDAO
|
||||
implements PhabricatorMarkupInterface {
|
||||
|
||||
const MARKUP_FIELD_BODY = 'markup:body';
|
||||
|
||||
protected $taskID;
|
||||
protected $authorPHID;
|
||||
|
@ -27,7 +31,6 @@ final class ManiphestTransaction extends ManiphestDAO {
|
|||
protected $oldValue;
|
||||
protected $newValue;
|
||||
protected $comments;
|
||||
protected $cache;
|
||||
protected $metadata = array();
|
||||
protected $contentSource;
|
||||
|
||||
|
@ -143,4 +146,55 @@ final class ManiphestTransaction extends ManiphestDAO {
|
|||
return PhabricatorContentSource::newFromSerialized($this->contentSource);
|
||||
}
|
||||
|
||||
|
||||
/* -( Markup Interface )--------------------------------------------------- */
|
||||
|
||||
|
||||
/**
|
||||
* @task markup
|
||||
*/
|
||||
public function getMarkupFieldKey($field) {
|
||||
if ($this->shouldUseMarkupCache($field)) {
|
||||
$id = $this->getID();
|
||||
} else {
|
||||
$id = PhabricatorHash::digest($this->getMarkupText($field));
|
||||
}
|
||||
return "maniphest:x:{$field}:{$id}";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task markup
|
||||
*/
|
||||
public function getMarkupText($field) {
|
||||
return $this->getComments();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task markup
|
||||
*/
|
||||
public function newMarkupEngine($field) {
|
||||
return PhabricatorMarkupEngine::newManiphestMarkupEngine();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task markup
|
||||
*/
|
||||
public function didMarkupText(
|
||||
$field,
|
||||
$output,
|
||||
PhutilMarkupEngine $engine) {
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task markup
|
||||
*/
|
||||
public function shouldUseMarkupCache($field) {
|
||||
return (bool)$this->getID();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ final class ManiphestTransactionDetailView extends ManiphestView {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setMarkupEngine(PhutilMarkupEngine $engine) {
|
||||
public function setMarkupEngine(PhabricatorMarkupEngine $engine) {
|
||||
$this->markupEngine = $engine;
|
||||
return $this;
|
||||
}
|
||||
|
@ -199,22 +199,12 @@ final class ManiphestTransactionDetailView extends ManiphestView {
|
|||
}
|
||||
|
||||
if ($comment_transaction && $comment_transaction->hasComments()) {
|
||||
$comments = $comment_transaction->getCache();
|
||||
if (!strlen($comments)) {
|
||||
$comments = $comment_transaction->getComments();
|
||||
if (strlen($comments)) {
|
||||
$comments = $this->markupEngine->markupText($comments);
|
||||
$comment_transaction->setCache($comments);
|
||||
if ($comment_transaction->getID() && !$this->preview) {
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
$comment_transaction->save();
|
||||
unset($unguarded);
|
||||
}
|
||||
}
|
||||
}
|
||||
$comment_block = $this->markupEngine->getOutput(
|
||||
$comment_transaction,
|
||||
ManiphestTransaction::MARKUP_FIELD_BODY);
|
||||
$comment_block =
|
||||
'<div class="maniphest-transaction-comments phabricator-remarkup">'.
|
||||
$comments.
|
||||
$comment_block.
|
||||
'</div>';
|
||||
} else {
|
||||
$comment_block = null;
|
||||
|
|
|
@ -45,7 +45,7 @@ final class ManiphestTransactionListView extends ManiphestView {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setMarkupEngine(PhutilMarkupEngine $engine) {
|
||||
public function setMarkupEngine(PhabricatorMarkupEngine $engine) {
|
||||
$this->markupEngine = $engine;
|
||||
return $this;
|
||||
}
|
||||
|
|
|
@ -78,12 +78,16 @@ abstract class PhabricatorMailReplyHandler {
|
|||
assert_instances_of($cc_handles, 'PhabricatorObjectHandle');
|
||||
|
||||
$body = '';
|
||||
if ($to_handles) {
|
||||
$body .= "To: ".implode(', ', mpull($to_handles, 'getName'))."\n";
|
||||
}
|
||||
if ($cc_handles) {
|
||||
$body .= "Cc: ".implode(', ', mpull($cc_handles, 'getName'))."\n";
|
||||
|
||||
if (PhabricatorEnv::getEnvConfig('metamta.recipients.show-hints')) {
|
||||
if ($to_handles) {
|
||||
$body .= "To: ".implode(', ', mpull($to_handles, 'getName'))."\n";
|
||||
}
|
||||
if ($cc_handles) {
|
||||
$body .= "Cc: ".implode(', ', mpull($cc_handles, 'getName'))."\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
|
|
|
@ -222,6 +222,10 @@ final class PhabricatorMetaMTAReceivedMail extends PhabricatorMetaMTADAO {
|
|||
return $parser->stripTextBody($body);
|
||||
}
|
||||
|
||||
public function getRawTextBody() {
|
||||
return idx($this->bodies, 'text');
|
||||
}
|
||||
|
||||
public static function loadReceiverObject($receiver_name) {
|
||||
if (!$receiver_name) {
|
||||
return null;
|
||||
|
|
136
src/applications/metamta/view/PhabricatorMetaMTAMailBody.php
Normal file
136
src/applications/metamta/view/PhabricatorMetaMTAMailBody.php
Normal file
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Render the body of an application email by building it up section-by-section.
|
||||
*
|
||||
* @task compose Composition
|
||||
* @task render Rendering
|
||||
* @group metamta
|
||||
*/
|
||||
final class PhabricatorMetaMTAMailBody {
|
||||
|
||||
private $sections = array();
|
||||
|
||||
|
||||
/* -( Composition )-------------------------------------------------------- */
|
||||
|
||||
|
||||
/**
|
||||
* Add a raw block of text to the email. This will be rendered as-is.
|
||||
*
|
||||
* @param string Block of text.
|
||||
* @return this
|
||||
* @task compose
|
||||
*/
|
||||
public function addRawSection($text) {
|
||||
if (strlen($text)) {
|
||||
$this->sections[] = rtrim($text);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a block of text with a section header. This is rendered like this:
|
||||
*
|
||||
* HEADER
|
||||
* Text is indented.
|
||||
*
|
||||
* @param string Header text.
|
||||
* @param string Section text.
|
||||
* @return this
|
||||
* @task compose
|
||||
*/
|
||||
public function addTextSection($header, $text) {
|
||||
$this->sections[] = $header."\n".$this->indent($text);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a Herald section with a rule management URI and a transcript URI.
|
||||
*
|
||||
* @param string URI to rule management.
|
||||
* @param string URI to rule transcripts.
|
||||
* @return this
|
||||
* @task compose
|
||||
*/
|
||||
public function addHeraldSection($rules_uri, $xscript_uri) {
|
||||
if (!PhabricatorEnv::getEnvConfig('metamta.herald.show-hints')) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->addTextSection(
|
||||
pht('MANAGE HERALD RULES'),
|
||||
PhabricatorEnv::getProductionURI($rules_uri));
|
||||
$this->addTextSection(
|
||||
pht('WHY DID I GET THIS EMAIL?'),
|
||||
PhabricatorEnv::getProductionURI($xscript_uri));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a section with reply handler instructions.
|
||||
*
|
||||
* @param string Reply handler instructions.
|
||||
* @return this
|
||||
* @task compose
|
||||
*/
|
||||
public function addReplySection($instructions) {
|
||||
if (!PhabricatorEnv::getEnvConfig('metamta.reply.show-hints')) {
|
||||
return $this;
|
||||
}
|
||||
if (!strlen($instructions)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->addTextSection(pht('REPLY HANDLER ACTIONS'), $instructions);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/* -( Rendering )---------------------------------------------------------- */
|
||||
|
||||
|
||||
/**
|
||||
* Render the email body.
|
||||
*
|
||||
* @return string Rendered body.
|
||||
* @task render
|
||||
*/
|
||||
public function render() {
|
||||
return implode("\n\n", $this->sections)."\n";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Indent a block of text for rendering under a section heading.
|
||||
*
|
||||
* @param string Text to indent.
|
||||
* @return string Indented text.
|
||||
* @task render
|
||||
*/
|
||||
private function indent($text) {
|
||||
return rtrim(" ".str_replace("\n", "\n ", $text));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @group metamta
|
||||
*/
|
||||
final class PhabricatorMetaMTAMailBodyTestCase extends PhabricatorTestCase {
|
||||
|
||||
|
||||
public function testBodyRender() {
|
||||
$expect = <<<EOTEXT
|
||||
salmon
|
||||
|
||||
HEADER
|
||||
bass
|
||||
trout
|
||||
|
||||
MANAGE HERALD RULES
|
||||
http://test.com/rules/
|
||||
|
||||
WHY DID I GET THIS EMAIL?
|
||||
http://test.com/xscript/
|
||||
|
||||
REPLY HANDLER ACTIONS
|
||||
pike
|
||||
|
||||
EOTEXT;
|
||||
|
||||
$this->assertEmail($expect, true, true);
|
||||
}
|
||||
|
||||
|
||||
public function testBodyRenderNoHerald() {
|
||||
$expect = <<<EOTEXT
|
||||
salmon
|
||||
|
||||
HEADER
|
||||
bass
|
||||
trout
|
||||
|
||||
REPLY HANDLER ACTIONS
|
||||
pike
|
||||
|
||||
EOTEXT;
|
||||
|
||||
$this->assertEmail($expect, false, true);
|
||||
}
|
||||
|
||||
|
||||
public function testBodyRenderNoReply() {
|
||||
$expect = <<<EOTEXT
|
||||
salmon
|
||||
|
||||
HEADER
|
||||
bass
|
||||
trout
|
||||
|
||||
MANAGE HERALD RULES
|
||||
http://test.com/rules/
|
||||
|
||||
WHY DID I GET THIS EMAIL?
|
||||
http://test.com/xscript/
|
||||
|
||||
EOTEXT;
|
||||
|
||||
$this->assertEmail($expect, true, false);
|
||||
}
|
||||
|
||||
private function assertEmail($expect, $herald_hints, $reply_hints) {
|
||||
$env = PhabricatorEnv::beginScopedEnv();
|
||||
$env->overrideEnvConfig('phabricator.base-uri', 'http://test.com/');
|
||||
$env->overrideEnvConfig('metamta.herald.show-hints', $herald_hints);
|
||||
$env->overrideEnvConfig('metamta.reply.show-hints', $reply_hints);
|
||||
|
||||
$body = new PhabricatorMetaMTAMailBody();
|
||||
$body->addRawSection("salmon");
|
||||
$body->addTextSection("HEADER", "bass\ntrout\n");
|
||||
$body->addHeraldSection("/rules/", "/xscript/");
|
||||
$body->addReplySection("pike");
|
||||
|
||||
$this->assertEqual($expect, $body->render());
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -142,40 +142,51 @@ final class PhabricatorOwnersPackage extends PhabricatorOwnersDAO {
|
|||
$path = new PhabricatorOwnersPath();
|
||||
$conn = $package->establishConnection('r');
|
||||
|
||||
$repository_clause = qsprintf($conn, 'AND p.repositoryPHID = %s',
|
||||
$repository_clause = qsprintf(
|
||||
$conn,
|
||||
'AND p.repositoryPHID = %s',
|
||||
$repository->getPHID());
|
||||
|
||||
$limit_clause = '';
|
||||
if (!empty($limit)) {
|
||||
$limit_clause = qsprintf($conn, 'LIMIT %d', $limit);
|
||||
}
|
||||
// NOTE: The list of $paths may be very large if we're coming from
|
||||
// the OwnersWorker and processing, e.g., an SVN commit which created a new
|
||||
// branch. Break it apart so that it will fit within 'max_allowed_packet',
|
||||
// and then merge results in PHP.
|
||||
|
||||
$data = queryfx_all(
|
||||
$conn,
|
||||
'SELECT pkg.id FROM %T pkg JOIN %T p ON p.packageID = pkg.id
|
||||
WHERE p.path IN (%Ls) %Q ORDER BY LENGTH(p.path) DESC %Q',
|
||||
$package->getTableName(),
|
||||
$path->getTableName(),
|
||||
$paths,
|
||||
$repository_clause,
|
||||
$limit_clause);
|
||||
$ids = array();
|
||||
foreach (array_chunk($paths, 128) as $chunk) {
|
||||
$rows = queryfx_all(
|
||||
$conn,
|
||||
'SELECT pkg.id id, LENGTH(p.path) len
|
||||
FROM %T pkg JOIN %T p ON p.packageID = pkg.id
|
||||
WHERE p.path IN (%Ls) %Q',
|
||||
$package->getTableName(),
|
||||
$path->getTableName(),
|
||||
$chunk,
|
||||
$repository_clause);
|
||||
|
||||
$ids = ipull($data, 'id');
|
||||
|
||||
if (empty($ids)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$order = array();
|
||||
foreach ($ids as $id) {
|
||||
if (empty($order[$id])) {
|
||||
$order[$id] = true;
|
||||
foreach ($rows as $row) {
|
||||
$id = (int)$row['id'];
|
||||
$len = (int)$row['len'];
|
||||
if (isset($ids[$id])) {
|
||||
$ids[$id] = max($len, $ids[$id]);
|
||||
} else {
|
||||
$ids[$id] = $len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$packages = $package->loadAllWhere('id in (%Ld)', array_keys($order));
|
||||
if (!$ids) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$packages = array_select_keys($packages, array_keys($order));
|
||||
arsort($ids);
|
||||
if ($limit) {
|
||||
$ids = array_slice($ids, 0, $limit, $preserve_keys = true);
|
||||
}
|
||||
$ids = array_keys($ids);
|
||||
|
||||
$packages = $package->loadAllWhere('id in (%Ld)', array_keys($ids));
|
||||
$packages = array_select_keys($packages, array_keys($ids));
|
||||
|
||||
return $packages;
|
||||
}
|
||||
|
|
|
@ -127,7 +127,10 @@ final class PhabricatorUserEditor {
|
|||
/**
|
||||
* @task edit
|
||||
*/
|
||||
public function changePassword(PhabricatorUser $user, $password) {
|
||||
public function changePassword(
|
||||
PhabricatorUser $user,
|
||||
PhutilOpaqueEnvelope $envelope) {
|
||||
|
||||
if (!$user->getID()) {
|
||||
throw new Exception("User has not been created yet!");
|
||||
}
|
||||
|
@ -135,7 +138,7 @@ final class PhabricatorUserEditor {
|
|||
$user->openTransaction();
|
||||
$user->reload();
|
||||
|
||||
$user->setPassword($password);
|
||||
$user->setPassword($envelope);
|
||||
$user->save();
|
||||
|
||||
$log = PhabricatorUserLog::newLog(
|
||||
|
|
|
@ -0,0 +1,198 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
final class PhabricatorPeopleLdapController
|
||||
extends PhabricatorPeopleController {
|
||||
|
||||
public function shouldRequireAdmin() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private $view;
|
||||
|
||||
public function processRequest() {
|
||||
|
||||
$request = $this->getRequest();
|
||||
$admin = $request->getUser();
|
||||
|
||||
$content = array();
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
->setAction($request->getRequestURI()
|
||||
->alter('search', 'true')->alter('import', null))
|
||||
->setUser($admin)
|
||||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setLabel('LDAP username')
|
||||
->setName('username'))
|
||||
->appendChild(
|
||||
id(new AphrontFormPasswordControl())
|
||||
->setLabel('Password')
|
||||
->setName('password'))
|
||||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setLabel('LDAP query')
|
||||
->setCaption('A filter such as (objectClass=*)')
|
||||
->setName('query'))
|
||||
->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->setValue('Search'));
|
||||
|
||||
$panel = new AphrontPanelView();
|
||||
$panel->setHeader('Import LDAP Users');
|
||||
$panel->appendChild($form);
|
||||
|
||||
|
||||
if ($request->getStr('import')) {
|
||||
$content[] = $this->processImportRequest($request);
|
||||
}
|
||||
|
||||
$content[] = $panel;
|
||||
|
||||
if ($request->getStr('search')) {
|
||||
$content[] = $this->processSearchRequest($request);
|
||||
}
|
||||
|
||||
return $this->buildStandardPageResponse(
|
||||
$content,
|
||||
array(
|
||||
'title' => 'Import Ldap Users',
|
||||
));
|
||||
}
|
||||
|
||||
private function processImportRequest($request) {
|
||||
$admin = $request->getUser();
|
||||
$usernames = $request->getArr('usernames');
|
||||
$emails = $request->getArr('email');
|
||||
$names = $request->getArr('name');
|
||||
|
||||
$panel = new AphrontErrorView();
|
||||
$panel->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
|
||||
$panel->setTitle("Import Successful");
|
||||
$errors = array("Successfully imported users from LDAP");
|
||||
|
||||
|
||||
foreach ($usernames as $username) {
|
||||
$user = new PhabricatorUser();
|
||||
$user->setUsername($username);
|
||||
$user->setRealname($names[$username]);
|
||||
|
||||
$email_obj = id(new PhabricatorUserEmail())
|
||||
->setAddress($emails[$username])
|
||||
->setIsVerified(1);
|
||||
try {
|
||||
id(new PhabricatorUserEditor())
|
||||
->setActor($admin)
|
||||
->createNewUser($user, $email_obj);
|
||||
|
||||
$ldap_info = new PhabricatorUserLDAPInfo();
|
||||
$ldap_info->setLDAPUsername($username);
|
||||
$ldap_info->setUserID($user->getID());
|
||||
$ldap_info->save();
|
||||
$errors[] = 'Successfully added ' . $username;
|
||||
} catch (Exception $ex) {
|
||||
$errors[] = 'Failed to add ' . $username . ' ' . $ex->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
$panel->setErrors($errors);
|
||||
return $panel;
|
||||
|
||||
}
|
||||
|
||||
private function processSearchRequest($request) {
|
||||
$panel = new AphrontPanelView();
|
||||
|
||||
$admin = $request->getUser();
|
||||
|
||||
$username = $request->getStr('username');
|
||||
$password = $request->getStr('password');
|
||||
$search = $request->getStr('query');
|
||||
|
||||
try {
|
||||
$ldap_provider = new PhabricatorLDAPProvider();
|
||||
$envelope = new PhutilOpaqueEnvelope($password);
|
||||
$ldap_provider->auth($username, $envelope);
|
||||
$results = $ldap_provider->search($search);
|
||||
foreach ($results as $key => $result) {
|
||||
$results[$key][] = $this->renderUserInputs($result);
|
||||
}
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($admin);
|
||||
|
||||
$table = new AphrontTableView($results);
|
||||
$table->setHeaders(
|
||||
array(
|
||||
'Username',
|
||||
'Email',
|
||||
'RealName',
|
||||
'Import?',
|
||||
));
|
||||
$form->appendChild($table);
|
||||
$form->setAction($request->getRequestURI()
|
||||
->alter('import', 'true')->alter('search', null))
|
||||
->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->setValue('Import'));
|
||||
|
||||
|
||||
$panel->appendChild($form);
|
||||
} catch (Exception $ex) {
|
||||
$error_view = new AphrontErrorView();
|
||||
$error_view->setTitle('LDAP Search Failed');
|
||||
$error_view->setErrors(array($ex->getMessage()));
|
||||
return $error_view;
|
||||
}
|
||||
return $panel;
|
||||
|
||||
}
|
||||
|
||||
private function renderUserInputs($user) {
|
||||
$username = $user[0];
|
||||
$inputs = phutil_render_tag(
|
||||
'input',
|
||||
array(
|
||||
'type' => 'checkbox',
|
||||
'name' => 'usernames[]',
|
||||
'value' =>$username,
|
||||
),
|
||||
'');
|
||||
|
||||
$inputs .= phutil_render_tag(
|
||||
'input',
|
||||
array(
|
||||
'type' => 'hidden',
|
||||
'name' => "email[$username]",
|
||||
'value' =>$user[1],
|
||||
),
|
||||
'');
|
||||
|
||||
$inputs .= phutil_render_tag(
|
||||
'input',
|
||||
array(
|
||||
'type' => 'hidden',
|
||||
'name' => "name[$username]",
|
||||
'value' =>$user[2],
|
||||
),
|
||||
'');
|
||||
|
||||
return $inputs;
|
||||
}
|
||||
|
||||
}
|
|
@ -130,6 +130,16 @@ final class PhabricatorPeopleListController
|
|||
'class' => 'button green',
|
||||
),
|
||||
'Create New Account'));
|
||||
if (PhabricatorEnv::getEnvConfig('ldap.auth-enabled')) {
|
||||
$panel->addButton(
|
||||
phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => '/people/ldap/',
|
||||
'class' => 'button green'
|
||||
),
|
||||
'Import from Ldap'));
|
||||
}
|
||||
}
|
||||
|
||||
return $this->buildStandardPageResponse($panel, array(
|
||||
|
|
|
@ -59,7 +59,8 @@ final class PhabricatorUserPasswordSettingsPanelController
|
|||
$errors = array();
|
||||
if ($request->isFormPost()) {
|
||||
if (!$valid_token) {
|
||||
if (!$user->comparePassword($request->getStr('old_pw'))) {
|
||||
$envelope = new PhutilOpaqueEnvelope($request->getStr('old_pw'));
|
||||
if (!$user->comparePassword($envelope)) {
|
||||
$errors[] = 'The old password you entered is incorrect.';
|
||||
$e_old = 'Invalid';
|
||||
}
|
||||
|
@ -85,9 +86,10 @@ final class PhabricatorUserPasswordSettingsPanelController
|
|||
// is changed here the CSRF token check will fail.
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
|
||||
$envelope = new PhutilOpaqueEnvelope($pass);
|
||||
id(new PhabricatorUserEditor())
|
||||
->setActor($user)
|
||||
->changePassword($user, $pass);
|
||||
->changePassword($user, $envelope);
|
||||
|
||||
unset($unguarded);
|
||||
|
||||
|
|
|
@ -59,6 +59,9 @@ EXAMPLE;
|
|||
),
|
||||
'User Guide: Configuring an External Editor');
|
||||
|
||||
$font_default = PhabricatorEnv::getEnvConfig('style.monospace');
|
||||
$font_default = phutil_escape_html($font_default);
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($user)
|
||||
->setAction('/settings/page/preferences/')
|
||||
|
@ -90,8 +93,7 @@ EXAMPLE;
|
|||
->setName($pref_monospaced)
|
||||
->setCaption(
|
||||
'Overrides default fonts in tools like Differential. '.
|
||||
'(Default: 10px "Menlo", "Consolas", "Monaco", '.
|
||||
'monospace)')
|
||||
'(Default: '.$font_default.')')
|
||||
->setValue($preferences->getPreference($pref_monospaced)))
|
||||
->appendChild(
|
||||
id(new AphrontFormMarkupControl())
|
||||
|
|
|
@ -137,7 +137,7 @@ final class PhabricatorUserProfileSettingsPanelController
|
|||
asort($translations);
|
||||
$default = PhabricatorEnv::newObjectFromConfig('translation.provider');
|
||||
$translations = array(
|
||||
'' => 'Sever Default ('.$default->getName().')',
|
||||
'' => 'Server Default ('.$default->getName().')',
|
||||
) + $translations;
|
||||
|
||||
$form = new AphrontFormView();
|
||||
|
|
|
@ -74,18 +74,18 @@ final class PhabricatorUser extends PhabricatorUserDAO implements PhutilPerson {
|
|||
PhabricatorPHIDConstants::PHID_TYPE_USER);
|
||||
}
|
||||
|
||||
public function setPassword($password) {
|
||||
public function setPassword(PhutilOpaqueEnvelope $envelope) {
|
||||
if (!$this->getPHID()) {
|
||||
throw new Exception(
|
||||
"You can not set a password for an unsaved user because their PHID ".
|
||||
"is a salt component in the password hash.");
|
||||
}
|
||||
|
||||
if (!strlen($password)) {
|
||||
if (!strlen($envelope->openEnvelope())) {
|
||||
$this->setPasswordHash('');
|
||||
} else {
|
||||
$this->setPasswordSalt(md5(mt_rand()));
|
||||
$hash = $this->hashPassword($password);
|
||||
$hash = $this->hashPassword($envelope);
|
||||
$this->setPasswordHash($hash);
|
||||
}
|
||||
return $this;
|
||||
|
@ -129,26 +129,26 @@ final class PhabricatorUser extends PhabricatorUserDAO implements PhutilPerson {
|
|||
return Filesystem::readRandomCharacters(255);
|
||||
}
|
||||
|
||||
public function comparePassword($password) {
|
||||
if (!strlen($password)) {
|
||||
public function comparePassword(PhutilOpaqueEnvelope $envelope) {
|
||||
if (!strlen($envelope->openEnvelope())) {
|
||||
return false;
|
||||
}
|
||||
if (!strlen($this->getPasswordHash())) {
|
||||
return false;
|
||||
}
|
||||
$password = $this->hashPassword($password);
|
||||
return ($password === $this->getPasswordHash());
|
||||
$password_hash = $this->hashPassword($envelope);
|
||||
return ($password_hash === $this->getPasswordHash());
|
||||
}
|
||||
|
||||
private function hashPassword($password) {
|
||||
$password = $this->getUsername().
|
||||
$password.
|
||||
$this->getPHID().
|
||||
$this->getPasswordSalt();
|
||||
private function hashPassword(PhutilOpaqueEnvelope $envelope) {
|
||||
$hash = $this->getUsername().
|
||||
$envelope->openEnvelope().
|
||||
$this->getPHID().
|
||||
$this->getPasswordSalt();
|
||||
for ($ii = 0; $ii < 1000; $ii++) {
|
||||
$password = md5($password);
|
||||
$hash = md5($hash);
|
||||
}
|
||||
return $password;
|
||||
return $hash;
|
||||
}
|
||||
|
||||
const CSRF_CYCLE_FREQUENCY = 3600;
|
||||
|
|
|
@ -30,6 +30,8 @@ final class PhabricatorUserPreferences extends PhabricatorUserDAO {
|
|||
const PREFERENCE_SEARCHBAR_JUMP = 'searchbar-jump';
|
||||
const PREFERENCE_SEARCH_SHORTCUT = 'search-shortcut';
|
||||
|
||||
const PREFERENCE_DIFFUSION_VIEW = 'diffusion-view';
|
||||
|
||||
protected $userPHID;
|
||||
protected $preferences = array();
|
||||
|
||||
|
|
|
@ -60,8 +60,9 @@ abstract class PhameController extends PhabricatorController {
|
|||
}
|
||||
|
||||
private function renderSideNavFilterView($filter) {
|
||||
$base_uri = new PhutilURI('/phame/');
|
||||
$nav = new AphrontSideNavFilterView();
|
||||
$nav->setBaseURI(new PhutilURI('/phame/'));
|
||||
$nav->setBaseURI($base_uri);
|
||||
$nav->addLabel('Drafts');
|
||||
$nav->addFilter('post/new',
|
||||
'New Draft');
|
||||
|
@ -71,12 +72,16 @@ abstract class PhameController extends PhabricatorController {
|
|||
$nav->addLabel('Posts');
|
||||
$nav->addFilter('post',
|
||||
'My Posts');
|
||||
$nav->addFilter('everyone',
|
||||
'Everyone',
|
||||
$base_uri);
|
||||
foreach ($this->getSideNavExtraPostFilters() as $post_filter) {
|
||||
$nav->addFilter($post_filter['key'],
|
||||
$post_filter['name']);
|
||||
$post_filter['name'],
|
||||
idx($post_filter, 'uri'));
|
||||
}
|
||||
|
||||
$nav->selectFilter($filter, 'post');
|
||||
$nav->selectFilter($filter);
|
||||
|
||||
return $nav;
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@ extends PhameController {
|
|||
$post = id(new PhamePost())
|
||||
->setBloggerPHID($user->getPHID())
|
||||
->setVisibility(PhamePost::VISIBILITY_DRAFT);
|
||||
$cancel_uri = '/phame';
|
||||
$cancel_uri = '/phame/';
|
||||
$submit_button = 'Create Post';
|
||||
$delete_button = null;
|
||||
$page_title = 'Create Post';
|
||||
|
|
|
@ -60,6 +60,12 @@ extends PhameController {
|
|||
return $filters;
|
||||
}
|
||||
|
||||
public function shouldRequireLogin() {
|
||||
// TODO -- get policy logic going
|
||||
// return PhabricatorEnv::getEnvConfig('policy.allow-public');
|
||||
return true;
|
||||
}
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->setPostPHID(idx($data, 'phid'));
|
||||
$this->setPhameTitle(idx($data, 'phametitle'));
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @group phame
|
||||
*/
|
||||
final class PhameAllBloggersPostListController
|
||||
extends PhamePostListBaseController {
|
||||
|
||||
public function shouldRequireLogin() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getSideNavFilter() {
|
||||
return 'everyone';
|
||||
}
|
||||
|
||||
protected function getNoticeView() {
|
||||
$user = $this->getRequest()->getUser();
|
||||
|
||||
$new_link = phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => '/phame/post/new/',
|
||||
'class' => 'button green',
|
||||
),
|
||||
'write a blog post'
|
||||
);
|
||||
|
||||
$remarkup_link = phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' =>
|
||||
PhabricatorEnv::getDoclink('article/Remarkup_Reference.html'),
|
||||
),
|
||||
'remarkup'
|
||||
);
|
||||
|
||||
$guide_link = phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => PhabricatorEnv::getDoclink('article/Phame_User_Guide.html'),
|
||||
),
|
||||
'Phame user guide'
|
||||
);
|
||||
|
||||
$notices = array(
|
||||
'Seek phame and '.$new_link,
|
||||
'Use '.$remarkup_link.' for maximal elegance, grace, and style. ',
|
||||
'If you need more help try the '.$guide_link.'.',
|
||||
);
|
||||
|
||||
$notice_view = id(new AphrontErrorView())
|
||||
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
|
||||
->setTitle('Meta thoughts and feelings');
|
||||
foreach ($notices as $notice) {
|
||||
$notice_view->appendChild('<p>'.$notice.'</p>');
|
||||
}
|
||||
|
||||
return $notice_view;
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$user = $this->getRequest()->getUser();
|
||||
|
||||
$query = new PhamePostQuery();
|
||||
$query->withVisibility(PhamePost::VISIBILITY_PUBLISHED);
|
||||
$this->setPhamePostQuery($query);
|
||||
|
||||
$this->setActions(array('view'));
|
||||
|
||||
$page_title = 'Posts by Everyone';
|
||||
$this->setPageTitle($page_title);
|
||||
|
||||
$this->setShowSideNav(true);
|
||||
|
||||
return $this->buildPostListPageResponse();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @group phame
|
||||
*/
|
||||
final class PhameBloggerPostListController
|
||||
extends PhamePostListBaseController {
|
||||
|
||||
private $bloggerName;
|
||||
|
||||
private function setBloggerName($blogger_name) {
|
||||
$this->bloggerName = $blogger_name;
|
||||
return $this;
|
||||
}
|
||||
private function getBloggerName() {
|
||||
return $this->bloggerName;
|
||||
}
|
||||
|
||||
public function shouldRequireLogin() {
|
||||
// TODO -- get policy logic going
|
||||
// return PhabricatorEnv::getEnvConfig('policy.allow-public');
|
||||
return true;
|
||||
}
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->setBloggerName(idx($data, 'bloggername'));
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$user = $this->getRequest()->getUser();
|
||||
|
||||
$blogger = id(new PhabricatorUser())->loadOneWhere(
|
||||
'username = %s',
|
||||
$this->getBloggerName());
|
||||
if (!$blogger) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
$blogger_phid = $blogger->getPHID();
|
||||
if ($blogger_phid == $user->getPHID()) {
|
||||
$actions = array('view', 'edit');
|
||||
} else {
|
||||
$actions = array('view');
|
||||
}
|
||||
$this->setActions($actions);
|
||||
|
||||
$query = new PhamePostQuery();
|
||||
$query->withBloggerPHID($blogger_phid);
|
||||
$query->withVisibility(PhamePost::VISIBILITY_PUBLISHED);
|
||||
$this->setPhamePostQuery($query);
|
||||
|
||||
$page_title = 'Posts by '.$this->getBloggerName();
|
||||
$this->setPageTitle($page_title);
|
||||
|
||||
$this->setShowSideNav(false);
|
||||
|
||||
return $this->buildPostListPageResponse();
|
||||
}
|
||||
}
|
|
@ -22,8 +22,34 @@
|
|||
final class PhameDraftListController
|
||||
extends PhamePostListBaseController {
|
||||
|
||||
public function shouldRequireLogin() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getSideNavFilter() {
|
||||
return 'draft';
|
||||
}
|
||||
|
||||
protected function isDraft() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$this->setIsDraft(true);
|
||||
return parent::processRequest();
|
||||
$user = $this->getRequest()->getUser();
|
||||
$phid = $user->getPHID();
|
||||
|
||||
$query = new PhamePostQuery();
|
||||
$query->withBloggerPHID($phid);
|
||||
$query->withVisibility(PhamePost::VISIBILITY_DRAFT);
|
||||
$this->setPhamePostQuery($query);
|
||||
|
||||
$actions = array('view', 'edit');
|
||||
$this->setActions($actions);
|
||||
|
||||
$this->setPageTitle('My Drafts');
|
||||
|
||||
$this->setShowSideNav(true);
|
||||
|
||||
return $this->buildPostListPageResponse();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,106 +22,87 @@
|
|||
abstract class PhamePostListBaseController
|
||||
extends PhameController {
|
||||
|
||||
private $bloggerName;
|
||||
private $isDraft;
|
||||
private $phamePostQuery;
|
||||
private $actions;
|
||||
private $pageTitle;
|
||||
|
||||
private function setBloggerName($blogger_name) {
|
||||
$this->bloggerName = $blogger_name;
|
||||
protected function setPageTitle($page_title) {
|
||||
$this->pageTitle = $page_title;
|
||||
return $this;
|
||||
}
|
||||
private function getBloggerName() {
|
||||
return $this->bloggerName;
|
||||
private function getPageTitle() {
|
||||
return $this->pageTitle;
|
||||
}
|
||||
|
||||
protected function getSideNavExtraPostFilters() {
|
||||
if ($this->isDraft() || !$this->getBloggerName()) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return
|
||||
array(array('key' => $this->getSideNavFilter(),
|
||||
'name' => 'Posts by '.$this->getBloggerName()));
|
||||
}
|
||||
|
||||
protected function getSideNavFilter() {
|
||||
if ($this->getBloggerName()) {
|
||||
$filter = 'posts/'.$this->getBloggerName();
|
||||
} else if ($this->isDraft()) {
|
||||
$filter = 'draft';
|
||||
} else {
|
||||
$filter = 'posts';
|
||||
}
|
||||
return $filter;
|
||||
}
|
||||
|
||||
private function isDraft() {
|
||||
return (bool) $this->isDraft;
|
||||
}
|
||||
protected function setIsDraft($is_draft) {
|
||||
$this->isDraft = $is_draft;
|
||||
protected function setActions($actions) {
|
||||
$this->actions = $actions;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->setBloggerName(idx($data, 'bloggername'));
|
||||
private function getActions() {
|
||||
return $this->actions;
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
protected function setPhamePostQuery(PhamePostQuery $query) {
|
||||
$this->phamePostQuery = $query;
|
||||
return $this;
|
||||
}
|
||||
private function getPhamePostQuery() {
|
||||
return $this->phamePostQuery;
|
||||
}
|
||||
|
||||
protected function isDraft() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getPager() {
|
||||
$request = $this->getRequest();
|
||||
$user = $request->getUser();
|
||||
$pager = new AphrontPagerView();
|
||||
$page_size = 50;
|
||||
$pager->setURI($request->getRequestURI(), 'offset');
|
||||
$pager->setPageSize($page_size);
|
||||
$pager->setOffset($request->getInt('offset'));
|
||||
|
||||
if ($this->getBloggerName()) {
|
||||
$blogger = id(new PhabricatorUser())->loadOneWhere(
|
||||
'username = %s',
|
||||
$this->getBloggerName());
|
||||
if (!$blogger) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
$page_title = 'Posts by '.$this->getBloggerName();
|
||||
if ($blogger->getPHID() == $user->getPHID()) {
|
||||
$actions = array('view', 'edit');
|
||||
} else {
|
||||
$actions = array('view');
|
||||
}
|
||||
$this->setShowSideNav(false);
|
||||
} else {
|
||||
$blogger = $user;
|
||||
$page_title = 'Posts by '.$user->getUserName();
|
||||
$actions = array('view', 'edit');
|
||||
$this->setShowSideNav(true);
|
||||
return $pager;
|
||||
}
|
||||
|
||||
protected function getNoticeView() {
|
||||
return null;
|
||||
}
|
||||
|
||||
private function loadBloggersFromPosts(array $posts) {
|
||||
assert_instances_of($posts, 'PhamePost');
|
||||
if (empty($posts)) {
|
||||
return array();
|
||||
}
|
||||
$phid = $blogger->getPHID();
|
||||
// user gets to see their own unpublished stuff
|
||||
if ($phid == $user->getPHID() && $this->isDraft()) {
|
||||
$post_visibility = PhamePost::VISIBILITY_DRAFT;
|
||||
} else {
|
||||
$post_visibility = PhamePost::VISIBILITY_PUBLISHED;
|
||||
}
|
||||
$query = new PhamePostQuery();
|
||||
$query->withBloggerPHID($phid);
|
||||
$query->withVisibility($post_visibility);
|
||||
$posts = $query->executeWithPager($pager);
|
||||
$bloggers = array($blogger->getPHID() => $blogger);
|
||||
|
||||
$blogger_phids = mpull($posts, 'getBloggerPHID', 'getBloggerPHID');
|
||||
|
||||
return
|
||||
id(new PhabricatorObjectHandleData($blogger_phids))->loadHandles();
|
||||
}
|
||||
|
||||
protected function buildPostListPageResponse() {
|
||||
$pager = $this->getPager();
|
||||
$query = $this->getPhamePostQuery();
|
||||
$posts = $query->executeWithPager($pager);
|
||||
|
||||
$bloggers = $this->loadBloggersFromPosts($posts);
|
||||
|
||||
$panel = id(new PhamePostListView())
|
||||
->setUser($user)
|
||||
->setUser($this->getRequest()->getUser())
|
||||
->setBloggers($bloggers)
|
||||
->setPosts($posts)
|
||||
->setActions($actions)
|
||||
->setActions($this->getActions())
|
||||
->setDraftList($this->isDraft());
|
||||
|
||||
return $this->buildStandardPageResponse(
|
||||
array(
|
||||
$this->getNoticeView(),
|
||||
$panel,
|
||||
$pager
|
||||
),
|
||||
array(
|
||||
'title' => $page_title,
|
||||
'title' => $this->getPageTitle(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @group phame
|
||||
*/
|
||||
final class PhameUserPostListController
|
||||
extends PhamePostListBaseController {
|
||||
|
||||
public function shouldRequireLogin() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getSideNavFilter() {
|
||||
return 'post';
|
||||
}
|
||||
|
||||
protected function getNoticeView() {
|
||||
$user = $this->getRequest()->getUser();
|
||||
|
||||
$new_link = phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => '/phame/post/new/',
|
||||
'class' => 'button green',
|
||||
),
|
||||
'write another blog post'
|
||||
);
|
||||
|
||||
$pretty_uri = PhabricatorEnv::getProductionURI(
|
||||
'/phame/posts/'.$user->getUserName().'/');
|
||||
$pretty_link = phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => (string) $pretty_uri
|
||||
),
|
||||
(string) $pretty_uri
|
||||
);
|
||||
|
||||
$notices = array(
|
||||
'Seek even more phame and '.$new_link,
|
||||
'Published posts also appear at the awesome, world-accessible '.
|
||||
'URI: '.$pretty_link
|
||||
);
|
||||
|
||||
$notice_view = id(new AphrontErrorView())
|
||||
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
|
||||
->setTitle('Meta thoughts and feelings');
|
||||
foreach ($notices as $notice) {
|
||||
$notice_view->appendChild('<p>'.$notice.'</p>');
|
||||
}
|
||||
|
||||
return $notice_view;
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$user = $this->getRequest()->getUser();
|
||||
$phid = $user->getPHID();
|
||||
|
||||
$query = new PhamePostQuery();
|
||||
$query->withBloggerPHID($phid);
|
||||
$query->withVisibility(PhamePost::VISIBILITY_PUBLISHED);
|
||||
$this->setPhamePostQuery($query);
|
||||
|
||||
$actions = array('view', 'edit');
|
||||
$this->setActions($actions);
|
||||
|
||||
$this->setPageTitle('My Posts');
|
||||
|
||||
$this->setShowSideNav(true);
|
||||
|
||||
return $this->buildPostListPageResponse();
|
||||
}
|
||||
}
|
|
@ -19,12 +19,24 @@
|
|||
final class PhamePostQuery extends PhabricatorOffsetPagedQuery {
|
||||
|
||||
private $bloggerPHID;
|
||||
private $withoutBloggerPHID;
|
||||
private $visibility;
|
||||
|
||||
/**
|
||||
* Mutually exlusive with @{method:withoutBloggerPHID}.
|
||||
*
|
||||
* @{method:withBloggerPHID} wins because being positive and inclusive is
|
||||
* cool.
|
||||
*/
|
||||
public function withBloggerPHID($blogger_phid) {
|
||||
$this->bloggerPHID = $blogger_phid;
|
||||
return $this;
|
||||
}
|
||||
public function withoutBloggerPHID($blogger_phid) {
|
||||
$this->withoutBloggerPHID = $blogger_phid;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withVisibility($visibility) {
|
||||
$this->visibility = $visibility;
|
||||
return $this;
|
||||
|
@ -60,6 +72,12 @@ final class PhamePostQuery extends PhabricatorOffsetPagedQuery {
|
|||
'bloggerPHID = %s',
|
||||
$this->bloggerPHID
|
||||
);
|
||||
} else if ($this->withoutBloggerPHID) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'bloggerPHID != %s',
|
||||
$this->withoutBloggerPHID
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->visibility !== null) {
|
||||
|
|
|
@ -79,7 +79,7 @@ final class PhamePostDetailView extends AphrontView {
|
|||
$uri = '/phame/draft/';
|
||||
$label = 'Back to Your Drafts';
|
||||
} else {
|
||||
$uri = '/phame/posts/'.$blogger->getUsername();
|
||||
$uri = '/phame/posts/'.$blogger->getUsername().'/';
|
||||
$label = 'More Posts by '.phutil_escape_html($blogger->getUsername());
|
||||
}
|
||||
$button = phutil_render_tag(
|
||||
|
@ -101,6 +101,14 @@ final class PhamePostDetailView extends AphrontView {
|
|||
phabricator_datetime($post->getDateModified(),
|
||||
$user);
|
||||
}
|
||||
$caption .= ' by '.phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => new PhutilURI('/p/'.$blogger->getUsername().'/'),
|
||||
),
|
||||
phutil_escape_html($blogger->getUsername())
|
||||
).'.';
|
||||
|
||||
if ($this->isPreview()) {
|
||||
$width = AphrontPanelView::WIDTH_FULL;
|
||||
} else {
|
||||
|
|
|
@ -59,7 +59,7 @@ final class PhamePostListView extends AphrontView {
|
|||
return $this->posts;
|
||||
}
|
||||
public function setBloggers(array $bloggers) {
|
||||
assert_instances_of($bloggers, 'PhabricatorUser');
|
||||
assert_instances_of($bloggers, 'PhabricatorObjectHandle');
|
||||
$this->bloggers = $bloggers;
|
||||
return $this;
|
||||
}
|
||||
|
@ -99,17 +99,18 @@ final class PhamePostListView extends AphrontView {
|
|||
foreach ($posts as $post) {
|
||||
$blogger_phid = $post->getBloggerPHID();
|
||||
$blogger = $bloggers[$blogger_phid];
|
||||
$blogger_link = $blogger->renderLink();
|
||||
$updated = phabricator_datetime($post->getDateModified(),
|
||||
$user);
|
||||
$body = $engine->markupText($post->getBody());
|
||||
$panel = id(new AphrontPanelView())
|
||||
->setHeader(phutil_escape_html($post->getTitle()))
|
||||
->setCaption('Last updated '.$updated)
|
||||
->setCaption('Last updated '.$updated.' by '.$blogger_link.'.')
|
||||
->appendChild('<div class="phabricator-remarkup">'.$body.'</div>');
|
||||
foreach ($actions as $action) {
|
||||
switch ($action) {
|
||||
case 'view':
|
||||
$uri = $post->getViewURI($blogger->getUsername());
|
||||
$uri = $post->getViewURI($blogger->getName());
|
||||
$label = 'View '.$noun;
|
||||
break;
|
||||
case 'edit':
|
||||
|
|
|
@ -40,4 +40,6 @@ final class PhabricatorPHIDConstants {
|
|||
const PHID_TYPE_OASC = 'OASC';
|
||||
const PHID_TYPE_OASA = 'OASA';
|
||||
const PHID_TYPE_POST = 'POST';
|
||||
const PHID_TYPE_TOBJ = 'TOBJ';
|
||||
|
||||
}
|
||||
|
|
|
@ -103,10 +103,12 @@ final class PhrictionEditController
|
|||
require_celerity_resource('phriction-document-css');
|
||||
|
||||
$e_title = true;
|
||||
$notes = null;
|
||||
$errors = array();
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$title = $request->getStr('title');
|
||||
$notes = $request->getStr('description');
|
||||
|
||||
if (!strlen($title)) {
|
||||
$e_title = 'Required';
|
||||
|
@ -115,12 +117,27 @@ final class PhrictionEditController
|
|||
$e_title = null;
|
||||
}
|
||||
|
||||
if ($document->getID()) {
|
||||
if ($content->getTitle() == $title &&
|
||||
$content->getContent() == $request->getStr('content')) {
|
||||
|
||||
$dialog = new AphrontDialogView();
|
||||
$dialog->setUser($user);
|
||||
$dialog->setTitle('No Edits');
|
||||
$dialog->appendChild(
|
||||
'<p>You did not make any changes to the document.</p>');
|
||||
$dialog->addCancelButton($request->getRequestURI());
|
||||
|
||||
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||
}
|
||||
}
|
||||
|
||||
if (!count($errors)) {
|
||||
$editor = id(PhrictionDocumentEditor::newForSlug($document->getSlug()))
|
||||
->setUser($user)
|
||||
->setTitle($title)
|
||||
->setContent($request->getStr('content'))
|
||||
->setDescription($request->getStr('description'));
|
||||
->setDescription($notes);
|
||||
|
||||
$editor->save();
|
||||
|
||||
|
@ -195,6 +212,7 @@ final class PhrictionEditController
|
|||
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($user)
|
||||
->setWorkflow(true)
|
||||
->setAction($request->getRequestURI()->getPath())
|
||||
->addHiddenInput('slug', $document->getSlug())
|
||||
->addHiddenInput('nodraft', $request->getBool('nodraft'))
|
||||
|
@ -220,7 +238,7 @@ final class PhrictionEditController
|
|||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setLabel('Edit Notes')
|
||||
->setValue($content->getDescription())
|
||||
->setValue($notes)
|
||||
->setError(null)
|
||||
->setName('description'))
|
||||
->appendChild(
|
||||
|
|
|
@ -61,7 +61,7 @@ final class PhrictionHistoryController
|
|||
$rows = array();
|
||||
foreach ($history as $content) {
|
||||
|
||||
$uri = PhrictionDocument::getSlugURI($document->getSlug());
|
||||
$slug_uri = PhrictionDocument::getSlugURI($document->getSlug());
|
||||
$version = $content->getVersion();
|
||||
|
||||
$diff_uri = new PhutilURI('/phriction/diff/'.$document->getID().'/');
|
||||
|
@ -102,7 +102,7 @@ final class PhrictionHistoryController
|
|||
phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $uri.'?v='.$version,
|
||||
'href' => $slug_uri.'?v='.$version,
|
||||
),
|
||||
'Version '.$version),
|
||||
$handles[$content->getAuthorPHID()]->renderLink(),
|
||||
|
|
|
@ -17,9 +17,14 @@
|
|||
*/
|
||||
|
||||
/**
|
||||
* @task markup Markup Interface
|
||||
*
|
||||
* @group phriction
|
||||
*/
|
||||
final class PhrictionContent extends PhrictionDAO {
|
||||
final class PhrictionContent extends PhrictionDAO
|
||||
implements PhabricatorMarkupInterface {
|
||||
|
||||
const MARKUP_FIELD_BODY = 'markup:body';
|
||||
|
||||
protected $id;
|
||||
protected $documentID;
|
||||
|
@ -35,11 +40,55 @@ final class PhrictionContent extends PhrictionDAO {
|
|||
protected $changeRef;
|
||||
|
||||
public function renderContent() {
|
||||
$engine = PhabricatorMarkupEngine::newPhrictionMarkupEngine();
|
||||
$markup = $engine->markupText($this->getContent());
|
||||
return PhabricatorMarkupEngine::renderOneObject(
|
||||
$this,
|
||||
self::MARKUP_FIELD_BODY);
|
||||
}
|
||||
|
||||
|
||||
/* -( Markup Interface )--------------------------------------------------- */
|
||||
|
||||
|
||||
/**
|
||||
* @task markup
|
||||
*/
|
||||
public function getMarkupFieldKey($field) {
|
||||
if ($this->shouldUseMarkupCache($field)) {
|
||||
$id = $this->getID();
|
||||
} else {
|
||||
$id = PhabricatorHash::digest($this->getMarkupText($field));
|
||||
}
|
||||
return "phriction:{$field}:{$id}";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task markup
|
||||
*/
|
||||
public function getMarkupText($field) {
|
||||
return $this->getContent();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task markup
|
||||
*/
|
||||
public function newMarkupEngine($field) {
|
||||
return PhabricatorMarkupEngine::newPhrictionMarkupEngine();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task markup
|
||||
*/
|
||||
public function didMarkupText(
|
||||
$field,
|
||||
$output,
|
||||
PhutilMarkupEngine $engine) {
|
||||
|
||||
$toc = PhutilRemarkupEngineRemarkupHeaderBlockRule::renderTableOfContents(
|
||||
$engine);
|
||||
|
||||
if ($toc) {
|
||||
$toc =
|
||||
'<div class="phabricator-remarkup-toc">'.
|
||||
|
@ -53,8 +102,17 @@ final class PhrictionContent extends PhrictionDAO {
|
|||
return
|
||||
'<div class="phabricator-remarkup">'.
|
||||
$toc.
|
||||
$markup.
|
||||
$output.
|
||||
'</div>';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task markup
|
||||
*/
|
||||
public function shouldUseMarkupCache($field) {
|
||||
return (bool)$this->getID();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -45,6 +45,13 @@ final class PhabricatorRepositorySymbol extends PhabricatorRepositoryDAO {
|
|||
}
|
||||
|
||||
public function getURI() {
|
||||
if (!$this->repository) {
|
||||
// This symbol is in the index, but we don't know which Repository it's
|
||||
// part of. Usually this means the Arcanist Project hasn't been linked
|
||||
// to a Repository. We can't generate a URI, so just fail.
|
||||
return null;
|
||||
}
|
||||
|
||||
$request = DiffusionRequest::newFromDictionary(
|
||||
array(
|
||||
'repository' => $this->getRepository(),
|
||||
|
|
|
@ -111,46 +111,29 @@ final class PhabricatorRepositoryCommitHeraldWorker
|
|||
|
||||
$files = $adapter->loadAffectedPaths();
|
||||
sort($files);
|
||||
$files = implode("\n ", $files);
|
||||
$files = implode("\n", $files);
|
||||
|
||||
$xscript_id = $xscript->getID();
|
||||
|
||||
$manage_uri = PhabricatorEnv::getProductionURI('/herald/view/commits/');
|
||||
$why_uri = PhabricatorEnv::getProductionURI(
|
||||
'/herald/transcript/'.$xscript_id.'/');
|
||||
$manage_uri = '/herald/view/commits/';
|
||||
$why_uri = '/herald/transcript/'.$xscript_id.'/';
|
||||
|
||||
$reply_handler = PhabricatorAuditCommentEditor::newReplyHandlerForCommit(
|
||||
$commit);
|
||||
|
||||
$reply_instructions = $reply_handler->getReplyHandlerInstructions();
|
||||
if ($reply_instructions) {
|
||||
$reply_instructions =
|
||||
"\n".
|
||||
"REPLY HANDLER ACTIONS\n".
|
||||
" ".$reply_instructions."\n";
|
||||
}
|
||||
$template = new PhabricatorMetaMTAMail();
|
||||
|
||||
$inline_patch_text = $this->buildPatch($template, $repository, $commit);
|
||||
|
||||
$body = <<<EOBODY
|
||||
DESCRIPTION
|
||||
{$description}
|
||||
|
||||
DETAILS
|
||||
{$commit_uri}
|
||||
|
||||
DIFFERENTIAL REVISION
|
||||
{$differential}
|
||||
|
||||
AFFECTED FILES
|
||||
{$files}
|
||||
{$reply_instructions}
|
||||
MANAGE HERALD COMMIT RULES
|
||||
{$manage_uri}
|
||||
|
||||
WHY DID I GET THIS EMAIL?
|
||||
{$why_uri}
|
||||
|
||||
EOBODY;
|
||||
$body = new PhabricatorMetaMTAMailBody();
|
||||
$body->addRawSection($description);
|
||||
$body->addTextSection(pht('DETAILS'), $commit_uri);
|
||||
$body->addTextSection(pht('DIFFERENTIAL REVISION'), $differential);
|
||||
$body->addTextSection(pht('AFFECTED FILES'), $files);
|
||||
$body->addReplySection($reply_handler->getReplyHandlerInstructions());
|
||||
$body->addHeraldSection($manage_uri, $why_uri);
|
||||
$body->addRawSection($inline_patch_text);
|
||||
$body = $body->render();
|
||||
|
||||
$prefix = PhabricatorEnv::getEnvConfig('metamta.diffusion.subject-prefix');
|
||||
|
||||
|
@ -159,7 +142,6 @@ EOBODY;
|
|||
$commit);
|
||||
list($thread_id, $thread_topic) = $threading;
|
||||
|
||||
$template = new PhabricatorMetaMTAMail();
|
||||
$template->setRelatedPHID($commit->getPHID());
|
||||
$template->setSubject("{$commit_name}: {$name}");
|
||||
$template->setSubjectPrefix($prefix);
|
||||
|
@ -314,4 +296,99 @@ EOBODY;
|
|||
$publisher->publish();
|
||||
}
|
||||
|
||||
private function buildPatch(
|
||||
PhabricatorMetaMTAMail $template,
|
||||
PhabricatorRepository $repository,
|
||||
PhabricatorRepositoryCommit $commit) {
|
||||
|
||||
$attach_key = 'metamta.diffusion.attach-patches';
|
||||
$inline_key = 'metamta.diffusion.inline-patches';
|
||||
|
||||
$attach_patches = PhabricatorEnv::getEnvConfig($attach_key);
|
||||
$inline_patches = PhabricatorEnv::getEnvConfig($inline_key);
|
||||
|
||||
if (!$attach_patches && !$inline_patches) {
|
||||
return;
|
||||
}
|
||||
|
||||
$encoding = $repository->getDetail('encoding', 'utf-8');
|
||||
|
||||
$result = null;
|
||||
$patch_error = null;
|
||||
|
||||
try {
|
||||
$raw_patch = $this->loadRawPatchText($repository, $commit);
|
||||
if ($attach_patches) {
|
||||
$commit_name = $repository->formatCommitName(
|
||||
$commit->getCommitIdentifier());
|
||||
|
||||
$template->addAttachment(
|
||||
new PhabricatorMetaMTAAttachment(
|
||||
$raw_patch,
|
||||
$commit_name.'.patch',
|
||||
'text/x-patch; charset='.$encoding));
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
phlog($ex);
|
||||
$patch_error = 'Unable to generate: '.$ex->getMessage();
|
||||
}
|
||||
|
||||
if ($patch_error) {
|
||||
$result = $patch_error;
|
||||
} else if ($inline_patches) {
|
||||
$len = substr_count($raw_patch, "\n");
|
||||
if ($len <= $inline_patches) {
|
||||
// We send email as utf8, so we need to convert the text to utf8 if
|
||||
// we can.
|
||||
if (strtolower($encoding) != 'utf-8' &&
|
||||
function_exists('mb_convert_encoding')) {
|
||||
$raw_patch = mb_convert_encoding($raw_patch, 'utf-8', $encoding);
|
||||
}
|
||||
$result = phutil_utf8ize($raw_patch);
|
||||
}
|
||||
}
|
||||
|
||||
if ($result) {
|
||||
$result = "PATCH\n\n{$result}\n";
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function loadRawPatchText(
|
||||
PhabricatorRepository $repository,
|
||||
PhabricatorRepositoryCommit $commit) {
|
||||
|
||||
$drequest = DiffusionRequest::newFromDictionary(
|
||||
array(
|
||||
'repository' => $repository,
|
||||
'commit' => $commit->getCommitIdentifier(),
|
||||
));
|
||||
|
||||
$raw_query = DiffusionRawDiffQuery::newFromDiffusionRequest($drequest);
|
||||
$raw_query->setLinesOfContext(3);
|
||||
|
||||
$time_key = 'metamta.diffusion.time-limit';
|
||||
$byte_key = 'metamta.diffusion.byte-limit';
|
||||
$time_limit = PhabricatorEnv::getEnvConfig($time_key);
|
||||
$byte_limit = PhabricatorEnv::getEnvConfig($byte_key);
|
||||
|
||||
if ($time_limit) {
|
||||
$raw_query->setTimeout($time_limit);
|
||||
}
|
||||
|
||||
$raw_diff = $raw_query->loadRawDiff();
|
||||
|
||||
$size = strlen($raw_diff);
|
||||
if ($byte_limit && $size > $byte_limit) {
|
||||
$pretty_size = phabricator_format_bytes($size);
|
||||
$pretty_limit = phabricator_format_bytes($byte_limit);
|
||||
throw new Exception(
|
||||
"Patch size of {$pretty_size} exceeds configured byte size limit of ".
|
||||
"{$pretty_limit}.");
|
||||
}
|
||||
|
||||
return $raw_diff;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ nothing (the default).
|
|||
|
||||
To configure GitHub OAuth, create a new GitHub Application:
|
||||
|
||||
https://github.com/account/applications/new
|
||||
https://github.com/settings/applications/new
|
||||
|
||||
You should set these things in your application:
|
||||
|
||||
|
@ -116,11 +116,6 @@ set these keys:
|
|||
- **github.auth-permanent**: set to ##true## to prevent unlinking Phabricator
|
||||
accounts from GitHub accounts.
|
||||
|
||||
Note that you can see a list of your GitHub applications here, although it's not
|
||||
immediately clear how to get there via the UI:
|
||||
|
||||
https://github.com/account/applications/
|
||||
|
||||
= Configuring Google OAuth =
|
||||
|
||||
You can configure Google OAuth to allow login, login and registration, or
|
||||
|
|
|
@ -27,12 +27,53 @@ Then, configure:
|
|||
`php`, `arc`, or (for example) `git` from the command line, they should all
|
||||
do something.
|
||||
- Your EDITOR environmental variable should point at some valid CLI editor,
|
||||
like the Git Bash `vim`. (Under `cmd.exe`, you need to point to the actual
|
||||
`vim.exe`, not just the `bin/vim` symlink which runs it under Git Bash
|
||||
since `cmd.exe` does not know how to run the symlink.)
|
||||
like the Git Bash `vim`. You can set this in `arc` if you prefer.
|
||||
See below for details.
|
||||
|
||||
You can set environmental variables somewhere in the `Advanced` tab of the
|
||||
`System` control panel.
|
||||
|
||||
Now you should be able to run `arc` normally (either from `cmd.exe` or
|
||||
Git Bash) and it should work more-or-less properly.
|
||||
|
||||
= Configuring an Editor =
|
||||
|
||||
NOTE: You **can not** use Notepad as your editor, because it does not have a
|
||||
blocking mode. You can use GitPad instead.
|
||||
|
||||
Some arc workflows prompt you to edit large blocks of text using a text editor.
|
||||
You can configure various programs for this purpose, depending on which text
|
||||
editor you prefer. Some editors that will work are:
|
||||
|
||||
- [[ http://notepad-plus-plus.org/ | Notepad++ ]], a good all-around editor.
|
||||
- **vim**, which comes with Git Bash.
|
||||
- [[ https://github.com/github/gitpad | GitPad ]], which allows you to use
|
||||
Notepad as your editor.
|
||||
|
||||
Other editors may also work, but they must have a blocking edit mode.
|
||||
|
||||
To configure an editor, either set the `EDITOR` environmental variable to point
|
||||
at it, or run:
|
||||
|
||||
$ arc set-config editor "\"C:\path\to\some\editor.exe\""
|
||||
|
||||
NOTE: Note the use of quotes. Paths with spaces in them must be quoted, and
|
||||
these quotes must be escaped when passed to `arc set-config`, as in the examples
|
||||
below.
|
||||
|
||||
Specifically, you can use this command for **Notepad++** (adjusting the path for
|
||||
your machine):
|
||||
|
||||
name=Notepad++
|
||||
$ arc set-config editor "\"C:\Program Files (x86)\Notepad++\notepad++.exe\" -multiInst -nosession"
|
||||
|
||||
And this command for Vim (you may need to adjust the path):
|
||||
|
||||
name=vim
|
||||
$ arc set-config editor "\"C:\Program Files (x86)\Git\share\vim\vim73\vim.exe\""
|
||||
|
||||
And this for GitPad (you may need to adjust the path):
|
||||
|
||||
name=GitPad
|
||||
$ arc set-config editor "\"C:\Users\yourusername\AppData\Roaming\GitPad\GitPad.exe\""
|
||||
|
||||
|
|
|
@ -206,10 +206,12 @@ If you're having problems with your listener, try these steps:
|
|||
events instead, to test that your listener reacts to them properly. You
|
||||
might have to use fake data, but this gives you an easy way to test the
|
||||
at least the basics.
|
||||
- For scripts, you can run under `--trace` to see which events are emitted
|
||||
and how many handlers are listening to each event.
|
||||
|
||||
= Next Steps =
|
||||
|
||||
Continue by:
|
||||
|
||||
- taking a look at @{class:PhabricatorExampleEventListener}; or
|
||||
- building a library with @{article:@{article:libphutil Libraries User Guide}.
|
||||
- building a library with @{article:libphutil Libraries User Guide}.
|
|
@ -76,6 +76,10 @@ You can run `aphlict` in the foreground to get output to your console:
|
|||
|
||||
phabricator/ $ ./bin/aphlict --foreground
|
||||
|
||||
You can run `support/aphlict/client/aphlict_test_client.php` to connect to the
|
||||
Aphlict server from the command line. Messages the client receives will be
|
||||
printed to stdout.
|
||||
|
||||
You can set `notification.debug` in your configuration to get additional
|
||||
output in your browser.
|
||||
|
||||
|
|
|
@ -41,9 +41,10 @@ final class PhabricatorDaemonControl {
|
|||
|
||||
if (!$daemons) {
|
||||
echo "There are no running Phabricator daemons.\n";
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
$status = 0;
|
||||
printf(
|
||||
"%-5s\t%-24s\t%s\n",
|
||||
"PID",
|
||||
|
@ -52,10 +53,8 @@ final class PhabricatorDaemonControl {
|
|||
foreach ($daemons as $daemon) {
|
||||
$name = $daemon->getName();
|
||||
if (!$daemon->isRunning()) {
|
||||
$status = 2;
|
||||
$name = '<DEAD> '.$name;
|
||||
if ($daemon->getPIDFile()) {
|
||||
Filesystem::remove($daemon->getPIDFile());
|
||||
}
|
||||
}
|
||||
printf(
|
||||
"%5s\t%-24s\t%s\n",
|
||||
|
@ -66,7 +65,7 @@ final class PhabricatorDaemonControl {
|
|||
$name);
|
||||
}
|
||||
|
||||
return 0;
|
||||
return $status;
|
||||
}
|
||||
|
||||
public function executeStopCommand($pids = null) {
|
||||
|
@ -174,7 +173,8 @@ final class PhabricatorDaemonControl {
|
|||
List available daemons.
|
||||
|
||||
**status**
|
||||
List running daemons.
|
||||
List running daemons. This command will exit with a non-zero exit
|
||||
status if any daemons are not running.
|
||||
|
||||
**help**
|
||||
Show this help.
|
||||
|
|
|
@ -46,7 +46,7 @@ final class PhabricatorGarbageCollectorDaemon extends PhabricatorDaemon {
|
|||
|
||||
if ($now < $start || $now > ($start + $run_for)) {
|
||||
if ($just_ran) {
|
||||
echo "Stopped garbage collector.\n";
|
||||
$this->log("Stopped garbage collector.");
|
||||
$just_ran = false;
|
||||
}
|
||||
// The configuration says we can't collect garbage right now, so
|
||||
|
@ -56,27 +56,26 @@ final class PhabricatorGarbageCollectorDaemon extends PhabricatorDaemon {
|
|||
}
|
||||
|
||||
if (!$just_ran) {
|
||||
echo "Started garbage collector.\n";
|
||||
$this->log("Started garbage collector.");
|
||||
$just_ran = true;
|
||||
}
|
||||
|
||||
$n_herald = $this->collectHeraldTranscripts();
|
||||
$n_daemon = $this->collectDaemonLogs();
|
||||
$n_parse = $this->collectParseCaches();
|
||||
$n_markup = $this->collectMarkupCaches();
|
||||
|
||||
$collected = array(
|
||||
'Herald Transcript' => $n_herald,
|
||||
'Daemon Log' => $n_daemon,
|
||||
'Differential Parse Cache' => $n_parse,
|
||||
'Markup Cache' => $n_markup,
|
||||
);
|
||||
$collected = array_filter($collected);
|
||||
|
||||
foreach ($collected as $thing => $count) {
|
||||
if ($thing == 'Daemon Log' && !$this->getTraceMode()) {
|
||||
continue;
|
||||
}
|
||||
$count = number_format($count);
|
||||
echo "Garbage collected {$count} '{$thing}' objects.\n";
|
||||
$this->log("Garbage collected {$count} '{$thing}' objects.");
|
||||
}
|
||||
|
||||
$total = array_sum($collected);
|
||||
|
@ -154,4 +153,23 @@ final class PhabricatorGarbageCollectorDaemon extends PhabricatorDaemon {
|
|||
return $conn_w->getAffectedRows();
|
||||
}
|
||||
|
||||
private function collectMarkupCaches() {
|
||||
$key = 'gcdaemon.ttl.markup-cache';
|
||||
$ttl = PhabricatorEnv::getEnvConfig($key);
|
||||
if ($ttl <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$table = new PhabricatorMarkupCache();
|
||||
$conn_w = $table->establishConnection('w');
|
||||
|
||||
queryfx(
|
||||
$conn_w,
|
||||
'DELETE FROM %T WHERE dateCreated < %d LIMIT 100',
|
||||
$table->getTableName(),
|
||||
time() - $ttl);
|
||||
|
||||
return $conn_w->getAffectedRows();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -240,16 +240,17 @@ final class PhabricatorIRCObjectNameHandler extends PhabricatorIRCHandler {
|
|||
'name' => $symbol,
|
||||
));
|
||||
|
||||
$default_uri = $this->getURI('/diffusion/symbol/'.$symbol.'/');
|
||||
|
||||
if (count($results) > 1) {
|
||||
$uri = $this->getURI('/diffusion/symbol/'.$symbol.'/');
|
||||
$response = "Multiple symbols named '{$symbol}': {$uri}";
|
||||
$response = "Multiple symbols named '{$symbol}': {$default_uri}";
|
||||
} else if (count($results) == 1) {
|
||||
$result = head($results);
|
||||
$response =
|
||||
$result['type'].' '.
|
||||
$result['name'].' '.
|
||||
'('.$result['language'].'): '.
|
||||
$result['uri'];
|
||||
nonempty($result['uri'], $default_uri);
|
||||
} else {
|
||||
$response = "No symbol '{$symbol}' found anywhere.";
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ final class PhabricatorIRCProtocolHandler extends PhabricatorIRCHandler {
|
|||
|
||||
public function receiveMessage(PhabricatorIRCMessage $message) {
|
||||
switch ($message->getCommand()) {
|
||||
case '422': // Error - no MOTD
|
||||
case '376': // End of MOTD
|
||||
$join = $this->getConfig('join');
|
||||
if (!$join) {
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
final class PhabricatorEdgeTestCase extends PhabricatorTestCase {
|
||||
|
||||
protected function getPhabricatorTestCaseConfiguration() {
|
||||
return array(
|
||||
self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true,
|
||||
);
|
||||
}
|
||||
|
||||
public function testCycleDetection() {
|
||||
|
||||
// The editor should detect that this introduces a cycle and prevent the
|
||||
// edit.
|
||||
|
||||
$user = new PhabricatorUser();
|
||||
|
||||
$obj1 = id(new HarbormasterObject())->save();
|
||||
$obj2 = id(new HarbormasterObject())->save();
|
||||
$phid1 = $obj1->getPHID();
|
||||
$phid2 = $obj2->getPHID();
|
||||
|
||||
$editor = id(new PhabricatorEdgeEditor())
|
||||
->setUser($user)
|
||||
->addEdge($phid1, PhabricatorEdgeConfig::TYPE_TEST_NO_CYCLE, $phid2)
|
||||
->addEdge($phid2, PhabricatorEdgeConfig::TYPE_TEST_NO_CYCLE, $phid1);
|
||||
|
||||
$caught = null;
|
||||
try {
|
||||
$editor->save();
|
||||
} catch (Exception $ex) {
|
||||
$caught = $ex;
|
||||
}
|
||||
|
||||
$this->assertEqual(
|
||||
true,
|
||||
$caught instanceof Exception);
|
||||
|
||||
|
||||
// The first edit should go through (no cycle), bu the second one should
|
||||
// fail (it introduces a cycle).
|
||||
|
||||
$editor = id(new PhabricatorEdgeEditor())
|
||||
->setUser($user)
|
||||
->addEdge($phid1, PhabricatorEdgeConfig::TYPE_TEST_NO_CYCLE, $phid2)
|
||||
->save();
|
||||
|
||||
$editor = id(new PhabricatorEdgeEditor())
|
||||
->setUser($user)
|
||||
->addEdge($phid2, PhabricatorEdgeConfig::TYPE_TEST_NO_CYCLE, $phid1);
|
||||
|
||||
$caught = null;
|
||||
try {
|
||||
$editor->save();
|
||||
} catch (Exception $ex) {
|
||||
$caught = $ex;
|
||||
}
|
||||
|
||||
$this->assertEqual(
|
||||
true,
|
||||
$caught instanceof Exception);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -24,6 +24,8 @@ final class PhabricatorEdgeConfig extends PhabricatorEdgeConstants {
|
|||
const TYPE_TASK_HAS_COMMIT = 1;
|
||||
const TYPE_COMMIT_HAS_TASK = 2;
|
||||
|
||||
const TYPE_TEST_NO_CYCLE = 9000;
|
||||
|
||||
public static function getInverse($edge_type) {
|
||||
static $map = array(
|
||||
self::TYPE_TASK_HAS_COMMIT => self::TYPE_COMMIT_HAS_TASK,
|
||||
|
@ -33,6 +35,13 @@ final class PhabricatorEdgeConfig extends PhabricatorEdgeConstants {
|
|||
return idx($map, $edge_type);
|
||||
}
|
||||
|
||||
public static function shouldPreventCycles($edge_type) {
|
||||
static $map = array(
|
||||
self::TYPE_TEST_NO_CYCLE => true,
|
||||
);
|
||||
return isset($map[$edge_type]);
|
||||
}
|
||||
|
||||
public static function establishConnection($phid_type, $conn_type) {
|
||||
static $class_map = array(
|
||||
PhabricatorPHIDConstants::PHID_TYPE_TASK => 'ManiphestTask',
|
||||
|
@ -43,6 +52,7 @@ final class PhabricatorEdgeConfig extends PhabricatorEdgeConstants {
|
|||
PhabricatorPHIDConstants::PHID_TYPE_PROJ => 'PhabricatorProject',
|
||||
PhabricatorPHIDConstants::PHID_TYPE_MLST =>
|
||||
'PhabricatorMetaMTAMailingList',
|
||||
PhabricatorPHIDConstants::PHID_TYPE_TOBJ => 'HarbormasterObject',
|
||||
);
|
||||
|
||||
$class = idx($class_map, $phid_type);
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
* ->save();
|
||||
*
|
||||
* @task edit Editing Edges
|
||||
* @task cycles Cycle Prevention
|
||||
* @task internal Internals
|
||||
*/
|
||||
final class PhabricatorEdgeEditor {
|
||||
|
@ -113,26 +114,61 @@ final class PhabricatorEdgeEditor {
|
|||
*/
|
||||
public function save() {
|
||||
|
||||
// NOTE: We write edge data first, before doing any transactions, since
|
||||
// it's OK if we just leave it hanging out in space unattached to anything.
|
||||
$cycle_types = $this->getPreventCyclesEdgeTypes();
|
||||
|
||||
$this->writeEdgeData();
|
||||
$locks = array();
|
||||
$caught = null;
|
||||
try {
|
||||
|
||||
static $id = 0;
|
||||
$id++;
|
||||
// NOTE: We write edge data first, before doing any transactions, since
|
||||
// it's OK if we just leave it hanging out in space unattached to
|
||||
// anything.
|
||||
$this->writeEdgeData();
|
||||
|
||||
$this->sendEvent($id, PhabricatorEventType::TYPE_EDGE_WILLEDITEDGES);
|
||||
// If we're going to perform cycle detection, lock the edge type before
|
||||
// doing edits.
|
||||
if ($cycle_types) {
|
||||
$src_phids = ipull($this->addEdges, 'src');
|
||||
foreach ($cycle_types as $cycle_type) {
|
||||
$key = 'edge.cycle:'.$cycle_type;
|
||||
$locks[] = PhabricatorGlobalLock::newLock($key)->lock(15);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Removes first, then adds, so that "remove + add" is a useful
|
||||
// operation meaning "overwrite".
|
||||
static $id = 0;
|
||||
$id++;
|
||||
|
||||
$this->executeRemoves();
|
||||
$this->executeAdds();
|
||||
$this->sendEvent($id, PhabricatorEventType::TYPE_EDGE_WILLEDITEDGES);
|
||||
|
||||
$this->sendEvent($id, PhabricatorEventType::TYPE_EDGE_DIDEDITEDGES);
|
||||
// NOTE: Removes first, then adds, so that "remove + add" is a useful
|
||||
// operation meaning "overwrite".
|
||||
|
||||
$this->executeRemoves();
|
||||
$this->executeAdds();
|
||||
|
||||
foreach ($cycle_types as $cycle_type) {
|
||||
$this->detectCycles($src_phids, $cycle_type);
|
||||
}
|
||||
|
||||
$this->sendEvent($id, PhabricatorEventType::TYPE_EDGE_DIDEDITEDGES);
|
||||
|
||||
$this->saveTransactions();
|
||||
} catch (Exception $ex) {
|
||||
$caught = $ex;
|
||||
}
|
||||
|
||||
|
||||
$this->saveTransactions();
|
||||
if ($caught) {
|
||||
$this->killTransactions();
|
||||
}
|
||||
|
||||
foreach ($locks as $lock) {
|
||||
$lock->unlock();
|
||||
}
|
||||
|
||||
if ($caught) {
|
||||
throw $caught;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -327,6 +363,13 @@ final class PhabricatorEdgeEditor {
|
|||
}
|
||||
}
|
||||
|
||||
private function killTransactions() {
|
||||
foreach ($this->openTransactions as $key => $conn_w) {
|
||||
$conn_w->killTransaction();
|
||||
unset($this->openTransactions[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
private function sendEvent($edit_id, $event_type) {
|
||||
$event = new PhabricatorEvent(
|
||||
$event_type,
|
||||
|
@ -339,4 +382,58 @@ final class PhabricatorEdgeEditor {
|
|||
PhutilEventEngine::dispatchEvent($event);
|
||||
}
|
||||
|
||||
|
||||
/* -( Cycle Prevention )--------------------------------------------------- */
|
||||
|
||||
|
||||
/**
|
||||
* Get a list of all edge types which are being added, and which we should
|
||||
* prevent cycles on.
|
||||
*
|
||||
* @return list<const> List of edge types which should have cycles prevented.
|
||||
* @task cycle
|
||||
*/
|
||||
private function getPreventCyclesEdgeTypes() {
|
||||
$edge_types = array();
|
||||
foreach ($this->addEdges as $edge) {
|
||||
$edge_types[$edge['type']] = true;
|
||||
}
|
||||
foreach ($edge_types as $type => $ignored) {
|
||||
if (!PhabricatorEdgeConfig::shouldPreventCycles($type)) {
|
||||
unset($edge_types[$type]);
|
||||
}
|
||||
}
|
||||
return array_keys($edge_types);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Detect graph cycles of a given edge type. If the edit introduces a cycle,
|
||||
* a @{class:PhabricatorEdgeCycleException} is thrown with details.
|
||||
*
|
||||
* @return void
|
||||
* @task cycle
|
||||
*/
|
||||
private function detectCycles(array $phids, $edge_type) {
|
||||
// For simplicity, we just seed the graph with the affected nodes rather
|
||||
// than seeding it with their edges. To do this, we just add synthetic
|
||||
// edges from an imaginary '<seed>' node to the known edges.
|
||||
|
||||
|
||||
$graph = id(new PhabricatorEdgeGraph())
|
||||
->setEdgeType($edge_type)
|
||||
->addNodes(
|
||||
array(
|
||||
'<seed>' => $phids,
|
||||
))
|
||||
->loadGraph();
|
||||
|
||||
foreach ($phids as $phid) {
|
||||
$cycle = $graph->detectCycles($phid);
|
||||
if ($cycle) {
|
||||
throw new PhabricatorEdgeCycleException($edge_type, $cycle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
final class PhabricatorEdgeCycleException extends Exception {
|
||||
|
||||
private $cycleEdgeType;
|
||||
private $cycle;
|
||||
|
||||
public function __construct($cycle_edge_type, array $cycle) {
|
||||
$this->cycleEdgeType = $cycle_edge_type;
|
||||
$this->cycle = $cycle;
|
||||
|
||||
$cycle_list = implode(', ', $cycle);
|
||||
|
||||
parent::__construct(
|
||||
"Graph cycle detected (type={$cycle_edge_type}, cycle={$cycle_list}).");
|
||||
}
|
||||
|
||||
public function getCycle() {
|
||||
return $this->cycle;
|
||||
}
|
||||
|
||||
public function getCycleEdgeType() {
|
||||
return $this->cycleEdgeType;
|
||||
}
|
||||
|
||||
}
|
50
src/infrastructure/edges/util/PhabricatorEdgeGraph.php
Normal file
50
src/infrastructure/edges/util/PhabricatorEdgeGraph.php
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
final class PhabricatorEdgeGraph extends AbstractDirectedGraph {
|
||||
|
||||
private $edgeType;
|
||||
|
||||
public function setEdgeType($edge_type) {
|
||||
$this->edgeType = $edge_type;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function loadEdges(array $nodes) {
|
||||
if (!$this->edgeType) {
|
||||
throw new Exception("Set edge type before loading graph!");
|
||||
}
|
||||
|
||||
$edges = id(new PhabricatorEdgeQuery())
|
||||
->withSourcePHIDs($nodes)
|
||||
->withEdgeTypes(array($this->edgeType))
|
||||
->execute();
|
||||
|
||||
$results = array_fill_keys($nodes, array());
|
||||
foreach ($edges as $src => $types) {
|
||||
foreach ($types as $type => $dsts) {
|
||||
foreach ($dsts as $dst => $edge) {
|
||||
$results[$src][] = $dst;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
}
|
|
@ -16,41 +16,268 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
class PhabricatorMarkupEngine {
|
||||
/**
|
||||
* Manages markup engine selection, configuration, application, caching and
|
||||
* pipelining.
|
||||
*
|
||||
* @{class:PhabricatorMarkupEngine} can be used to render objects which
|
||||
* implement @{interface:PhabricatorMarkupInterface} in a batched, cache-aware
|
||||
* way. For example, if you have a list of comments written in remarkup (and
|
||||
* the objects implement the correct interface) you can render them by first
|
||||
* building an engine and adding the fields with @{method:addObject}.
|
||||
*
|
||||
* $field = 'field:body'; // Field you want to render. Each object exposes
|
||||
* // one or more fields of markup.
|
||||
*
|
||||
* $engine = new PhabricatorMarkupEngine();
|
||||
* foreach ($comments as $comment) {
|
||||
* $engine->addObject($comment, $field);
|
||||
* }
|
||||
*
|
||||
* Now, call @{method:process} to perform the actual cache/rendering
|
||||
* step. This is a heavyweight call which does batched data access and
|
||||
* transforms the markup into output.
|
||||
*
|
||||
* $engine->process();
|
||||
*
|
||||
* Finally, do something with the results:
|
||||
*
|
||||
* $results = array();
|
||||
* foreach ($comments as $comment) {
|
||||
* $results[] = $engine->getOutput($comment, $field);
|
||||
* }
|
||||
*
|
||||
* If you have a single object to render, you can use the convenience method
|
||||
* @{method:renderOneObject}.
|
||||
*
|
||||
* @task markup Markup Pipeline
|
||||
* @task engine Engine Construction
|
||||
*/
|
||||
final class PhabricatorMarkupEngine {
|
||||
|
||||
public static function extractPHIDsFromMentions(array $content_blocks) {
|
||||
$mentions = array();
|
||||
private $objects = array();
|
||||
|
||||
$engine = self::newDifferentialMarkupEngine();
|
||||
|
||||
foreach ($content_blocks as $content_block) {
|
||||
$engine->markupText($content_block);
|
||||
$phids = $engine->getTextMetadata(
|
||||
PhabricatorRemarkupRuleMention::KEY_MENTIONED,
|
||||
array());
|
||||
$mentions += $phids;
|
||||
}
|
||||
/* -( Markup Pipeline )---------------------------------------------------- */
|
||||
|
||||
return $mentions;
|
||||
|
||||
/**
|
||||
* Convenience method for pushing a single object through the markup
|
||||
* pipeline.
|
||||
*
|
||||
* @param PhabricatorMarkupInterface The object to render.
|
||||
* @param string The field to render.
|
||||
* @return string Marked up output.
|
||||
* @task markup
|
||||
*/
|
||||
public static function renderOneObject(
|
||||
PhabricatorMarkupInterface $object,
|
||||
$field) {
|
||||
return id(new PhabricatorMarkupEngine())
|
||||
->addObject($object, $field)
|
||||
->process()
|
||||
->getOutput($object, $field);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Queue an object for markup generation when @{method:process} is
|
||||
* called. You can retrieve the output later with @{method:getOutput}.
|
||||
*
|
||||
* @param PhabricatorMarkupInterface The object to render.
|
||||
* @param string The field to render.
|
||||
* @return this
|
||||
* @task markup
|
||||
*/
|
||||
public function addObject(PhabricatorMarkupInterface $object, $field) {
|
||||
$key = $this->getMarkupFieldKey($object, $field);
|
||||
$this->objects[$key] = array(
|
||||
'object' => $object,
|
||||
'field' => $field,
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process objects queued with @{method:addObject}. You can then retrieve
|
||||
* the output with @{method:getOutput}.
|
||||
*
|
||||
* @return this
|
||||
* @task markup
|
||||
*/
|
||||
public function process() {
|
||||
$keys = array();
|
||||
foreach ($this->objects as $key => $info) {
|
||||
if (!isset($info['markup'])) {
|
||||
$keys[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$keys) {
|
||||
return;
|
||||
}
|
||||
|
||||
$objects = array_select_keys($this->objects, $keys);
|
||||
|
||||
// Build all the markup engines. We need an engine for each field whether
|
||||
// we have a cache or not, since we still need to postprocess the cache.
|
||||
$engines = array();
|
||||
foreach ($objects as $key => $info) {
|
||||
$engines[$key] = $info['object']->newMarkupEngine($info['field']);
|
||||
}
|
||||
|
||||
// Load or build the preprocessor caches.
|
||||
$blocks = $this->loadPreprocessorCaches($engines, $objects);
|
||||
|
||||
// Finalize the output.
|
||||
foreach ($objects as $key => $info) {
|
||||
$data = $blocks[$key]->getCacheData();
|
||||
$engine = $engines[$key];
|
||||
$field = $info['field'];
|
||||
$object = $info['object'];
|
||||
|
||||
$output = $engine->postprocessText($data);
|
||||
$output = $object->didMarkupText($field, $output, $engine);
|
||||
$this->objects[$key]['output'] = $output;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the output of markup processing for a field queued with
|
||||
* @{method:addObject}. Before you can call this method, you must call
|
||||
* @{method:process}.
|
||||
*
|
||||
* @param PhabricatorMarkupInterface The object to retrieve.
|
||||
* @param string The field to retrieve.
|
||||
* @return string Processed output.
|
||||
* @task markup
|
||||
*/
|
||||
public function getOutput(PhabricatorMarkupInterface $object, $field) {
|
||||
$key = $this->getMarkupFieldKey($object, $field);
|
||||
|
||||
if (empty($this->objects[$key])) {
|
||||
throw new Exception(
|
||||
"Call addObject() before getOutput() (key = '{$key}').");
|
||||
}
|
||||
|
||||
if (!isset($this->objects[$key]['output'])) {
|
||||
throw new Exception(
|
||||
"Call process() before getOutput().");
|
||||
}
|
||||
|
||||
return $this->objects[$key]['output'];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task markup
|
||||
*/
|
||||
private function getMarkupFieldKey(
|
||||
PhabricatorMarkupInterface $object,
|
||||
$field) {
|
||||
return $object->getMarkupFieldKey($field);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task markup
|
||||
*/
|
||||
private function loadPreprocessorCaches(array $engines, array $objects) {
|
||||
$blocks = array();
|
||||
|
||||
$use_cache = array();
|
||||
foreach ($objects as $key => $info) {
|
||||
if ($info['object']->shouldUseMarkupCache($info['field'])) {
|
||||
$use_cache[$key] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($use_cache) {
|
||||
$blocks = id(new PhabricatorMarkupCache())->loadAllWhere(
|
||||
'cacheKey IN (%Ls)',
|
||||
array_keys($use_cache));
|
||||
$blocks = mpull($blocks, null, 'getCacheKey');
|
||||
}
|
||||
|
||||
foreach ($objects as $key => $info) {
|
||||
if (isset($blocks[$key])) {
|
||||
// If we already have a preprocessing cache, we don't need to rebuild
|
||||
// it.
|
||||
continue;
|
||||
}
|
||||
|
||||
$text = $info['object']->getMarkupText($info['field']);
|
||||
$data = $engines[$key]->preprocessText($text);
|
||||
|
||||
// NOTE: This is just debugging information to help sort out cache issues.
|
||||
// If one machine is misconfigured and poisoning caches you can use this
|
||||
// field to hunt it down.
|
||||
|
||||
$metadata = array(
|
||||
'host' => php_uname('n'),
|
||||
);
|
||||
|
||||
$blocks[$key] = id(new PhabricatorMarkupCache())
|
||||
->setCacheKey($key)
|
||||
->setCacheData($data)
|
||||
->setMetadata($metadata);
|
||||
|
||||
if (isset($use_cache[$key])) {
|
||||
// This is just filling a cache and always safe, even on a read pathway.
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
try {
|
||||
$blocks[$key]->save();
|
||||
} catch (AphrontQueryDuplicateKeyException $ex) {
|
||||
// Ignore this, we just raced to write the cache.
|
||||
}
|
||||
unset($unguarded);
|
||||
}
|
||||
}
|
||||
|
||||
return $blocks;
|
||||
}
|
||||
|
||||
|
||||
/* -( Engine Construction )------------------------------------------------ */
|
||||
|
||||
|
||||
/**
|
||||
* @task engine
|
||||
*/
|
||||
public static function newManiphestMarkupEngine() {
|
||||
return self::newMarkupEngine(array(
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task engine
|
||||
*/
|
||||
public static function newPhrictionMarkupEngine() {
|
||||
return self::newMarkupEngine(array(
|
||||
'header.generate-toc' => true,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task engine
|
||||
*/
|
||||
public static function newPhameMarkupEngine() {
|
||||
return self::newMarkupEngine(array(
|
||||
'macros' => false,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task engine
|
||||
*/
|
||||
public static function newFeedMarkupEngine() {
|
||||
return self::newMarkupEngine(
|
||||
array(
|
||||
|
@ -61,6 +288,10 @@ class PhabricatorMarkupEngine {
|
|||
));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task engine
|
||||
*/
|
||||
public static function newDifferentialMarkupEngine(array $options = array()) {
|
||||
return self::newMarkupEngine(array(
|
||||
'custom-inline' => PhabricatorEnv::getEnvConfig(
|
||||
|
@ -71,21 +302,37 @@ class PhabricatorMarkupEngine {
|
|||
));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task engine
|
||||
*/
|
||||
public static function newDiffusionMarkupEngine(array $options = array()) {
|
||||
return self::newMarkupEngine(array(
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task engine
|
||||
*/
|
||||
public static function newProfileMarkupEngine() {
|
||||
return self::newMarkupEngine(array(
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task engine
|
||||
*/
|
||||
public static function newSlowvoteMarkupEngine() {
|
||||
return self::newMarkupEngine(array(
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task engine
|
||||
*/
|
||||
private static function getMarkupEngineDefaultConfiguration() {
|
||||
return array(
|
||||
'pygments' => PhabricatorEnv::getEnvConfig('pygments.enabled'),
|
||||
|
@ -104,6 +351,10 @@ class PhabricatorMarkupEngine {
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task engine
|
||||
*/
|
||||
private static function newMarkupEngine(array $options) {
|
||||
|
||||
$options += self::getMarkupEngineDefaultConfiguration();
|
||||
|
@ -198,4 +449,20 @@ class PhabricatorMarkupEngine {
|
|||
return $engine;
|
||||
}
|
||||
|
||||
public static function extractPHIDsFromMentions(array $content_blocks) {
|
||||
$mentions = array();
|
||||
|
||||
$engine = self::newDifferentialMarkupEngine();
|
||||
|
||||
foreach ($content_blocks as $content_block) {
|
||||
$engine->markupText($content_block);
|
||||
$phids = $engine->getTextMetadata(
|
||||
PhabricatorRemarkupRuleMention::KEY_MENTIONED,
|
||||
array());
|
||||
$mentions += $phids;
|
||||
}
|
||||
|
||||
return $mentions;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
103
src/infrastructure/markup/PhabricatorMarkupInterface.php
Normal file
103
src/infrastructure/markup/PhabricatorMarkupInterface.php
Normal file
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* An object which has one or more fields containing markup that can be
|
||||
* rendered into a display format. Commonly, the fields contain Remarkup and
|
||||
* are rendered into HTML. Implementing this interface allows you to render
|
||||
* objects through @{class:PhabricatorMarkupEngine} and benefit from caching
|
||||
* and pipelining infrastructure.
|
||||
*
|
||||
* An object may have several "fields" of markup. For example, Differential
|
||||
* revisions have a "summary" and a "test plan". In these cases, the `$field`
|
||||
* parameter is used to identify which field is being operated on. For simple
|
||||
* objects like comments, you might only have one field (say, "body"). In
|
||||
* these cases, the implementation can largely ignore the `$field` parameter.
|
||||
*
|
||||
* @task markup Markup Interface
|
||||
*
|
||||
* @group markup
|
||||
*/
|
||||
interface PhabricatorMarkupInterface {
|
||||
|
||||
|
||||
/* -( Markup Interface )--------------------------------------------------- */
|
||||
|
||||
|
||||
/**
|
||||
* Get a key to identify this field. This should uniquely identify the block
|
||||
* of text to be rendered and be usable as a cache key. If the object has a
|
||||
* PHID, using the PHID and the field name is likley reasonable:
|
||||
*
|
||||
* "{$phid}:{$field}"
|
||||
*
|
||||
* @param string Field name.
|
||||
* @return string Cache key.
|
||||
*
|
||||
* @task markup
|
||||
*/
|
||||
public function getMarkupFieldKey($field);
|
||||
|
||||
|
||||
/**
|
||||
* Build the engine the field should use.
|
||||
*
|
||||
* @param string Field name.
|
||||
* @return PhutilRemarkupEngine Markup engine to use.
|
||||
* @task markup
|
||||
*/
|
||||
public function newMarkupEngine($field);
|
||||
|
||||
|
||||
/**
|
||||
* Return the contents of the specified field.
|
||||
*
|
||||
* @param string Field name.
|
||||
* @return string The raw markup contained in the field.
|
||||
* @task markup
|
||||
*/
|
||||
public function getMarkupText($field);
|
||||
|
||||
|
||||
/**
|
||||
* Callback for final postprocessing of output. Normally, you can return
|
||||
* the output unmodified.
|
||||
*
|
||||
* @param string Field name.
|
||||
* @param string The finalized output of the engine.
|
||||
* @param string The engine which generated the output.
|
||||
* @return string Final output.
|
||||
* @task markup
|
||||
*/
|
||||
public function didMarkupText(
|
||||
$field,
|
||||
$output,
|
||||
PhutilMarkupEngine $engine);
|
||||
|
||||
|
||||
/**
|
||||
* Determine if the engine should try to use the markup cache or not.
|
||||
* Generally you should use the cache for durable/permanent content but
|
||||
* should not use the cache for temporary/draft content.
|
||||
*
|
||||
* @return bool True to use the markup cache.
|
||||
* @task markup
|
||||
*/
|
||||
public function shouldUseMarkupCache($field);
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue