1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-26 08:42:41 +01:00

Merge branch 'master' of github.com:facebook/phabricator

This commit is contained in:
mkedia 2012-07-17 20:17:48 -07:00
commit ce3a3f1d14
111 changed files with 3117 additions and 586 deletions

View file

@ -23,7 +23,8 @@
"search" : "Search",
"daemon" : "Daemons, Tasks and Workers",
"irc" : "IRC",
"markup" : "Remarkup Extensions"
"markup" : "Remarkup Extensions",
"metamta" : "MetaMTA (Mail)"
},
"engines" : [
["DivinerArticleEngine", {}],

View file

@ -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,
);

View 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;

View file

@ -0,0 +1,2 @@
ALTER TABLE `{$NAMESPACE}_maniphest`.`maniphest_transaction`
DROP `cache`;

View 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;

View file

@ -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();

View file

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

View file

@ -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',

View file

@ -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',
),

View file

@ -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();
}
}

View file

@ -29,6 +29,10 @@ final class DarkConsoleErrorLogPluginAPI {
self::$discardMode = true;
}
public static function disableDiscardMode() {
self::$discardMode = false;
}
public static function getErrors() {
return self::$errors;
}

View file

@ -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;
}

View file

@ -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 '';
}

View file

@ -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 )-------------------------------------------------- */

View file

@ -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();
}
}

View file

@ -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())

View file

@ -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.';
}
}

View file

@ -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(

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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(

View file

@ -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';
}
}

View 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();
}
}

View file

@ -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(),
);
}
}

View file

@ -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();

View file

@ -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);

View file

@ -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);
}
}

View file

@ -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.
}

View file

@ -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,
);
}

View file

@ -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();

View file

@ -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();

View file

@ -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;

View file

@ -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());

View file

@ -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();

View file

@ -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}

View file

@ -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();
}
/**

View file

@ -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;
}
}
}

View file

@ -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) {

View file

@ -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());
}

View file

@ -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(

View file

@ -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());

View file

@ -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());
}

View file

@ -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));

View file

@ -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) {

View file

@ -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');

View 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);
}
}

View file

@ -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);

View file

@ -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())

View file

@ -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();

View file

@ -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);

View file

@ -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,

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;

View 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));
}
}

View file

@ -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());
}
}

View file

@ -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;
}

View file

@ -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(

View file

@ -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;
}
}

View file

@ -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(

View file

@ -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);

View file

@ -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())

View file

@ -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();

View file

@ -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;

View file

@ -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();

View file

@ -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;
}

View file

@ -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';

View file

@ -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'));

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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(),
));
}
}

View file

@ -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();
}
}

View file

@ -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) {

View file

@ -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 {

View file

@ -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':

View file

@ -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';
}

View file

@ -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(

View file

@ -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(),

View file

@ -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();
}
}

View file

@ -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(),

View file

@ -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;
}
}

View file

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

View file

@ -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\""

View file

@ -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}.

View file

@ -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.

View file

@ -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.

View file

@ -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();
}
}

View file

@ -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.";
}

View file

@ -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) {

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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);
}
}
}
}

View file

@ -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;
}
}

View 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;
}
}

View file

@ -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;
}
}

View 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