diff --git a/conf/__init_conf__.php b/conf/__init_conf__.php index 941efd1b09..3dd66b2ddd 100644 --- a/conf/__init_conf__.php +++ b/conf/__init_conf__.php @@ -1,7 +1,6 @@ setViewer(PhabricatorUser::getOmnipotentUser()) - ->needRepositories(true) - ->execute(); - $table = new PhabricatorRepositorySymbol(); $conn_w = $table->establishConnection('w'); +$projects = queryfx_all( + $conn_w, + 'SELECT * FROM %T', + 'repository_arcanistproject'); + foreach ($projects as $project) { - $repo = $project->getRepository(); + $repo = id(new PhabricatorRepositoryQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withIDs(array($project['repositoryID'])) + ->executeOne(); if (!$repo) { continue; } - echo pht("Migrating symbols for '%s' project...\n", $project->getName()); + echo pht("Migrating symbols for '%s' project...\n", $project['name']); queryfx( $conn_w, 'UPDATE %T SET repositoryPHID = %s WHERE arcanistProjectID = %d', $table->getTableName(), $repo->getPHID(), - $project->getID()); + $project['id']); } diff --git a/resources/sql/autopatches/20150504.symbolsproject.1.php b/resources/sql/autopatches/20150504.symbolsproject.1.php index 8ab77bbef4..037397998b 100644 --- a/resources/sql/autopatches/20150504.symbolsproject.1.php +++ b/resources/sql/autopatches/20150504.symbolsproject.1.php @@ -11,6 +11,10 @@ $raw_projects_data = queryfx_all($conn_r, 'SELECT * FROM %T', $projects_table); $raw_projects_data = ipull($raw_projects_data, null, 'id'); $repository_ids = ipull($raw_projects_data, 'repositoryID'); +if (!$repository_ids) { + return; +} + $repositories = id(new PhabricatorRepositoryQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withIDs($repository_ids) diff --git a/resources/sql/autopatches/20150527.calendar.recurringevents.sql b/resources/sql/autopatches/20150527.calendar.recurringevents.sql new file mode 100644 index 0000000000..43fa15177b --- /dev/null +++ b/resources/sql/autopatches/20150527.calendar.recurringevents.sql @@ -0,0 +1,17 @@ +ALTER TABLE {$NAMESPACE}_calendar.calendar_event + ADD isRecurring BOOL NOT NULL; + +ALTER TABLE {$NAMESPACE}_calendar.calendar_event + ADD recurrenceFrequency LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL; + +ALTER TABLE {$NAMESPACE}_calendar.calendar_event + ADD recurrenceEndDate INT UNSIGNED; + +ALTER TABLE {$NAMESPACE}_calendar.calendar_event + ADD instanceOfEventPHID varbinary(64); + +ALTER TABLE {$NAMESPACE}_calendar.calendar_event + ADD sequenceIndex INT UNSIGNED; + +UPDATE {$NAMESPACE}_calendar.calendar_event + SET recurrenceFrequency = '[]' WHERE recurrenceFrequency = ''; diff --git a/resources/sql/autopatches/20150601.spaces.1.namespace.sql b/resources/sql/autopatches/20150601.spaces.1.namespace.sql new file mode 100644 index 0000000000..302e5e8277 --- /dev/null +++ b/resources/sql/autopatches/20150601.spaces.1.namespace.sql @@ -0,0 +1,12 @@ +CREATE TABLE {$NAMESPACE}_spaces.spaces_namespace ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + namespaceName VARCHAR(255) NOT NULL COLLATE {$COLLATE_TEXT}, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + isDefaultNamespace BOOL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (phid), + UNIQUE KEY `key_default` (isDefaultNamespace) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20150601.spaces.2.xaction.sql b/resources/sql/autopatches/20150601.spaces.2.xaction.sql new file mode 100644 index 0000000000..4222593c6b --- /dev/null +++ b/resources/sql/autopatches/20150601.spaces.2.xaction.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_spaces.spaces_namespacetransaction ( + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + phid VARBINARY(64) NOT NULL, + authorPHID VARBINARY(64) NOT NULL, + objectPHID VARBINARY(64) NOT NULL, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + commentPHID VARBINARY(64), + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}, + oldValue LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + newValue LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + contentSource LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + metadata LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (phid), + KEY `key_object` (objectPHID) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20150602.mlist.1.sql b/resources/sql/autopatches/20150602.mlist.1.sql new file mode 100644 index 0000000000..5479b972a6 --- /dev/null +++ b/resources/sql/autopatches/20150602.mlist.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_user.user + ADD isMailingList BOOL NOT NULL; diff --git a/resources/sql/autopatches/20150602.mlist.2.php b/resources/sql/autopatches/20150602.mlist.2.php new file mode 100644 index 0000000000..a8f2a090ba --- /dev/null +++ b/resources/sql/autopatches/20150602.mlist.2.php @@ -0,0 +1,145 @@ +establishConnection('w'); +$lists = new LiskRawMigrationIterator($conn_w, 'metamta_mailinglist'); + +echo pht('Migrating mailing lists...')."\n"; + +foreach ($lists as $list) { + $name = $list['name']; + $email = $list['email']; + $uri = $list['uri']; + $old_phid = $list['phid']; + + $username = preg_replace('/[^a-zA-Z0-9_-]+/', '-', $name); + $username = preg_replace('/-{2,}/', '-', $username); + $username = trim($username, '-'); + if (!strlen($username)) { + $username = 'mailinglist'; + } + $username .= '-list'; + + $username_okay = false; + for ($suffix = 1; $suffix <= 9; $suffix++) { + if ($suffix == 1) { + $effective_username = $username; + } else { + $effective_username = $username.$suffix; + } + + $collision = id(new PhabricatorPeopleQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withUsernames(array($effective_username)) + ->executeOne(); + if (!$collision) { + $username_okay = true; + break; + } + } + + if (!$username_okay) { + echo pht( + 'Failed to migrate mailing list "%s": unable to generate a unique '. + 'username for it.')."\n"; + continue; + } + + $username = $effective_username; + if (!PhabricatorUser::validateUsername($username)) { + echo pht( + 'Failed to migrate mailing list "%s": unable to generate a valid '. + 'username for it.', + $name)."\n"; + continue; + } + + $address = id(new PhabricatorUserEmail())->loadOneWhere( + 'address = %s', + $email); + if ($address) { + echo pht( + 'Failed to migrate mailing list "%s": an existing user already '. + 'has the email address "%s".', + $name, + $email)."\n"; + continue; + } + + $user = id(new PhabricatorUser()) + ->setUsername($username) + ->setRealName(pht('Mailing List "%s"', $name)) + ->setIsApproved(1) + ->setIsMailingList(1); + + $email_object = id(new PhabricatorUserEmail()) + ->setAddress($email) + ->setIsVerified(1); + + try { + id(new PhabricatorUserEditor()) + ->setActor($user) + ->createNewUser($user, $email_object); + } catch (Exception $ex) { + echo pht( + 'Failed to migrate mailing list "%s": %s.', + $name, + $ex->getMessage())."\n"; + continue; + } + + $new_phid = $user->getPHID(); + + // NOTE: After the PHID type is removed we can't use any Edge code to + // modify edges. + + $edge_type = PhabricatorSubscribedToObjectEdgeType::EDGECONST; + $edge_inverse = PhabricatorObjectHasSubscriberEdgeType::EDGECONST; + + $map = PhabricatorPHIDType::getAllTypes(); + foreach ($map as $type => $spec) { + try { + $object = $spec->newObject(); + if (!$object) { + continue; + } + $object_conn_w = $object->establishConnection('w'); + queryfx( + $object_conn_w, + 'UPDATE %T SET dst = %s WHERE dst = %s AND type = %s', + PhabricatorEdgeConfig::TABLE_NAME_EDGE, + $new_phid, + $old_phid, + $edge_inverse); + } catch (Exception $ex) { + // Just ignore these; they're mostly tables not existing. + continue; + } + } + + try { + $dst_phids = queryfx_all( + $conn_w, + 'SELECT dst FROM %T WHERE src = %s AND type = %s', + PhabricatorEdgeConfig::TABLE_NAME_EDGE, + $old_phid, + $edge_type); + if ($dst_phids) { + $editor = new PhabricatorEdgeEditor(); + foreach ($dst_phids as $dst_phid) { + $editor->addEdge($new_phid, $edge_type, $dst_phid['dst']); + } + $editor->save(); + } + } catch (Exception $ex) { + echo pht( + 'Unable to migrate some inverse edges for mailing list "%s": %s.', + $name, + $ex->getMessage())."\n"; + continue; + } + + echo pht( + 'Migrated mailing list "%s" to mailing list user "%s".', + $name, + $user->getUsername())."\n"; +} diff --git a/resources/sql/autopatches/20150604.spaces.1.sql b/resources/sql/autopatches/20150604.spaces.1.sql new file mode 100644 index 0000000000..6fdfd66d82 --- /dev/null +++ b/resources/sql/autopatches/20150604.spaces.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_pastebin.pastebin_paste + ADD spacePHID VARBINARY(64); diff --git a/resources/sql/autopatches/20150606.mlist.1.php b/resources/sql/autopatches/20150606.mlist.1.php new file mode 100644 index 0000000000..8090f28d69 --- /dev/null +++ b/resources/sql/autopatches/20150606.mlist.1.php @@ -0,0 +1,152 @@ +establishConnection('r'); + +$rows = queryfx_all( + $conn_r, + 'SELECT phid, email FROM %T', + 'metamta_mailinglist'); +if (!$rows) { + echo pht('No mailing lists to migrate.')."\n"; + return; +} + +$list_map = array(); +foreach ($rows as $row) { + $list_map[phutil_utf8_strtolower($row['email'])] = $row['phid']; +} + +$emails = id(new PhabricatorUserEmail())->loadAllWhere( + 'address IN (%Ls)', + array_keys($list_map)); +if (!$emails) { + echo pht('No mailing lists match addresses.')."\n"; + return; +} + +// Create a map from old mailing list PHIDs to new user PHIDs. +$map = array(); +foreach ($emails as $email) { + $user_phid = $email->getUserPHID(); + if (!$user_phid) { + continue; + } + + $address = $email->getAddress(); + $address = phutil_utf8_strtolower($address); + if (isset($list_map[$address])) { + $map[$list_map[$address]] = $user_phid; + } +} + +if (!$map) { + echo pht('No mailing lists match users.')."\n"; + return; +} + +echo pht('Migrating Herald conditions which use mailing lists..')."\n"; + +$table = new HeraldCondition(); +$conn_w = $table->establishConnection('w'); +foreach (new LiskMigrationIterator($table) as $condition) { + $name = $condition->getFieldName(); + if ($name == 'cc') { + // Okay, we can migrate these. + } else { + // This is not a condition type which has mailing lists in its value, so + // don't try to migrate it. + continue; + } + + $value = $condition->getValue(); + if (!is_array($value)) { + // Only migrate PHID lists. + continue; + } + + foreach ($value as $v) { + if (!is_string($v)) { + // Only migrate PHID lists where all members are PHIDs. + continue 2; + } + } + + $new = array(); + $any_change = false; + foreach ($value as $v) { + if (isset($map[$v])) { + $new[] = $map[$v]; + $any_change = true; + } else { + $new[] = $v; + } + } + + if (!$any_change) { + continue; + } + + $id = $condition->getID(); + + queryfx( + $conn_w, + 'UPDATE %T SET value = %s WHERE id = %d', + $table->getTableName(), + json_encode($new), + $id); + + + echo pht('Updated mailing lists in Herald condition %d.', $id)."\n"; +} + +$table = new HeraldAction(); +$conn_w = $table->establishConnection('w'); +foreach (new LiskMigrationIterator($table) as $action) { + $name = $action->getAction(); + if ($name == 'addcc' || $name == 'remcc') { + // Okay, we can migrate these. + } else { + // This is not an action type which has mailing lists in its targets, so + // don't try to migrate it. + continue; + } + + $value = $action->getTarget(); + if (!is_array($value)) { + // Only migrate PHID lists. + continue; + } + + foreach ($value as $v) { + if (!is_string($v)) { + // Only migrate PHID lists where all members are PHIDs. + continue 2; + } + } + + $new = array(); + $any_change = false; + foreach ($value as $v) { + if (isset($map[$v])) { + $new[] = $map[$v]; + $any_change = true; + } else { + $new[] = $v; + } + } + + if (!$any_change) { + continue; + } + + $id = $action->getID(); + + queryfx( + $conn_w, + 'UPDATE %T SET target = %s WHERE id = %d', + $table->getTableName(), + json_encode($new), + $id); + + echo pht('Updated mailing lists in Herald action %d.', $id)."\n"; +} diff --git a/resources/sql/autopatches/20150609.inline.sql b/resources/sql/autopatches/20150609.inline.sql new file mode 100644 index 0000000000..1c9765ac5f --- /dev/null +++ b/resources/sql/autopatches/20150609.inline.sql @@ -0,0 +1,4 @@ +/* This cleans up some errant transactions, see T8483. */ + +DELETE FROM {$NAMESPACE}_differential.differential_transaction + WHERE transactionType = 'core:inlinestate' AND newValue = 'null'; diff --git a/resources/sql/patches/131.migraterevisionquery.php b/resources/sql/patches/131.migraterevisionquery.php index 316c7cdee2..c3f97a04a5 100644 --- a/resources/sql/patches/131.migraterevisionquery.php +++ b/resources/sql/patches/131.migraterevisionquery.php @@ -1,35 +1,3 @@ openTransaction(); -$table->beginReadLocking(); -$conn_w = $table->establishConnection('w'); - -echo pht('Migrating revisions')."\n"; -do { - $revisions = $table->loadAllWhere('branchName IS NULL LIMIT 1000'); - - foreach ($revisions as $revision) { - echo '.'; - - $diff = $revision->loadActiveDiff(); - if (!$diff) { - continue; - } - - $branch_name = $diff->getBranch(); - $arc_project_phid = $diff->getArcanistProjectPHID(); - - queryfx( - $conn_w, - 'UPDATE %T SET branchName = %s, arcanistProjectPHID = %s WHERE id = %d', - $table->getTableName(), - $branch_name, - $arc_project_phid, - $revision->getID()); - } -} while (count($revisions) == 1000); - -$table->endReadLocking(); -$table->saveTransaction(); -echo "\n".pht('Done.')."\n"; +// This migration has been dropped, see T7604 for details. diff --git a/resources/sql/patches/20130508.releephtransactionsmig.php b/resources/sql/patches/20130508.releephtransactionsmig.php index ede09993d2..c5203cf9b6 100644 --- a/resources/sql/patches/20130508.releephtransactionsmig.php +++ b/resources/sql/patches/20130508.releephtransactionsmig.php @@ -5,4 +5,4 @@ // already migrated, so this was cleaned up when ReleephRequestEvent was // removed. -echo "(This migration is obsolete.)\n"; +echo pht('(This migration is obsolete.)')."\n"; diff --git a/scripts/ssh/ssh-exec.php b/scripts/ssh/ssh-exec.php index 0eb9b4a3ad..0426a30825 100755 --- a/scripts/ssh/ssh-exec.php +++ b/scripts/ssh/ssh-exec.php @@ -182,11 +182,11 @@ try { 'P' => $user->getPHID(), )); - if (!$user->isUserActivated()) { + if (!$user->canEstablishSSHSessions()) { throw new Exception( pht( - 'Your account ("%s") is not activated. Visit the web interface '. - 'for more information.', + 'Your account ("%s") does not have permission to establish SSH '. + 'sessions. Visit the web interface for more information.', $user->getUsername())); } diff --git a/scripts/user/account_admin.php b/scripts/user/account_admin.php index dc1cb50190..2fa5446648 100755 --- a/scripts/user/account_admin.php +++ b/scripts/user/account_admin.php @@ -125,7 +125,7 @@ if (strlen($password)) { $is_system_agent = $user->getIsSystemAgent(); $set_system_agent = phutil_console_confirm( - pht('Is this user a bot/script?'), + pht('Is this user a bot?'), $default_no = !$is_system_agent); $verify_email = null; @@ -165,7 +165,7 @@ printf($tpl, pht('Password'), null, printf( $tpl, - pht('Bot/Script'), + pht('Bot'), $original->getIsSystemAgent() ? 'Y' : 'N', $set_system_agent ? 'Y' : 'N'); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index a4db7fda6e..14b61c3149 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -175,8 +175,6 @@ phutil_register_library_map(array( 'AuditConduitAPIMethod' => 'applications/audit/conduit/AuditConduitAPIMethod.php', 'AuditQueryConduitAPIMethod' => 'applications/audit/conduit/AuditQueryConduitAPIMethod.php', 'AuthManageProvidersCapability' => 'applications/auth/capability/AuthManageProvidersCapability.php', - 'CalendarColors' => 'applications/calendar/constants/CalendarColors.php', - 'CalendarConstants' => 'applications/calendar/constants/CalendarConstants.php', 'CalendarTimeUtil' => 'applications/calendar/util/CalendarTimeUtil.php', 'CalendarTimeUtilTestCase' => 'applications/calendar/__tests__/CalendarTimeUtilTestCase.php', 'CelerityAPI' => 'applications/celerity/CelerityAPI.php', @@ -265,7 +263,6 @@ phutil_register_library_map(array( 'ConpherenceTransactionComment' => 'applications/conpherence/storage/ConpherenceTransactionComment.php', 'ConpherenceTransactionQuery' => 'applications/conpherence/query/ConpherenceTransactionQuery.php', 'ConpherenceTransactionRenderer' => 'applications/conpherence/ConpherenceTransactionRenderer.php', - 'ConpherenceTransactionType' => 'applications/conpherence/constants/ConpherenceTransactionType.php', 'ConpherenceTransactionView' => 'applications/conpherence/view/ConpherenceTransactionView.php', 'ConpherenceUpdateActions' => 'applications/conpherence/constants/ConpherenceUpdateActions.php', 'ConpherenceUpdateController' => 'applications/conpherence/controller/ConpherenceUpdateController.php', @@ -506,6 +503,8 @@ phutil_register_library_map(array( 'DiffusionEmptyResultView' => 'applications/diffusion/view/DiffusionEmptyResultView.php', 'DiffusionExistsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionExistsQueryConduitAPIMethod.php', 'DiffusionExternalController' => 'applications/diffusion/controller/DiffusionExternalController.php', + 'DiffusionExternalSymbolQuery' => 'applications/diffusion/symbol/DiffusionExternalSymbolQuery.php', + 'DiffusionExternalSymbolsSource' => 'applications/diffusion/symbol/DiffusionExternalSymbolsSource.php', 'DiffusionFileContent' => 'applications/diffusion/data/DiffusionFileContent.php', 'DiffusionFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionFileContentQuery.php', 'DiffusionFileContentQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionFileContentQueryConduitAPIMethod.php', @@ -564,11 +563,13 @@ phutil_register_library_map(array( 'DiffusionPathQueryTestCase' => 'applications/diffusion/query/pathid/__tests__/DiffusionPathQueryTestCase.php', 'DiffusionPathTreeController' => 'applications/diffusion/controller/DiffusionPathTreeController.php', 'DiffusionPathValidateController' => 'applications/diffusion/controller/DiffusionPathValidateController.php', + 'DiffusionPhpExternalSymbolsSource' => 'applications/diffusion/symbol/DiffusionPhpExternalSymbolsSource.php', 'DiffusionPushCapability' => 'applications/diffusion/capability/DiffusionPushCapability.php', 'DiffusionPushEventViewController' => 'applications/diffusion/controller/DiffusionPushEventViewController.php', 'DiffusionPushLogController' => 'applications/diffusion/controller/DiffusionPushLogController.php', 'DiffusionPushLogListController' => 'applications/diffusion/controller/DiffusionPushLogListController.php', 'DiffusionPushLogListView' => 'applications/diffusion/view/DiffusionPushLogListView.php', + 'DiffusionPythonExternalSymbolsSource' => 'applications/diffusion/symbol/DiffusionPythonExternalSymbolsSource.php', 'DiffusionQuery' => 'applications/diffusion/query/DiffusionQuery.php', 'DiffusionQueryCommitsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionQueryCommitsConduitAPIMethod.php', 'DiffusionQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionQueryConduitAPIMethod.php', @@ -639,12 +640,14 @@ phutil_register_library_map(array( 'DivinerAtomQuery' => 'applications/diviner/query/DivinerAtomQuery.php', 'DivinerAtomRef' => 'applications/diviner/atom/DivinerAtomRef.php', 'DivinerAtomSearchEngine' => 'applications/diviner/query/DivinerAtomSearchEngine.php', + 'DivinerAtomSearchIndexer' => 'applications/diviner/search/DivinerAtomSearchIndexer.php', 'DivinerAtomizeWorkflow' => 'applications/diviner/workflow/DivinerAtomizeWorkflow.php', 'DivinerAtomizer' => 'applications/diviner/atomizer/DivinerAtomizer.php', 'DivinerBookController' => 'applications/diviner/controller/DivinerBookController.php', 'DivinerBookItemView' => 'applications/diviner/view/DivinerBookItemView.php', 'DivinerBookPHIDType' => 'applications/diviner/phid/DivinerBookPHIDType.php', 'DivinerBookQuery' => 'applications/diviner/query/DivinerBookQuery.php', + 'DivinerBookSearchIndexer' => 'applications/diviner/search/DivinerBookSearchIndexer.php', 'DivinerController' => 'applications/diviner/controller/DivinerController.php', 'DivinerDAO' => 'applications/diviner/storage/DivinerDAO.php', 'DivinerDefaultRenderer' => 'applications/diviner/renderer/DivinerDefaultRenderer.php', @@ -668,6 +671,7 @@ phutil_register_library_map(array( 'DivinerSymbolRemarkupRule' => 'applications/diviner/markup/DivinerSymbolRemarkupRule.php', 'DivinerWorkflow' => 'applications/diviner/workflow/DivinerWorkflow.php', 'DoorkeeperAsanaFeedWorker' => 'applications/doorkeeper/worker/DoorkeeperAsanaFeedWorker.php', + 'DoorkeeperAsanaRemarkupRule' => 'applications/doorkeeper/remarkup/DoorkeeperAsanaRemarkupRule.php', 'DoorkeeperBridge' => 'applications/doorkeeper/bridge/DoorkeeperBridge.php', 'DoorkeeperBridgeAsana' => 'applications/doorkeeper/bridge/DoorkeeperBridgeAsana.php', 'DoorkeeperBridgeJIRA' => 'applications/doorkeeper/bridge/DoorkeeperBridgeJIRA.php', @@ -679,11 +683,10 @@ phutil_register_library_map(array( 'DoorkeeperFeedWorker' => 'applications/doorkeeper/worker/DoorkeeperFeedWorker.php', 'DoorkeeperImportEngine' => 'applications/doorkeeper/engine/DoorkeeperImportEngine.php', 'DoorkeeperJIRAFeedWorker' => 'applications/doorkeeper/worker/DoorkeeperJIRAFeedWorker.php', + 'DoorkeeperJIRARemarkupRule' => 'applications/doorkeeper/remarkup/DoorkeeperJIRARemarkupRule.php', 'DoorkeeperMissingLinkException' => 'applications/doorkeeper/exception/DoorkeeperMissingLinkException.php', 'DoorkeeperObjectRef' => 'applications/doorkeeper/engine/DoorkeeperObjectRef.php', 'DoorkeeperRemarkupRule' => 'applications/doorkeeper/remarkup/DoorkeeperRemarkupRule.php', - 'DoorkeeperRemarkupRuleAsana' => 'applications/doorkeeper/remarkup/DoorkeeperRemarkupRuleAsana.php', - 'DoorkeeperRemarkupRuleJIRA' => 'applications/doorkeeper/remarkup/DoorkeeperRemarkupRuleJIRA.php', 'DoorkeeperSchemaSpec' => 'applications/doorkeeper/storage/DoorkeeperSchemaSpec.php', 'DoorkeeperTagView' => 'applications/doorkeeper/view/DoorkeeperTagView.php', 'DoorkeeperTagsController' => 'applications/doorkeeper/controller/DoorkeeperTagsController.php', @@ -951,7 +954,6 @@ phutil_register_library_map(array( 'JavelinUIExample' => 'applications/uiexample/examples/JavelinUIExample.php', 'JavelinViewExampleServerView' => 'applications/uiexample/examples/JavelinViewExampleServerView.php', 'JavelinViewUIExample' => 'applications/uiexample/examples/JavelinViewUIExample.php', - 'LegalpadConstants' => 'applications/legalpad/constants/LegalpadConstants.php', 'LegalpadController' => 'applications/legalpad/controller/LegalpadController.php', 'LegalpadCreateDocumentsCapability' => 'applications/legalpad/capability/LegalpadCreateDocumentsCapability.php', 'LegalpadDAO' => 'applications/legalpad/storage/LegalpadDAO.php', @@ -985,7 +987,6 @@ phutil_register_library_map(array( 'LegalpadTransaction' => 'applications/legalpad/storage/LegalpadTransaction.php', 'LegalpadTransactionComment' => 'applications/legalpad/storage/LegalpadTransactionComment.php', 'LegalpadTransactionQuery' => 'applications/legalpad/query/LegalpadTransactionQuery.php', - 'LegalpadTransactionType' => 'applications/legalpad/constants/LegalpadTransactionType.php', 'LegalpadTransactionView' => 'applications/legalpad/view/LegalpadTransactionView.php', 'LiskChunkTestCase' => 'infrastructure/storage/lisk/__tests__/LiskChunkTestCase.php', 'LiskDAO' => 'infrastructure/storage/lisk/LiskDAO.php', @@ -1083,7 +1084,6 @@ phutil_register_library_map(array( 'MetaMTAEmailTransactionCommand' => 'applications/metamta/command/MetaMTAEmailTransactionCommand.php', 'MetaMTAMailReceivedGarbageCollector' => 'applications/metamta/garbagecollector/MetaMTAMailReceivedGarbageCollector.php', 'MetaMTAMailSentGarbageCollector' => 'applications/metamta/garbagecollector/MetaMTAMailSentGarbageCollector.php', - 'MetaMTANotificationType' => 'applications/metamta/constants/MetaMTANotificationType.php', 'MetaMTAReceivedMailStatus' => 'applications/metamta/constants/MetaMTAReceivedMailStatus.php', 'MultimeterContext' => 'applications/multimeter/storage/MultimeterContext.php', 'MultimeterControl' => 'applications/multimeter/data/MultimeterControl.php', @@ -1115,8 +1115,10 @@ phutil_register_library_map(array( 'NuanceQueueEditController' => 'applications/nuance/controller/NuanceQueueEditController.php', 'NuanceQueueEditor' => 'applications/nuance/editor/NuanceQueueEditor.php', 'NuanceQueueItem' => 'applications/nuance/storage/NuanceQueueItem.php', + 'NuanceQueueListController' => 'applications/nuance/controller/NuanceQueueListController.php', 'NuanceQueuePHIDType' => 'applications/nuance/phid/NuanceQueuePHIDType.php', 'NuanceQueueQuery' => 'applications/nuance/query/NuanceQueueQuery.php', + 'NuanceQueueSearchEngine' => 'applications/nuance/query/NuanceQueueSearchEngine.php', 'NuanceQueueTransaction' => 'applications/nuance/storage/NuanceQueueTransaction.php', 'NuanceQueueTransactionComment' => 'applications/nuance/storage/NuanceQueueTransactionComment.php', 'NuanceQueueTransactionQuery' => 'applications/nuance/query/NuanceQueueTransactionQuery.php', @@ -1133,14 +1135,18 @@ phutil_register_library_map(array( 'NuanceRequestorViewController' => 'applications/nuance/controller/NuanceRequestorViewController.php', 'NuanceSchemaSpec' => 'applications/nuance/storage/NuanceSchemaSpec.php', 'NuanceSource' => 'applications/nuance/storage/NuanceSource.php', + 'NuanceSourceActionController' => 'applications/nuance/controller/NuanceSourceActionController.php', + 'NuanceSourceCreateController' => 'applications/nuance/controller/NuanceSourceCreateController.php', 'NuanceSourceDefaultEditCapability' => 'applications/nuance/capability/NuanceSourceDefaultEditCapability.php', 'NuanceSourceDefaultViewCapability' => 'applications/nuance/capability/NuanceSourceDefaultViewCapability.php', 'NuanceSourceDefinition' => 'applications/nuance/source/NuanceSourceDefinition.php', 'NuanceSourceEditController' => 'applications/nuance/controller/NuanceSourceEditController.php', 'NuanceSourceEditor' => 'applications/nuance/editor/NuanceSourceEditor.php', + 'NuanceSourceListController' => 'applications/nuance/controller/NuanceSourceListController.php', 'NuanceSourceManageCapability' => 'applications/nuance/capability/NuanceSourceManageCapability.php', 'NuanceSourcePHIDType' => 'applications/nuance/phid/NuanceSourcePHIDType.php', 'NuanceSourceQuery' => 'applications/nuance/query/NuanceSourceQuery.php', + 'NuanceSourceSearchEngine' => 'applications/nuance/query/NuanceSourceSearchEngine.php', 'NuanceSourceTransaction' => 'applications/nuance/storage/NuanceSourceTransaction.php', 'NuanceSourceTransactionComment' => 'applications/nuance/storage/NuanceSourceTransactionComment.php', 'NuanceSourceTransactionQuery' => 'applications/nuance/query/NuanceSourceTransactionQuery.php', @@ -1216,6 +1222,7 @@ phutil_register_library_map(array( 'PHUIPropertyListExample' => 'applications/uiexample/examples/PHUIPropertyListExample.php', 'PHUIPropertyListView' => 'view/phui/PHUIPropertyListView.php', 'PHUIRemarkupPreviewPanel' => 'view/phui/PHUIRemarkupPreviewPanel.php', + 'PHUISpacesNamespaceContextView' => 'applications/spaces/view/PHUISpacesNamespaceContextView.php', 'PHUIStatusItemView' => 'view/phui/PHUIStatusItemView.php', 'PHUIStatusListView' => 'view/phui/PHUIStatusListView.php', 'PHUITagExample' => 'applications/uiexample/examples/PHUITagExample.php', @@ -1330,6 +1337,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionInterface' => 'applications/transactions/interface/PhabricatorApplicationTransactionInterface.php', 'PhabricatorApplicationTransactionNoEffectException' => 'applications/transactions/exception/PhabricatorApplicationTransactionNoEffectException.php', 'PhabricatorApplicationTransactionNoEffectResponse' => 'applications/transactions/response/PhabricatorApplicationTransactionNoEffectResponse.php', + 'PhabricatorApplicationTransactionPublishWorker' => 'applications/transactions/worker/PhabricatorApplicationTransactionPublishWorker.php', 'PhabricatorApplicationTransactionQuery' => 'applications/transactions/query/PhabricatorApplicationTransactionQuery.php', 'PhabricatorApplicationTransactionReplyHandler' => 'applications/transactions/replyhandler/PhabricatorApplicationTransactionReplyHandler.php', 'PhabricatorApplicationTransactionResponse' => 'applications/transactions/response/PhabricatorApplicationTransactionResponse.php', @@ -1488,6 +1496,7 @@ phutil_register_library_map(array( 'PhabricatorCacheSpec' => 'applications/cache/spec/PhabricatorCacheSpec.php', 'PhabricatorCacheTTLGarbageCollector' => 'applications/cache/garbagecollector/PhabricatorCacheTTLGarbageCollector.php', 'PhabricatorCaches' => 'applications/cache/PhabricatorCaches.php', + 'PhabricatorCachesTestCase' => 'applications/cache/__tests__/PhabricatorCachesTestCase.php', 'PhabricatorCalendarApplication' => 'applications/calendar/application/PhabricatorCalendarApplication.php', 'PhabricatorCalendarController' => 'applications/calendar/controller/PhabricatorCalendarController.php', 'PhabricatorCalendarDAO' => 'applications/calendar/storage/PhabricatorCalendarDAO.php', @@ -1790,6 +1799,7 @@ phutil_register_library_map(array( 'PhabricatorEventListener' => 'infrastructure/events/PhabricatorEventListener.php', 'PhabricatorEventType' => 'infrastructure/events/constant/PhabricatorEventType.php', 'PhabricatorExampleEventListener' => 'infrastructure/events/PhabricatorExampleEventListener.php', + 'PhabricatorExtendedPolicyInterface' => 'applications/policy/interface/PhabricatorExtendedPolicyInterface.php', 'PhabricatorExtendingPhabricatorConfigOptions' => 'applications/config/option/PhabricatorExtendingPhabricatorConfigOptions.php', 'PhabricatorExtensionsSetupCheck' => 'applications/config/check/PhabricatorExtensionsSetupCheck.php', 'PhabricatorExternalAccount' => 'applications/people/storage/PhabricatorExternalAccount.php', @@ -1970,6 +1980,8 @@ phutil_register_library_map(array( 'PhabricatorListFilterUIExample' => 'applications/uiexample/examples/PhabricatorListFilterUIExample.php', 'PhabricatorLocalDiskFileStorageEngine' => 'applications/files/engine/PhabricatorLocalDiskFileStorageEngine.php', 'PhabricatorLocalTimeTestCase' => 'view/__tests__/PhabricatorLocalTimeTestCase.php', + 'PhabricatorLocaleScopeGuard' => 'infrastructure/internationalization/scope/PhabricatorLocaleScopeGuard.php', + 'PhabricatorLocaleScopeGuardTestCase' => 'infrastructure/internationalization/scope/__tests__/PhabricatorLocaleScopeGuardTestCase.php', 'PhabricatorLogTriggerAction' => 'infrastructure/daemon/workers/action/PhabricatorLogTriggerAction.php', 'PhabricatorLogoutController' => 'applications/auth/controller/PhabricatorLogoutController.php', 'PhabricatorLunarPhasePolicyRule' => 'applications/policy/rule/PhabricatorLunarPhasePolicyRule.php', @@ -1994,9 +2006,7 @@ phutil_register_library_map(array( 'PhabricatorMacroTransaction' => 'applications/macro/storage/PhabricatorMacroTransaction.php', 'PhabricatorMacroTransactionComment' => 'applications/macro/storage/PhabricatorMacroTransactionComment.php', 'PhabricatorMacroTransactionQuery' => 'applications/macro/query/PhabricatorMacroTransactionQuery.php', - 'PhabricatorMacroTransactionType' => 'applications/macro/constants/PhabricatorMacroTransactionType.php', 'PhabricatorMacroViewController' => 'applications/macro/controller/PhabricatorMacroViewController.php', - 'PhabricatorMail' => 'applications/metamta/PhabricatorMail.php', 'PhabricatorMailImplementationAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationAdapter.php', 'PhabricatorMailImplementationAmazonSESAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationAmazonSESAdapter.php', 'PhabricatorMailImplementationMailgunAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationMailgunAdapter.php', @@ -2016,16 +2026,8 @@ phutil_register_library_map(array( 'PhabricatorMailReceiverTestCase' => 'applications/metamta/receiver/__tests__/PhabricatorMailReceiverTestCase.php', 'PhabricatorMailReplyHandler' => 'applications/metamta/replyhandler/PhabricatorMailReplyHandler.php', 'PhabricatorMailSetupCheck' => 'applications/config/check/PhabricatorMailSetupCheck.php', + 'PhabricatorMailTarget' => 'applications/metamta/replyhandler/PhabricatorMailTarget.php', 'PhabricatorMailgunConfigOptions' => 'applications/config/option/PhabricatorMailgunConfigOptions.php', - 'PhabricatorMailingListDatasource' => 'applications/mailinglists/typeahead/PhabricatorMailingListDatasource.php', - 'PhabricatorMailingListListPHIDType' => 'applications/mailinglists/phid/PhabricatorMailingListListPHIDType.php', - 'PhabricatorMailingListQuery' => 'applications/mailinglists/query/PhabricatorMailingListQuery.php', - 'PhabricatorMailingListSearchEngine' => 'applications/mailinglists/query/PhabricatorMailingListSearchEngine.php', - 'PhabricatorMailingListsApplication' => 'applications/mailinglists/application/PhabricatorMailingListsApplication.php', - 'PhabricatorMailingListsController' => 'applications/mailinglists/controller/PhabricatorMailingListsController.php', - 'PhabricatorMailingListsEditController' => 'applications/mailinglists/controller/PhabricatorMailingListsEditController.php', - 'PhabricatorMailingListsListController' => 'applications/mailinglists/controller/PhabricatorMailingListsListController.php', - 'PhabricatorMailingListsManageCapability' => 'applications/mailinglists/capability/PhabricatorMailingListsManageCapability.php', 'PhabricatorMainMenuSearchView' => 'view/page/menu/PhabricatorMainMenuSearchView.php', 'PhabricatorMainMenuView' => 'view/page/menu/PhabricatorMainMenuView.php', 'PhabricatorManagementWorkflow' => 'infrastructure/management/PhabricatorManagementWorkflow.php', @@ -2134,7 +2136,6 @@ phutil_register_library_map(array( 'PhabricatorOAuthServerTestController' => 'applications/oauthserver/controller/PhabricatorOAuthServerTestController.php', 'PhabricatorOAuthServerTokenController' => 'applications/oauthserver/controller/PhabricatorOAuthServerTokenController.php', 'PhabricatorObjectHandle' => 'applications/phid/PhabricatorObjectHandle.php', - 'PhabricatorObjectHandleConstants' => 'applications/phid/handle/const/PhabricatorObjectHandleConstants.php', 'PhabricatorObjectHasAsanaSubtaskEdgeType' => 'applications/doorkeeper/edge/PhabricatorObjectHasAsanaSubtaskEdgeType.php', 'PhabricatorObjectHasAsanaTaskEdgeType' => 'applications/doorkeeper/edge/PhabricatorObjectHasAsanaTaskEdgeType.php', 'PhabricatorObjectHasContributorEdgeType' => 'applications/transactions/edges/PhabricatorObjectHasContributorEdgeType.php', @@ -2242,6 +2243,7 @@ phutil_register_library_map(array( 'PhabricatorPeopleRenameController' => 'applications/people/controller/PhabricatorPeopleRenameController.php', 'PhabricatorPeopleSearchEngine' => 'applications/people/query/PhabricatorPeopleSearchEngine.php', 'PhabricatorPeopleTestDataGenerator' => 'applications/people/lipsum/PhabricatorPeopleTestDataGenerator.php', + 'PhabricatorPeopleTransactionQuery' => 'applications/people/query/PhabricatorPeopleTransactionQuery.php', 'PhabricatorPeopleUserFunctionDatasource' => 'applications/people/typeahead/PhabricatorPeopleUserFunctionDatasource.php', 'PhabricatorPeopleUserPHIDType' => 'applications/people/phid/PhabricatorPeopleUserPHIDType.php', 'PhabricatorPeopleWelcomeController' => 'applications/people/controller/PhabricatorPeopleWelcomeController.php', @@ -2381,7 +2383,6 @@ phutil_register_library_map(array( 'PhabricatorRemarkupFigletBlockInterpreter' => 'infrastructure/markup/interpreter/PhabricatorRemarkupFigletBlockInterpreter.php', 'PhabricatorRemarkupGraphvizBlockInterpreter' => 'infrastructure/markup/interpreter/PhabricatorRemarkupGraphvizBlockInterpreter.php', 'PhabricatorRemarkupUIExample' => 'applications/uiexample/examples/PhabricatorRemarkupUIExample.php', - 'PhabricatorRepositoriesApplication' => 'applications/repository/application/PhabricatorRepositoriesApplication.php', 'PhabricatorRepositoriesSetupCheck' => 'applications/config/check/PhabricatorRepositoriesSetupCheck.php', 'PhabricatorRepository' => 'applications/repository/storage/PhabricatorRepository.php', 'PhabricatorRepositoryArcanistProject' => 'applications/repository/storage/PhabricatorRepositoryArcanistProject.php', @@ -2400,7 +2401,6 @@ phutil_register_library_map(array( 'PhabricatorRepositoryCommitRef' => 'applications/repository/engine/PhabricatorRepositoryCommitRef.php', 'PhabricatorRepositoryCommitSearchIndexer' => 'applications/repository/search/PhabricatorRepositoryCommitSearchIndexer.php', 'PhabricatorRepositoryConfigOptions' => 'applications/repository/config/PhabricatorRepositoryConfigOptions.php', - 'PhabricatorRepositoryController' => 'applications/repository/controller/PhabricatorRepositoryController.php', 'PhabricatorRepositoryDAO' => 'applications/repository/storage/PhabricatorRepositoryDAO.php', 'PhabricatorRepositoryDiscoveryEngine' => 'applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php', 'PhabricatorRepositoryEditor' => 'applications/repository/editor/PhabricatorRepositoryEditor.php', @@ -2409,7 +2409,6 @@ phutil_register_library_map(array( 'PhabricatorRepositoryGitCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositoryGitCommitMessageParserWorker.php', 'PhabricatorRepositoryGraphCache' => 'applications/repository/graphcache/PhabricatorRepositoryGraphCache.php', 'PhabricatorRepositoryGraphStream' => 'applications/repository/daemon/PhabricatorRepositoryGraphStream.php', - 'PhabricatorRepositoryListController' => 'applications/repository/controller/PhabricatorRepositoryListController.php', 'PhabricatorRepositoryManagementCacheWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementCacheWorkflow.php', 'PhabricatorRepositoryManagementDiscoverWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementDiscoverWorkflow.php', 'PhabricatorRepositoryManagementEditWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php', @@ -2494,30 +2493,46 @@ phutil_register_library_map(array( 'PhabricatorSearchApplicationStorageEnginePanel' => 'applications/search/applicationpanel/PhabricatorSearchApplicationStorageEnginePanel.php', 'PhabricatorSearchAttachController' => 'applications/search/controller/PhabricatorSearchAttachController.php', 'PhabricatorSearchBaseController' => 'applications/search/controller/PhabricatorSearchBaseController.php', + 'PhabricatorSearchCheckboxesField' => 'applications/search/field/PhabricatorSearchCheckboxesField.php', 'PhabricatorSearchConfigOptions' => 'applications/search/config/PhabricatorSearchConfigOptions.php', 'PhabricatorSearchController' => 'applications/search/controller/PhabricatorSearchController.php', + 'PhabricatorSearchCustomFieldProxyField' => 'applications/search/field/PhabricatorSearchCustomFieldProxyField.php', 'PhabricatorSearchDAO' => 'applications/search/storage/PhabricatorSearchDAO.php', 'PhabricatorSearchDatasource' => 'applications/search/typeahead/PhabricatorSearchDatasource.php', + 'PhabricatorSearchDatasourceField' => 'applications/search/field/PhabricatorSearchDatasourceField.php', + 'PhabricatorSearchDateField' => 'applications/search/field/PhabricatorSearchDateField.php', 'PhabricatorSearchDeleteController' => 'applications/search/controller/PhabricatorSearchDeleteController.php', 'PhabricatorSearchDocument' => 'applications/search/storage/document/PhabricatorSearchDocument.php', 'PhabricatorSearchDocumentField' => 'applications/search/storage/document/PhabricatorSearchDocumentField.php', + 'PhabricatorSearchDocumentFieldType' => 'applications/search/constants/PhabricatorSearchDocumentFieldType.php', 'PhabricatorSearchDocumentIndexer' => 'applications/search/index/PhabricatorSearchDocumentIndexer.php', 'PhabricatorSearchDocumentQuery' => 'applications/search/query/PhabricatorSearchDocumentQuery.php', 'PhabricatorSearchDocumentRelationship' => 'applications/search/storage/document/PhabricatorSearchDocumentRelationship.php', 'PhabricatorSearchDocumentTypeDatasource' => 'applications/search/typeahead/PhabricatorSearchDocumentTypeDatasource.php', 'PhabricatorSearchEditController' => 'applications/search/controller/PhabricatorSearchEditController.php', 'PhabricatorSearchEngine' => 'applications/search/engine/PhabricatorSearchEngine.php', - 'PhabricatorSearchField' => 'applications/search/constants/PhabricatorSearchField.php', + 'PhabricatorSearchField' => 'applications/search/field/PhabricatorSearchField.php', 'PhabricatorSearchHovercardController' => 'applications/search/controller/PhabricatorSearchHovercardController.php', 'PhabricatorSearchIndexer' => 'applications/search/index/PhabricatorSearchIndexer.php', 'PhabricatorSearchManagementIndexWorkflow' => 'applications/search/management/PhabricatorSearchManagementIndexWorkflow.php', 'PhabricatorSearchManagementInitWorkflow' => 'applications/search/management/PhabricatorSearchManagementInitWorkflow.php', 'PhabricatorSearchManagementWorkflow' => 'applications/search/management/PhabricatorSearchManagementWorkflow.php', 'PhabricatorSearchOrderController' => 'applications/search/controller/PhabricatorSearchOrderController.php', + 'PhabricatorSearchOrderField' => 'applications/search/field/PhabricatorSearchOrderField.php', + 'PhabricatorSearchOwnersField' => 'applications/search/field/PhabricatorSearchOwnersField.php', 'PhabricatorSearchPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorSearchPreferencesSettingsPanel.php', + 'PhabricatorSearchProjectsField' => 'applications/search/field/PhabricatorSearchProjectsField.php', 'PhabricatorSearchRelationship' => 'applications/search/constants/PhabricatorSearchRelationship.php', 'PhabricatorSearchResultView' => 'applications/search/view/PhabricatorSearchResultView.php', 'PhabricatorSearchSelectController' => 'applications/search/controller/PhabricatorSearchSelectController.php', + 'PhabricatorSearchSelectField' => 'applications/search/field/PhabricatorSearchSelectField.php', + 'PhabricatorSearchSpacesField' => 'applications/search/field/PhabricatorSearchSpacesField.php', + 'PhabricatorSearchStringListField' => 'applications/search/field/PhabricatorSearchStringListField.php', + 'PhabricatorSearchSubscribersField' => 'applications/search/field/PhabricatorSearchSubscribersField.php', + 'PhabricatorSearchTextField' => 'applications/search/field/PhabricatorSearchTextField.php', + 'PhabricatorSearchThreeStateField' => 'applications/search/field/PhabricatorSearchThreeStateField.php', + 'PhabricatorSearchTokenizerField' => 'applications/search/field/PhabricatorSearchTokenizerField.php', + 'PhabricatorSearchUsersField' => 'applications/search/field/PhabricatorSearchUsersField.php', 'PhabricatorSearchWorker' => 'applications/search/worker/PhabricatorSearchWorker.php', 'PhabricatorSecurityConfigOptions' => 'applications/config/option/PhabricatorSecurityConfigOptions.php', 'PhabricatorSecuritySetupCheck' => 'applications/config/check/PhabricatorSecuritySetupCheck.php', @@ -2557,6 +2572,27 @@ phutil_register_library_map(array( 'PhabricatorSlugTestCase' => 'infrastructure/util/__tests__/PhabricatorSlugTestCase.php', 'PhabricatorSortTableUIExample' => 'applications/uiexample/examples/PhabricatorSortTableUIExample.php', 'PhabricatorSourceCodeView' => 'view/layout/PhabricatorSourceCodeView.php', + 'PhabricatorSpacesApplication' => 'applications/spaces/application/PhabricatorSpacesApplication.php', + 'PhabricatorSpacesCapabilityCreateSpaces' => 'applications/spaces/capability/PhabricatorSpacesCapabilityCreateSpaces.php', + 'PhabricatorSpacesCapabilityDefaultEdit' => 'applications/spaces/capability/PhabricatorSpacesCapabilityDefaultEdit.php', + 'PhabricatorSpacesCapabilityDefaultView' => 'applications/spaces/capability/PhabricatorSpacesCapabilityDefaultView.php', + 'PhabricatorSpacesControl' => 'applications/spaces/view/PhabricatorSpacesControl.php', + 'PhabricatorSpacesController' => 'applications/spaces/controller/PhabricatorSpacesController.php', + 'PhabricatorSpacesDAO' => 'applications/spaces/storage/PhabricatorSpacesDAO.php', + 'PhabricatorSpacesEditController' => 'applications/spaces/controller/PhabricatorSpacesEditController.php', + 'PhabricatorSpacesInterface' => 'applications/spaces/interface/PhabricatorSpacesInterface.php', + 'PhabricatorSpacesListController' => 'applications/spaces/controller/PhabricatorSpacesListController.php', + 'PhabricatorSpacesNamespace' => 'applications/spaces/storage/PhabricatorSpacesNamespace.php', + 'PhabricatorSpacesNamespaceDatasource' => 'applications/spaces/typeahead/PhabricatorSpacesNamespaceDatasource.php', + 'PhabricatorSpacesNamespaceEditor' => 'applications/spaces/editor/PhabricatorSpacesNamespaceEditor.php', + 'PhabricatorSpacesNamespacePHIDType' => 'applications/spaces/phid/PhabricatorSpacesNamespacePHIDType.php', + 'PhabricatorSpacesNamespaceQuery' => 'applications/spaces/query/PhabricatorSpacesNamespaceQuery.php', + 'PhabricatorSpacesNamespaceSearchEngine' => 'applications/spaces/query/PhabricatorSpacesNamespaceSearchEngine.php', + 'PhabricatorSpacesNamespaceTransaction' => 'applications/spaces/storage/PhabricatorSpacesNamespaceTransaction.php', + 'PhabricatorSpacesNamespaceTransactionQuery' => 'applications/spaces/query/PhabricatorSpacesNamespaceTransactionQuery.php', + 'PhabricatorSpacesRemarkupRule' => 'applications/spaces/remarkup/PhabricatorSpacesRemarkupRule.php', + 'PhabricatorSpacesTestCase' => 'applications/spaces/__tests__/PhabricatorSpacesTestCase.php', + 'PhabricatorSpacesViewController' => 'applications/spaces/controller/PhabricatorSpacesViewController.php', 'PhabricatorStandardCustomField' => 'infrastructure/customfield/standard/PhabricatorStandardCustomField.php', 'PhabricatorStandardCustomFieldBool' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldBool.php', 'PhabricatorStandardCustomFieldCredential' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldCredential.php', @@ -2814,7 +2850,6 @@ phutil_register_library_map(array( 'PhluxVariableQuery' => 'applications/phlux/query/PhluxVariableQuery.php', 'PhluxViewController' => 'applications/phlux/controller/PhluxViewController.php', 'PholioActionMenuEventListener' => 'applications/pholio/event/PholioActionMenuEventListener.php', - 'PholioConstants' => 'applications/pholio/constants/PholioConstants.php', 'PholioController' => 'applications/pholio/controller/PholioController.php', 'PholioDAO' => 'applications/pholio/storage/PholioDAO.php', 'PholioDefaultEditCapability' => 'applications/pholio/capability/PholioDefaultEditCapability.php', @@ -2846,7 +2881,6 @@ phutil_register_library_map(array( 'PholioTransaction' => 'applications/pholio/storage/PholioTransaction.php', 'PholioTransactionComment' => 'applications/pholio/storage/PholioTransactionComment.php', 'PholioTransactionQuery' => 'applications/pholio/query/PholioTransactionQuery.php', - 'PholioTransactionType' => 'applications/pholio/constants/PholioTransactionType.php', 'PholioTransactionView' => 'applications/pholio/view/PholioTransactionView.php', 'PholioUploadedImageView' => 'applications/pholio/view/PholioUploadedImageView.php', 'PhortuneAccount' => 'applications/phortune/storage/PhortuneAccount.php', @@ -3002,7 +3036,6 @@ phutil_register_library_map(array( 'PhrequentUIEventListener' => 'applications/phrequent/event/PhrequentUIEventListener.php', 'PhrequentUserTime' => 'applications/phrequent/storage/PhrequentUserTime.php', 'PhrequentUserTimeQuery' => 'applications/phrequent/query/PhrequentUserTimeQuery.php', - 'PhrictionActionConstants' => 'applications/phriction/constants/PhrictionActionConstants.php', 'PhrictionChangeType' => 'applications/phriction/constants/PhrictionChangeType.php', 'PhrictionConduitAPIMethod' => 'applications/phriction/conduit/PhrictionConduitAPIMethod.php', 'PhrictionConstants' => 'applications/phriction/constants/PhrictionConstants.php', @@ -3422,7 +3455,6 @@ phutil_register_library_map(array( 'AuditConduitAPIMethod' => 'ConduitAPIMethod', 'AuditQueryConduitAPIMethod' => 'AuditConduitAPIMethod', 'AuthManageProvidersCapability' => 'PhabricatorPolicyCapability', - 'CalendarColors' => 'CalendarConstants', 'CalendarTimeUtilTestCase' => 'PhabricatorTestCase', 'CelerityManagementMapWorkflow' => 'CelerityManagementWorkflow', 'CelerityManagementWorkflow' => 'PhabricatorManagementWorkflow', @@ -3507,7 +3539,6 @@ phutil_register_library_map(array( 'ConpherenceTransaction' => 'PhabricatorApplicationTransaction', 'ConpherenceTransactionComment' => 'PhabricatorApplicationTransactionComment', 'ConpherenceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', - 'ConpherenceTransactionType' => 'ConpherenceConstants', 'ConpherenceTransactionView' => 'AphrontView', 'ConpherenceUpdateActions' => 'ConpherenceConstants', 'ConpherenceUpdateController' => 'ConpherenceController', @@ -3660,6 +3691,7 @@ phutil_register_library_map(array( 'DifferentialDAO', 'PhabricatorTokenReceiverInterface', 'PhabricatorPolicyInterface', + 'PhabricatorExtendedPolicyInterface', 'PhabricatorFlaggableInterface', 'PhrequentTrackableInterface', 'HarbormasterBuildableInterface', @@ -3799,11 +3831,13 @@ phutil_register_library_map(array( 'DiffusionPathQueryTestCase' => 'PhabricatorTestCase', 'DiffusionPathTreeController' => 'DiffusionController', 'DiffusionPathValidateController' => 'DiffusionController', + 'DiffusionPhpExternalSymbolsSource' => 'DiffusionExternalSymbolsSource', 'DiffusionPushCapability' => 'PhabricatorPolicyCapability', 'DiffusionPushEventViewController' => 'DiffusionPushLogController', 'DiffusionPushLogController' => 'DiffusionController', 'DiffusionPushLogListController' => 'DiffusionPushLogController', 'DiffusionPushLogListView' => 'AphrontView', + 'DiffusionPythonExternalSymbolsSource' => 'DiffusionExternalSymbolsSource', 'DiffusionQuery' => 'PhabricatorQuery', 'DiffusionQueryCommitsConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionQueryConduitAPIMethod' => 'DiffusionConduitAPIMethod', @@ -3868,11 +3902,13 @@ phutil_register_library_map(array( 'DivinerAtomPHIDType' => 'PhabricatorPHIDType', 'DivinerAtomQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'DivinerAtomSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'DivinerAtomSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'DivinerAtomizeWorkflow' => 'DivinerWorkflow', 'DivinerBookController' => 'DivinerController', 'DivinerBookItemView' => 'AphrontTagView', 'DivinerBookPHIDType' => 'PhabricatorPHIDType', 'DivinerBookQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'DivinerBookSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'DivinerController' => 'PhabricatorController', 'DivinerDAO' => 'PhabricatorLiskDAO', 'DivinerDefaultRenderer' => 'DivinerRenderer', @@ -3902,6 +3938,7 @@ phutil_register_library_map(array( 'DivinerSymbolRemarkupRule' => 'PhutilRemarkupRule', 'DivinerWorkflow' => 'PhabricatorManagementWorkflow', 'DoorkeeperAsanaFeedWorker' => 'DoorkeeperFeedWorker', + 'DoorkeeperAsanaRemarkupRule' => 'DoorkeeperRemarkupRule', 'DoorkeeperBridge' => 'Phobject', 'DoorkeeperBridgeAsana' => 'DoorkeeperBridge', 'DoorkeeperBridgeJIRA' => 'DoorkeeperBridge', @@ -3915,11 +3952,10 @@ phutil_register_library_map(array( 'DoorkeeperFeedWorker' => 'FeedPushWorker', 'DoorkeeperImportEngine' => 'Phobject', 'DoorkeeperJIRAFeedWorker' => 'DoorkeeperFeedWorker', + 'DoorkeeperJIRARemarkupRule' => 'DoorkeeperRemarkupRule', 'DoorkeeperMissingLinkException' => 'Exception', 'DoorkeeperObjectRef' => 'Phobject', 'DoorkeeperRemarkupRule' => 'PhutilRemarkupRule', - 'DoorkeeperRemarkupRuleAsana' => 'DoorkeeperRemarkupRule', - 'DoorkeeperRemarkupRuleJIRA' => 'DoorkeeperRemarkupRule', 'DoorkeeperSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'DoorkeeperTagView' => 'AphrontView', 'DoorkeeperTagsController' => 'PhabricatorController', @@ -4291,7 +4327,6 @@ phutil_register_library_map(array( 'LegalpadTransaction' => 'PhabricatorApplicationTransaction', 'LegalpadTransactionComment' => 'PhabricatorApplicationTransactionComment', 'LegalpadTransactionQuery' => 'PhabricatorApplicationTransactionQuery', - 'LegalpadTransactionType' => 'LegalpadConstants', 'LegalpadTransactionView' => 'PhabricatorApplicationTransactionView', 'LiskChunkTestCase' => 'PhabricatorTestCase', 'LiskDAOTestCase' => 'PhabricatorTestCase', @@ -4400,7 +4435,6 @@ phutil_register_library_map(array( 'MetaMTAEmailTransactionCommand' => 'Phobject', 'MetaMTAMailReceivedGarbageCollector' => 'PhabricatorGarbageCollector', 'MetaMTAMailSentGarbageCollector' => 'PhabricatorGarbageCollector', - 'MetaMTANotificationType' => 'MetaMTAConstants', 'MetaMTAReceivedMailStatus' => 'MetaMTAConstants', 'MultimeterContext' => 'MultimeterDimension', 'MultimeterController' => 'PhabricatorController', @@ -4433,17 +4467,24 @@ phutil_register_library_map(array( 'NuanceQueue' => array( 'NuanceDAO', 'PhabricatorPolicyInterface', + 'PhabricatorApplicationTransactionInterface', ), 'NuanceQueueEditController' => 'NuanceController', 'NuanceQueueEditor' => 'PhabricatorApplicationTransactionEditor', 'NuanceQueueItem' => 'NuanceDAO', + 'NuanceQueueListController' => 'NuanceController', 'NuanceQueuePHIDType' => 'PhabricatorPHIDType', 'NuanceQueueQuery' => 'NuanceQuery', + 'NuanceQueueSearchEngine' => 'PhabricatorApplicationSearchEngine', 'NuanceQueueTransaction' => 'NuanceTransaction', 'NuanceQueueTransactionComment' => 'PhabricatorApplicationTransactionComment', 'NuanceQueueTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'NuanceQueueViewController' => 'NuanceController', - 'NuanceRequestor' => 'NuanceDAO', + 'NuanceRequestor' => array( + 'NuanceDAO', + 'PhabricatorPolicyInterface', + 'PhabricatorApplicationTransactionInterface', + ), 'NuanceRequestorEditController' => 'NuanceController', 'NuanceRequestorEditor' => 'PhabricatorApplicationTransactionEditor', 'NuanceRequestorPHIDType' => 'PhabricatorPHIDType', @@ -4459,14 +4500,18 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', ), + 'NuanceSourceActionController' => 'NuanceController', + 'NuanceSourceCreateController' => 'NuanceController', 'NuanceSourceDefaultEditCapability' => 'PhabricatorPolicyCapability', 'NuanceSourceDefaultViewCapability' => 'PhabricatorPolicyCapability', 'NuanceSourceDefinition' => 'Phobject', 'NuanceSourceEditController' => 'NuanceController', 'NuanceSourceEditor' => 'PhabricatorApplicationTransactionEditor', + 'NuanceSourceListController' => 'NuanceController', 'NuanceSourceManageCapability' => 'PhabricatorPolicyCapability', 'NuanceSourcePHIDType' => 'PhabricatorPHIDType', 'NuanceSourceQuery' => 'NuanceQuery', + 'NuanceSourceSearchEngine' => 'PhabricatorApplicationSearchEngine', 'NuanceSourceTransaction' => 'NuanceTransaction', 'NuanceSourceTransactionComment' => 'PhabricatorApplicationTransactionComment', 'NuanceSourceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', @@ -4541,6 +4586,7 @@ phutil_register_library_map(array( 'PHUIPropertyListExample' => 'PhabricatorUIExample', 'PHUIPropertyListView' => 'AphrontView', 'PHUIRemarkupPreviewPanel' => 'AphrontTagView', + 'PHUISpacesNamespaceContextView' => 'AphrontView', 'PHUIStatusItemView' => 'AphrontTagView', 'PHUIStatusListView' => 'AphrontTagView', 'PHUITagExample' => 'PhabricatorUIExample', @@ -4639,6 +4685,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationPanelController' => 'PhabricatorApplicationsController', 'PhabricatorApplicationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorApplicationSearchController' => 'PhabricatorSearchBaseController', + 'PhabricatorApplicationSearchEngine' => 'Phobject', 'PhabricatorApplicationStatusView' => 'AphrontView', 'PhabricatorApplicationTransaction' => array( 'PhabricatorLiskDAO', @@ -4665,6 +4712,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionFeedStory' => 'PhabricatorFeedStory', 'PhabricatorApplicationTransactionNoEffectException' => 'Exception', 'PhabricatorApplicationTransactionNoEffectResponse' => 'AphrontProxyResponse', + 'PhabricatorApplicationTransactionPublishWorker' => 'PhabricatorWorker', 'PhabricatorApplicationTransactionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorApplicationTransactionReplyHandler' => 'PhabricatorMailReplyHandler', 'PhabricatorApplicationTransactionResponse' => 'AphrontProxyResponse', @@ -4830,6 +4878,7 @@ phutil_register_library_map(array( 'PhabricatorCacheSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorCacheSpec' => 'Phobject', 'PhabricatorCacheTTLGarbageCollector' => 'PhabricatorGarbageCollector', + 'PhabricatorCachesTestCase' => 'PhabricatorTestCase', 'PhabricatorCalendarApplication' => 'PhabricatorApplication', 'PhabricatorCalendarController' => 'PhabricatorController', 'PhabricatorCalendarDAO' => 'PhabricatorLiskDAO', @@ -5373,6 +5422,8 @@ phutil_register_library_map(array( 'PhabricatorListFilterUIExample' => 'PhabricatorUIExample', 'PhabricatorLocalDiskFileStorageEngine' => 'PhabricatorFileStorageEngine', 'PhabricatorLocalTimeTestCase' => 'PhabricatorTestCase', + 'PhabricatorLocaleScopeGuard' => 'Phobject', + 'PhabricatorLocaleScopeGuardTestCase' => 'PhabricatorTestCase', 'PhabricatorLogTriggerAction' => 'PhabricatorTriggerAction', 'PhabricatorLogoutController' => 'PhabricatorAuthController', 'PhabricatorLunarPhasePolicyRule' => 'PhabricatorPolicyRule', @@ -5414,16 +5465,8 @@ phutil_register_library_map(array( 'PhabricatorMailManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorMailReceiverTestCase' => 'PhabricatorTestCase', 'PhabricatorMailSetupCheck' => 'PhabricatorSetupCheck', + 'PhabricatorMailTarget' => 'Phobject', 'PhabricatorMailgunConfigOptions' => 'PhabricatorApplicationConfigOptions', - 'PhabricatorMailingListDatasource' => 'PhabricatorTypeaheadDatasource', - 'PhabricatorMailingListListPHIDType' => 'PhabricatorPHIDType', - 'PhabricatorMailingListQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', - 'PhabricatorMailingListSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'PhabricatorMailingListsApplication' => 'PhabricatorApplication', - 'PhabricatorMailingListsController' => 'PhabricatorController', - 'PhabricatorMailingListsEditController' => 'PhabricatorMailingListsController', - 'PhabricatorMailingListsListController' => 'PhabricatorMailingListsController', - 'PhabricatorMailingListsManageCapability' => 'PhabricatorPolicyCapability', 'PhabricatorMainMenuSearchView' => 'AphrontView', 'PhabricatorMainMenuView' => 'AphrontView', 'PhabricatorManagementWorkflow' => 'PhutilArgumentWorkflow', @@ -5457,11 +5500,7 @@ phutil_register_library_map(array( 'PhabricatorMetaMTAMailableDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorMetaMTAMailableFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorMetaMTAMailgunReceiveController' => 'PhabricatorMetaMTAController', - 'PhabricatorMetaMTAMailingList' => array( - 'PhabricatorMetaMTADAO', - 'PhabricatorPolicyInterface', - 'PhabricatorDestructibleInterface', - ), + 'PhabricatorMetaMTAMailingList' => 'PhabricatorMetaMTADAO', 'PhabricatorMetaMTAMemberQuery' => 'PhabricatorQuery', 'PhabricatorMetaMTAPermanentFailureException' => 'Exception', 'PhabricatorMetaMTAReceivedMail' => 'PhabricatorMetaMTADAO', @@ -5601,6 +5640,7 @@ phutil_register_library_map(array( 'PhabricatorProjectInterface', 'PhabricatorDestructibleInterface', 'PhabricatorApplicationTransactionInterface', + 'PhabricatorSpacesInterface', ), 'PhabricatorPasteApplication' => 'PhabricatorApplication', 'PhabricatorPasteCommentController' => 'PhabricatorPasteController', @@ -5651,6 +5691,7 @@ phutil_register_library_map(array( 'PhabricatorPeopleRenameController' => 'PhabricatorPeopleController', 'PhabricatorPeopleSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorPeopleTestDataGenerator' => 'PhabricatorTestDataGenerator', + 'PhabricatorPeopleTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorPeopleUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorPeopleUserPHIDType' => 'PhabricatorPHIDType', 'PhabricatorPeopleWelcomeController' => 'PhabricatorPeopleController', @@ -5699,7 +5740,10 @@ phutil_register_library_map(array( 'PhabricatorPolicyPHIDTypePolicy' => 'PhabricatorPHIDType', 'PhabricatorPolicyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorPolicyTestCase' => 'PhabricatorTestCase', - 'PhabricatorPolicyTestObject' => 'PhabricatorPolicyInterface', + 'PhabricatorPolicyTestObject' => array( + 'PhabricatorPolicyInterface', + 'PhabricatorExtendedPolicyInterface', + ), 'PhabricatorPolicyType' => 'PhabricatorPolicyConstants', 'PhabricatorPonderApplication' => 'PhabricatorApplication', 'PhabricatorProject' => array( @@ -5812,7 +5856,6 @@ phutil_register_library_map(array( 'PhabricatorRemarkupFigletBlockInterpreter' => 'PhutilRemarkupBlockInterpreter', 'PhabricatorRemarkupGraphvizBlockInterpreter' => 'PhutilRemarkupBlockInterpreter', 'PhabricatorRemarkupUIExample' => 'PhabricatorUIExample', - 'PhabricatorRepositoriesApplication' => 'PhabricatorApplication', 'PhabricatorRepositoriesSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorRepository' => array( 'PhabricatorRepositoryDAO', @@ -5856,14 +5899,12 @@ phutil_register_library_map(array( 'PhabricatorRepositoryCommitParserWorker' => 'PhabricatorWorker', 'PhabricatorRepositoryCommitSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhabricatorRepositoryConfigOptions' => 'PhabricatorApplicationConfigOptions', - 'PhabricatorRepositoryController' => 'PhabricatorController', 'PhabricatorRepositoryDAO' => 'PhabricatorLiskDAO', 'PhabricatorRepositoryDiscoveryEngine' => 'PhabricatorRepositoryEngine', 'PhabricatorRepositoryEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorRepositoryGitCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker', 'PhabricatorRepositoryGitCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker', 'PhabricatorRepositoryGraphStream' => 'Phobject', - 'PhabricatorRepositoryListController' => 'PhabricatorRepositoryController', 'PhabricatorRepositoryManagementCacheWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementDiscoverWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementEditWorkflow' => 'PhabricatorRepositoryManagementWorkflow', @@ -5957,26 +5998,43 @@ phutil_register_library_map(array( 'PhabricatorSearchApplicationStorageEnginePanel' => 'PhabricatorApplicationConfigurationPanel', 'PhabricatorSearchAttachController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchBaseController' => 'PhabricatorController', + 'PhabricatorSearchCheckboxesField' => 'PhabricatorSearchField', 'PhabricatorSearchConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorSearchController' => 'PhabricatorSearchBaseController', + 'PhabricatorSearchCustomFieldProxyField' => 'PhabricatorSearchField', 'PhabricatorSearchDAO' => 'PhabricatorLiskDAO', 'PhabricatorSearchDatasource' => 'PhabricatorTypeaheadCompositeDatasource', + 'PhabricatorSearchDatasourceField' => 'PhabricatorSearchTokenizerField', + 'PhabricatorSearchDateField' => 'PhabricatorSearchField', 'PhabricatorSearchDeleteController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchDocument' => 'PhabricatorSearchDAO', 'PhabricatorSearchDocumentField' => 'PhabricatorSearchDAO', + 'PhabricatorSearchDocumentFieldType' => 'Phobject', 'PhabricatorSearchDocumentIndexer' => 'Phobject', 'PhabricatorSearchDocumentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorSearchDocumentRelationship' => 'PhabricatorSearchDAO', 'PhabricatorSearchDocumentTypeDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorSearchEditController' => 'PhabricatorSearchBaseController', + 'PhabricatorSearchField' => 'Phobject', 'PhabricatorSearchHovercardController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchManagementIndexWorkflow' => 'PhabricatorSearchManagementWorkflow', 'PhabricatorSearchManagementInitWorkflow' => 'PhabricatorSearchManagementWorkflow', 'PhabricatorSearchManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorSearchOrderController' => 'PhabricatorSearchBaseController', + 'PhabricatorSearchOrderField' => 'PhabricatorSearchField', + 'PhabricatorSearchOwnersField' => 'PhabricatorSearchTokenizerField', 'PhabricatorSearchPreferencesSettingsPanel' => 'PhabricatorSettingsPanel', + 'PhabricatorSearchProjectsField' => 'PhabricatorSearchTokenizerField', 'PhabricatorSearchResultView' => 'AphrontView', 'PhabricatorSearchSelectController' => 'PhabricatorSearchBaseController', + 'PhabricatorSearchSelectField' => 'PhabricatorSearchField', + 'PhabricatorSearchSpacesField' => 'PhabricatorSearchTokenizerField', + 'PhabricatorSearchStringListField' => 'PhabricatorSearchField', + 'PhabricatorSearchSubscribersField' => 'PhabricatorSearchTokenizerField', + 'PhabricatorSearchTextField' => 'PhabricatorSearchField', + 'PhabricatorSearchThreeStateField' => 'PhabricatorSearchField', + 'PhabricatorSearchTokenizerField' => 'PhabricatorSearchField', + 'PhabricatorSearchUsersField' => 'PhabricatorSearchTokenizerField', 'PhabricatorSearchWorker' => 'PhabricatorWorker', 'PhabricatorSecurityConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorSecuritySetupCheck' => 'PhabricatorSetupCheck', @@ -6021,6 +6079,32 @@ phutil_register_library_map(array( 'PhabricatorSlugTestCase' => 'PhabricatorTestCase', 'PhabricatorSortTableUIExample' => 'PhabricatorUIExample', 'PhabricatorSourceCodeView' => 'AphrontView', + 'PhabricatorSpacesApplication' => 'PhabricatorApplication', + 'PhabricatorSpacesCapabilityCreateSpaces' => 'PhabricatorPolicyCapability', + 'PhabricatorSpacesCapabilityDefaultEdit' => 'PhabricatorPolicyCapability', + 'PhabricatorSpacesCapabilityDefaultView' => 'PhabricatorPolicyCapability', + 'PhabricatorSpacesControl' => 'AphrontFormControl', + 'PhabricatorSpacesController' => 'PhabricatorController', + 'PhabricatorSpacesDAO' => 'PhabricatorLiskDAO', + 'PhabricatorSpacesEditController' => 'PhabricatorSpacesController', + 'PhabricatorSpacesInterface' => 'PhabricatorPHIDInterface', + 'PhabricatorSpacesListController' => 'PhabricatorSpacesController', + 'PhabricatorSpacesNamespace' => array( + 'PhabricatorSpacesDAO', + 'PhabricatorPolicyInterface', + 'PhabricatorApplicationTransactionInterface', + 'PhabricatorDestructibleInterface', + ), + 'PhabricatorSpacesNamespaceDatasource' => 'PhabricatorTypeaheadDatasource', + 'PhabricatorSpacesNamespaceEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhabricatorSpacesNamespacePHIDType' => 'PhabricatorPHIDType', + 'PhabricatorSpacesNamespaceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorSpacesNamespaceSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PhabricatorSpacesNamespaceTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorSpacesNamespaceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorSpacesRemarkupRule' => 'PhabricatorObjectRemarkupRule', + 'PhabricatorSpacesTestCase' => 'PhabricatorTestCase', + 'PhabricatorSpacesViewController' => 'PhabricatorSpacesController', 'PhabricatorStandardCustomField' => 'PhabricatorCustomField', 'PhabricatorStandardCustomFieldBool' => 'PhabricatorStandardCustomField', 'PhabricatorStandardCustomFieldCredential' => 'PhabricatorStandardCustomField', @@ -6146,6 +6230,7 @@ phutil_register_library_map(array( 'PhabricatorCustomFieldInterface', 'PhabricatorDestructibleInterface', 'PhabricatorSSHPublicKeyInterface', + 'PhabricatorApplicationTransactionInterface', ), 'PhabricatorUserBlurbField' => 'PhabricatorUserCustomField', 'PhabricatorUserConfigOptions' => 'PhabricatorApplicationConfigOptions', @@ -6241,6 +6326,7 @@ phutil_register_library_map(array( 'PhameDAO', 'PhabricatorPolicyInterface', 'PhabricatorMarkupInterface', + 'PhabricatorApplicationTransactionInterface', ), 'PhameBlogDeleteController' => 'PhameController', 'PhameBlogEditController' => 'PhameController', @@ -6345,7 +6431,6 @@ phutil_register_library_map(array( 'PholioTransaction' => 'PhabricatorApplicationTransaction', 'PholioTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PholioTransactionQuery' => 'PhabricatorApplicationTransactionQuery', - 'PholioTransactionType' => 'PholioConstants', 'PholioTransactionView' => 'PhabricatorApplicationTransactionView', 'PholioUploadedImageView' => 'AphrontView', 'PhortuneAccount' => array( @@ -6539,7 +6624,6 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', ), 'PhrequentUserTimeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', - 'PhrictionActionConstants' => 'PhrictionConstants', 'PhrictionChangeType' => 'PhrictionConstants', 'PhrictionConduitAPIMethod' => 'ConduitAPIMethod', 'PhrictionContent' => array( diff --git a/src/aphront/configuration/AphrontApplicationConfiguration.php b/src/aphront/configuration/AphrontApplicationConfiguration.php index a3821f18b5..d1ced8a3e8 100644 --- a/src/aphront/configuration/AphrontApplicationConfiguration.php +++ b/src/aphront/configuration/AphrontApplicationConfiguration.php @@ -80,7 +80,7 @@ abstract class AphrontApplicationConfiguration { // This is the earliest we can get away with this, we need env config first. PhabricatorAccessLog::init(); $access_log = PhabricatorAccessLog::getLog(); - PhabricatorStartup::setGlobal('log.access', $access_log); + PhabricatorStartup::setAccessLog($access_log); $access_log->setData( array( 'R' => AphrontRequest::getHTTPHeader('Referer', '-'), diff --git a/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php index 3d5bfffd2d..72131e9a28 100644 --- a/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php @@ -164,14 +164,6 @@ class AphrontDefaultApplicationConfiguration return $login_controller->handleRequest($request); } - $list = $ex->getMoreInfo(); - foreach ($list as $key => $item) { - $list[$key] = phutil_tag('li', array(), $item); - } - if ($list) { - $list = phutil_tag('ul', array(), $list); - } - $content = array( phutil_tag( 'div', @@ -179,17 +171,28 @@ class AphrontDefaultApplicationConfiguration 'class' => 'aphront-policy-rejection', ), $ex->getRejection()), - phutil_tag( + ); + + if ($ex->getCapabilityName()) { + $list = $ex->getMoreInfo(); + foreach ($list as $key => $item) { + $list[$key] = phutil_tag('li', array(), $item); + } + if ($list) { + $list = phutil_tag('ul', array(), $list); + } + + $content[] = phutil_tag( 'div', array( 'class' => 'aphront-capability-details', ), - pht('Users with the "%s" capability:', $ex->getCapabilityName())), - $list, - ); + pht('Users with the "%s" capability:', $ex->getCapabilityName())); - $dialog = new AphrontDialogView(); - $dialog + $content[] = $list; + } + + $dialog = id(new AphrontDialogView()) ->setTitle($ex->getTitle()) ->setClass('aphront-access-dialog') ->setUser($user) diff --git a/src/aphront/sink/__tests__/AphrontHTTPSinkTestCase.php b/src/aphront/sink/__tests__/AphrontHTTPSinkTestCase.php index 05235a77ba..7858aeea31 100644 --- a/src/aphront/sink/__tests__/AphrontHTTPSinkTestCase.php +++ b/src/aphront/sink/__tests__/AphrontHTTPSinkTestCase.php @@ -75,7 +75,9 @@ final class AphrontHTTPSinkTestCase extends PhabricatorTestCase { $this->assertEqual( 'for (;;);{"x":"\u003ciframe\u003e"}', $sink->getEmittedData(), - 'JSONResponse should prevent content-sniffing attacks.'); + pht( + '%s should prevent content-sniffing attacks.', + 'JSONResponse')); } diff --git a/src/applications/almanac/controller/AlmanacBindingEditController.php b/src/applications/almanac/controller/AlmanacBindingEditController.php index ad6936fa70..7fc5abc395 100644 --- a/src/applications/almanac/controller/AlmanacBindingEditController.php +++ b/src/applications/almanac/controller/AlmanacBindingEditController.php @@ -87,7 +87,7 @@ final class AlmanacBindingEditController ->appendControl( id(new AphrontFormTokenizerControl()) ->setName('interfacePHIDs') - ->setLabel('Interface') + ->setLabel(pht('Interface')) ->setLimit(1) ->setDatasource(new AlmanacInterfaceDatasource()) ->setValue($v_interface) diff --git a/src/applications/almanac/controller/AlmanacConsoleController.php b/src/applications/almanac/controller/AlmanacConsoleController.php index 9ab95af5d4..020027f23b 100644 --- a/src/applications/almanac/controller/AlmanacConsoleController.php +++ b/src/applications/almanac/controller/AlmanacConsoleController.php @@ -37,7 +37,7 @@ final class AlmanacConsoleController extends AlmanacController { $crumbs->addTextCrumb(pht('Console')); $box = id(new PHUIObjectBoxView()) - ->setHeaderText('Console') + ->setHeaderText(pht('Console')) ->setObjectList($menu); return $this->buildApplicationPage( diff --git a/src/applications/almanac/editor/AlmanacDeviceEditor.php b/src/applications/almanac/editor/AlmanacDeviceEditor.php index 82263e66c4..3f7817bccc 100644 --- a/src/applications/almanac/editor/AlmanacDeviceEditor.php +++ b/src/applications/almanac/editor/AlmanacDeviceEditor.php @@ -67,6 +67,7 @@ final class AlmanacDeviceEditor switch ($xaction->getTransactionType()) { case AlmanacDeviceTransaction::TYPE_NAME: + return; case AlmanacDeviceTransaction::TYPE_INTERFACE: $old = $xaction->getOldValue(); if ($old) { diff --git a/src/applications/almanac/query/AlmanacServiceQuery.php b/src/applications/almanac/query/AlmanacServiceQuery.php index 85217d1247..c31969d53f 100644 --- a/src/applications/almanac/query/AlmanacServiceQuery.php +++ b/src/applications/almanac/query/AlmanacServiceQuery.php @@ -60,47 +60,35 @@ final class AlmanacServiceQuery } protected function loadPage() { - $table = new AlmanacService(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT service.* FROM %T service %Q %Q %Q %Q', - $table->getTableName(), - $this->buildJoinClause($conn_r), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - return $table->loadAllFromArray($data); + return $this->loadStandardPage(new AlmanacService()); } - protected function buildJoinClause(AphrontDatabaseConnection $conn_r) { - $joins = array(); + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $joins = parent::buildJoinClauseParts($conn); if ($this->devicePHIDs !== null) { $joins[] = qsprintf( - $conn_r, + $conn, 'JOIN %T binding ON service.phid = binding.servicePHID', id(new AlmanacBinding())->getTableName()); } - return implode(' ', $joins); + return $joins; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'service.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'service.phid IN (%Ls)', $this->phids); } @@ -112,49 +100,47 @@ final class AlmanacServiceQuery } $where[] = qsprintf( - $conn_r, + $conn, 'service.nameIndex IN (%Ls)', $hashes); } if ($this->serviceClasses !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'service.serviceClass IN (%Ls)', $this->serviceClasses); } if ($this->devicePHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'binding.devicePHID IN (%Ls)', $this->devicePHIDs); } if ($this->locked !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'service.isLocked = %d', (int)$this->locked); } if ($this->namePrefix !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'service.name LIKE %>', $this->namePrefix); } if ($this->nameSuffix !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'service.name LIKE %<', $this->nameSuffix); } - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); + return $where; } protected function willFilterPage(array $services) { @@ -192,10 +178,14 @@ final class AlmanacServiceQuery return parent::didFilterPage($services); } + protected function getPrimaryTableAlias() { + return 'service'; + } + public function getOrderableColumns() { return parent::getOrderableColumns() + array( 'name' => array( - 'table' => 'service', + 'table' => $this->getPrimaryTableAlias(), 'column' => 'name', 'type' => 'string', 'unique' => true, diff --git a/src/applications/almanac/query/AlmanacServiceSearchEngine.php b/src/applications/almanac/query/AlmanacServiceSearchEngine.php index 8610f86572..27672e8bf9 100644 --- a/src/applications/almanac/query/AlmanacServiceSearchEngine.php +++ b/src/applications/almanac/query/AlmanacServiceSearchEngine.php @@ -11,30 +11,26 @@ final class AlmanacServiceSearchEngine return 'PhabricatorAlmanacApplication'; } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - - $this->saveQueryOrder($saved, $request); - - return $saved; + public function newQuery() { + return new AlmanacServiceQuery(); } - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new AlmanacServiceQuery()); + public function newResultObject() { + // NOTE: We need to attach a service type in order to generate custom + // field definitions. + return AlmanacService::initializeNewService() + ->attachServiceType(new AlmanacCustomServiceType()); + } - $this->setQueryOrder($query, $saved); + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved) { - $this->appendOrderFieldsToForm( - $form, - $saved, - new AlmanacServiceQuery()); + protected function buildCustomSearchFields() { + return array(); } protected function getURI($path) { @@ -62,12 +58,6 @@ final class AlmanacServiceSearchEngine return parent::buildSavedQueryFromBuiltin($query_key); } - protected function getRequiredHandlePHIDsForResultList( - array $services, - PhabricatorSavedQuery $query) { - return array(); - } - protected function renderResultList( array $services, PhabricatorSavedQuery $query, diff --git a/src/applications/aphlict/management/PhabricatorAphlictManagementWorkflow.php b/src/applications/aphlict/management/PhabricatorAphlictManagementWorkflow.php index d5d751363b..be5f6bae3c 100644 --- a/src/applications/aphlict/management/PhabricatorAphlictManagementWorkflow.php +++ b/src/applications/aphlict/management/PhabricatorAphlictManagementWorkflow.php @@ -86,7 +86,7 @@ abstract class PhabricatorAphlictManagementWorkflow exit(1); } - protected final function setDebug($debug) { + final protected function setDebug($debug) { $this->debug = $debug; } diff --git a/src/applications/audit/editor/PhabricatorAuditCommentEditor.php b/src/applications/audit/editor/PhabricatorAuditCommentEditor.php index 904592f7fa..9f4bd42f29 100644 --- a/src/applications/audit/editor/PhabricatorAuditCommentEditor.php +++ b/src/applications/audit/editor/PhabricatorAuditCommentEditor.php @@ -44,7 +44,9 @@ final class PhabricatorAuditCommentEditor extends PhabricatorEditor { return array( 'diffusion-audit-'.$commit->getPHID(), - 'Commit r'.$repository->getCallsign().$commit->getCommitIdentifier(), + pht( + 'Commit %s', + 'r'.$repository->getCallsign().$commit->getCommitIdentifier()), ); } diff --git a/src/applications/audit/editor/PhabricatorAuditEditor.php b/src/applications/audit/editor/PhabricatorAuditEditor.php index 24bc342f05..95f8ee8311 100644 --- a/src/applications/audit/editor/PhabricatorAuditEditor.php +++ b/src/applications/audit/editor/PhabricatorAuditEditor.php @@ -8,6 +8,7 @@ final class PhabricatorAuditEditor private $auditReasonMap = array(); private $affectedFiles; private $rawPatch; + private $auditorPHIDs = array(); private $didExpandInlineState; @@ -342,6 +343,9 @@ final class PhabricatorAuditEditor $object->writeImportStatusFlag($import_status_flag); } + // Collect auditor PHIDs for building mail. + $this->auditorPHIDs = mpull($object->getAudits(), 'getAuditorPHID'); + return $xactions; } @@ -669,17 +673,11 @@ final class PhabricatorAuditEditor $object); } - // Reload the commit to pull commit data. - $commit = id(new DiffusionCommitQuery()) - ->setViewer($this->requireActor()) - ->withIDs(array($object->getID())) - ->needCommitData(true) - ->executeOne(); - $data = $commit->getCommitData(); + $data = $object->getCommitData(); $user_phids = array(); - $author_phid = $commit->getAuthorPHID(); + $author_phid = $object->getAuthorPHID(); if ($author_phid) { $user_phids[$author_phid][] = pht('Author'); } @@ -689,10 +687,7 @@ final class PhabricatorAuditEditor $user_phids[$committer_phid][] = pht('Committer'); } - // we loaded this in applyFinalEffects - $audit_requests = $object->getAudits(); - $auditor_phids = mpull($audit_requests, 'getAuditorPHID'); - foreach ($auditor_phids as $auditor_phid) { + foreach ($this->auditorPHIDs as $auditor_phid) { $user_phids[$auditor_phid][] = pht('Auditor'); } @@ -894,6 +889,7 @@ final class PhabricatorAuditEditor "H{$rule_id}")); } } + if ($audit_phids) { $xactions[] = id(new PhabricatorAuditTransaction()) ->setTransactionType(PhabricatorAuditActionConstants::ADD_AUDITORS) @@ -905,15 +901,6 @@ final class PhabricatorAuditEditor 'auditReasonMap', $this->auditReasonMap); } - $cc_phids = $adapter->getAddCCMap(); - $add_ccs = array('+' => array()); - foreach ($cc_phids as $phid => $rule_ids) { - $add_ccs['+'][$phid] = $phid; - } - $xactions[] = id(new PhabricatorAuditTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS) - ->setNewValue($add_ccs); - HarbormasterBuildable::applyBuildPlans( $object->getPHID(), $object->getRepository()->getPHID(), @@ -983,4 +970,28 @@ final class PhabricatorAuditEditor return $this->shouldPublishRepositoryActivity($object, $xactions); } + protected function getCustomWorkerState() { + return array( + 'rawPatch' => $this->rawPatch, + 'affectedFiles' => $this->affectedFiles, + 'auditorPHIDs' => $this->auditorPHIDs, + ); + } + + protected function loadCustomWorkerState(array $state) { + $this->rawPatch = idx($state, 'rawPatch'); + $this->affectedFiles = idx($state, 'affectedFiles'); + $this->auditorPHIDs = idx($state, 'auditorPHIDs'); + return $this; + } + + protected function willPublish(PhabricatorLiskDAO $object, array $xactions) { + return id(new DiffusionCommitQuery()) + ->setViewer($this->requireActor()) + ->withIDs(array($object->getID())) + ->needAuditRequests(true) + ->needCommitData(true) + ->executeOne(); + } + } diff --git a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php index 3d030769da..cec965f6b5 100644 --- a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php +++ b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php @@ -158,6 +158,21 @@ final class PhabricatorAuthSessionEngine extends Phobject { $session_dict[substr($key, 2)] = $value; } } + + $user = $user_table->loadFromArray($info); + switch ($session_type) { + case PhabricatorAuthSession::TYPE_WEB: + // Explicitly prevent bots and mailing lists from establishing web + // sessions. It's normally impossible to attach authentication to these + // accounts, and likewise impossible to generate sessions, but it's + // technically possible that a session could exist in the database. If + // one does somehow, refuse to load it. + if (!$user->canEstablishWebSessions()) { + return null; + } + break; + } + $session = id(new PhabricatorAuthSession())->loadFromArray($session_dict); $ttl = PhabricatorAuthSession::getSessionTypeTTL($session_type); @@ -181,7 +196,6 @@ final class PhabricatorAuthSessionEngine extends Phobject { unset($unguarded); } - $user = $user_table->loadFromArray($info); $user->attachSession($session); return $user; } diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php index e97e4d605f..4c176641b0 100644 --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -29,7 +29,7 @@ abstract class PhabricatorApplication implements PhabricatorPolicyInterface { /* -( Application Information )-------------------------------------------- */ - public abstract function getName(); + abstract public function getName(); public function getShortDescription() { return pht('%s Application', $this->getName()); diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index 04bb633d4e..3476bb8f9a 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -114,10 +114,7 @@ abstract class PhabricatorController extends AphrontController { $request->setUser($user); } - $locale_code = $user->getTranslation(); - if ($locale_code) { - PhabricatorEnv::setLocaleCode($locale_code); - } + PhabricatorEnv::setLocaleCode($user->getTranslation()); $preferences = $user->loadPreferences(); if (PhabricatorEnv::getEnvConfig('darkconsole.enabled')) { diff --git a/src/applications/cache/PhabricatorCaches.php b/src/applications/cache/PhabricatorCaches.php index 1cddba9fb1..e3290172fb 100644 --- a/src/applications/cache/PhabricatorCaches.php +++ b/src/applications/cache/PhabricatorCaches.php @@ -1,12 +1,16 @@ setCaches($caches); } +/* -( Request Cache )------------------------------------------------------ */ -/* -( Local Cache )-------------------------------------------------------- */ + + /** + * Get a request cache stack. + * + * This cache stack is destroyed after each logical request. In particular, + * it is destroyed periodically by the daemons, while `static` caches are + * not. + * + * @return PhutilKeyValueCacheStack Request cache stack. + */ + public static function getRequestCache() { + if (!self::$requestCache) { + self::$requestCache = new PhutilInRequestKeyValueCache(); + } + return self::$requestCache; + } + + + /** + * Destroy the request cache. + * + * This is called at the beginning of each logical request. + * + * @return void + */ + public static function destroyRequestCache() { + self::$requestCache = null; + } + + +/* -( Immutable Cache )---------------------------------------------------- */ /** diff --git a/src/applications/cache/__tests__/PhabricatorCachesTestCase.php b/src/applications/cache/__tests__/PhabricatorCachesTestCase.php new file mode 100644 index 0000000000..7cfa713ead --- /dev/null +++ b/src/applications/cache/__tests__/PhabricatorCachesTestCase.php @@ -0,0 +1,41 @@ +assertEqual( + $default_value, + $cache->getKey($test_key, $default_value)); + + // Set a key, verify it persists. + $cache = PhabricatorCaches::getRequestCache(); + $cache->setKey($test_key, $new_value); + $this->assertEqual( + $new_value, + $cache->getKey($test_key, $default_value)); + + // Refetch the cache, verify it's really a cache. + $cache = PhabricatorCaches::getRequestCache(); + $this->assertEqual( + $new_value, + $cache->getKey($test_key, $default_value)); + + // Destroy the cache. + PhabricatorCaches::destroyRequestCache(); + + // Now, the value should be missing again. + $cache = PhabricatorCaches::getRequestCache(); + $this->assertEqual( + $default_value, + $cache->getKey($test_key, $default_value)); + } + +} diff --git a/src/applications/calendar/application/PhabricatorCalendarApplication.php b/src/applications/calendar/application/PhabricatorCalendarApplication.php index d41577a3af..20e89425df 100644 --- a/src/applications/calendar/application/PhabricatorCalendarApplication.php +++ b/src/applications/calendar/application/PhabricatorCalendarApplication.php @@ -40,7 +40,8 @@ final class PhabricatorCalendarApplication extends PhabricatorApplication { public function getRoutes() { return array( - '/E(?P[1-9]\d*)' => 'PhabricatorCalendarEventViewController', + '/E(?P[1-9]\d*)(?:/(?P\d+))?' + => 'PhabricatorCalendarEventViewController', '/calendar/' => array( '(?:query/(?P[^/]+)/(?:(?P\d+)/'. '(?P\d+)/)?(?:(?P\d+)/)?)?' @@ -52,11 +53,11 @@ final class PhabricatorCalendarApplication extends PhabricatorApplication { 'event/' => array( 'create/' => 'PhabricatorCalendarEventEditController', - 'edit/(?P[1-9]\d*)/' + 'edit/(?P[1-9]\d*)/(?:(?P\d+)/)?' => 'PhabricatorCalendarEventEditController', 'drag/(?P[1-9]\d*)/' => 'PhabricatorCalendarEventDragController', - 'cancel/(?P[1-9]\d*)/' + 'cancel/(?P[1-9]\d*)/(?:(?P\d+)/)?' => 'PhabricatorCalendarEventCancelController', '(?Pjoin|decline|accept)/(?P[1-9]\d*)/' => 'PhabricatorCalendarEventJoinController', diff --git a/src/applications/calendar/constants/CalendarColors.php b/src/applications/calendar/constants/CalendarColors.php deleted file mode 100644 index e7bd792e0b..0000000000 --- a/src/applications/calendar/constants/CalendarColors.php +++ /dev/null @@ -1,29 +0,0 @@ -setViewer($viewer) + ->withInstanceSequencePairs( + array( + array( + $phid, + $index, + ), + )) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + + return $result; + } + + protected function createEventFromGhost($viewer, $event, $index) { + $invitees = $event->getInvitees(); + + $new_ghost = $event->generateNthGhost($index, $viewer); + $new_ghost->attachParentEvent($event); + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $new_ghost + ->setID(null) + ->setPHID(null) + ->removeViewerTimezone($viewer) + ->save(); + $ghost_invitees = array(); + foreach ($invitees as $invitee) { + $ghost_invitee = clone $invitee; + $ghost_invitee + ->setID(null) + ->setEventPHID($new_ghost->getPHID()) + ->save(); + } + unset($unguarded); + return $new_ghost; + } } diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventCancelController.php b/src/applications/calendar/controller/PhabricatorCalendarEventCancelController.php index a3909e7207..c29541bcfd 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventCancelController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventCancelController.php @@ -12,8 +12,9 @@ final class PhabricatorCalendarEventCancelController public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); + $sequence = $request->getURIData('sequence'); - $status = id(new PhabricatorCalendarEventQuery()) + $event = id(new PhabricatorCalendarEventQuery()) ->setViewer($user) ->withIDs(array($this->id)) ->requireCapabilities( @@ -23,15 +24,39 @@ final class PhabricatorCalendarEventCancelController )) ->executeOne(); - if (!$status) { + if ($sequence) { + $parent_event = $event; + $event = $parent_event->generateNthGhost($sequence, $user); + $event->attachParentEvent($parent_event); + } + + if (!$event) { return new Aphront404Response(); } - $cancel_uri = '/E'.$status->getID(); + if (!$sequence) { + $cancel_uri = '/E'.$event->getID(); + } else { + $cancel_uri = '/E'.$event->getID().'/'.$sequence; + } + + $is_cancelled = $event->getIsCancelled(); + $is_parent_cancelled = $event->getIsParentCancelled(); + $is_parent = $event->getIsRecurrenceParent(); + $validation_exception = null; - $is_cancelled = $status->getIsCancelled(); if ($request->isFormPost()) { + if ($is_cancelled && $sequence) { + return id(new AphrontRedirectResponse())->setURI($cancel_uri); + } else if ($sequence) { + $event = $this->createEventFromGhost( + $user, + $event, + $sequence); + $event->applyViewerTimezone($user); + } + $xactions = array(); $xaction = id(new PhabricatorCalendarEventTransaction()) @@ -46,7 +71,7 @@ final class PhabricatorCalendarEventCancelController ->setContinueOnMissingFields(true); try { - $editor->applyTransactions($status, array($xaction)); + $editor->applyTransactions($event, array($xaction)); return id(new AphrontRedirectResponse())->setURI($cancel_uri); } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; @@ -54,15 +79,44 @@ final class PhabricatorCalendarEventCancelController } if ($is_cancelled) { - $title = pht('Reinstate Event'); - $paragraph = pht('Reinstate this event?'); - $cancel = pht('Don\'t Reinstate Event'); - $submit = pht('Reinstate Event'); + if ($sequence || $is_parent_cancelled) { + $title = pht('Cannot Reinstate Instance'); + $paragraph = pht('Cannot reinstate an instance of a + cancelled recurring event.'); + $cancel = pht('Cancel'); + $submit = null; + } else if ($is_parent) { + $title = pht('Reinstate Recurrence'); + $paragraph = pht('Reinstate the entire series + of recurring events?'); + $cancel = pht('Don\'t Reinstate Recurrence'); + $submit = pht('Reinstate Recurrence'); + } else { + $title = pht('Reinstate Event'); + $paragraph = pht('Reinstate this event?'); + $cancel = pht('Don\'t Reinstate Event'); + $submit = pht('Reinstate Event'); + } } else { - $title = pht('Cancel Event'); - $paragraph = pht('You can always reinstate the event later.'); - $cancel = pht('Don\'t Cancel Event'); - $submit = pht('Cancel Event'); + if ($sequence) { + $title = pht('Cancel Instance'); + $paragraph = pht('Cancel just this instance + of a recurring event.'); + $cancel = pht('Don\'t Cancel Instance'); + $submit = pht('Cancel Instance'); + } else if ($is_parent) { + $title = pht('Cancel Recurrence'); + $paragraph = pht('Cancel the entire series + of recurring events?'); + $cancel = pht('Don\'t Cancel Recurrence'); + $submit = pht('Cancel Recurrence'); + } else { + $title = pht('Cancel Event'); + $paragraph = pht('You can always reinstate + the event later.'); + $cancel = pht('Don\'t Cancel Event'); + $submit = pht('Cancel Event'); + } } return $this->newDialog() diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php index 109aa692bd..ccf7cbf0b0 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php @@ -17,13 +17,17 @@ final class PhabricatorCalendarEventEditController $viewer = $request->getViewer(); $user_phid = $viewer->getPHID(); $error_name = true; + $error_recurrence_end_date = true; $error_start_date = true; $error_end_date = true; $validation_exception = null; + $is_recurring_id = celerity_generate_unique_node_id(); + $recurrence_end_date_id = celerity_generate_unique_node_id(); + $frequency_id = celerity_generate_unique_node_id(); $all_day_id = celerity_generate_unique_node_id(); $start_date_id = celerity_generate_unique_node_id(); - $end_date_id = null; + $end_date_id = celerity_generate_unique_node_id(); $next_workflow = $request->getStr('next'); $uri_query = $request->getStr('query'); @@ -63,6 +67,8 @@ final class PhabricatorCalendarEventEditController list($start_value, $end_value) = $this->getDefaultTimeValues($viewer); } + $recurrence_end_date_value = clone $end_value; + $recurrence_end_date_value->setOptional(true); $submit_label = pht('Create'); $page_title = pht('Create Event'); @@ -70,27 +76,51 @@ final class PhabricatorCalendarEventEditController $subscribers = array(); $invitees = array($user_phid); $cancel_uri = $this->getApplicationURI(); - $end_date_id = celerity_generate_unique_node_id(); } else { $event = id(new PhabricatorCalendarEventQuery()) - ->setViewer($viewer) - ->withIDs(array($this->id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); + ->setViewer($viewer) + ->withIDs(array($this->id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$event) { return new Aphront404Response(); } + if ($request->getURIData('sequence')) { + $index = $request->getURIData('sequence'); + + $result = $this->getEventAtIndexForGhostPHID( + $viewer, + $event->getPHID(), + $index); + + if ($result) { + return id(new AphrontRedirectResponse()) + ->setURI('/calendar/event/edit/'.$result->getID().'/'); + } + + $event = $this->createEventFromGhost( + $viewer, + $event, + $index); + + return id(new AphrontRedirectResponse()) + ->setURI('/calendar/event/edit/'.$event->getID().'/'); + } + $end_value = AphrontFormDateControlValue::newFromEpoch( $viewer, $event->getDateTo()); $start_value = AphrontFormDateControlValue::newFromEpoch( $viewer, $event->getDateFrom()); + $recurrence_end_date_value = id(clone $end_value) + ->setOptional(true); $submit_label = pht('Update'); $page_title = pht('Update Event'); @@ -113,6 +143,9 @@ final class PhabricatorCalendarEventEditController $name = $event->getName(); $description = $event->getDescription(); $is_all_day = $event->getIsAllDay(); + $is_recurring = $event->getIsRecurring(); + $is_parent = $event->getIsRecurrenceParent(); + $frequency = idx($event->getRecurrenceFrequency(), 'rule'); $icon = $event->getIcon(); $current_policies = id(new PhabricatorPolicyQuery()) @@ -130,10 +163,16 @@ final class PhabricatorCalendarEventEditController $end_value = AphrontFormDateControlValue::newFromRequest( $request, 'end'); + $recurrence_end_date_value = AphrontFormDateControlValue::newFromRequest( + $request, + 'recurrenceEndDate'); + $recurrence_end_date_value->setOptional(true); $description = $request->getStr('description'); $subscribers = $request->getArr('subscribers'); $edit_policy = $request->getStr('editPolicy'); $view_policy = $request->getStr('viewPolicy'); + $is_recurring = $request->getStr('isRecurring') ? 1 : 0; + $frequency = $request->getStr('frequency'); $is_all_day = $request->getStr('isAllDay'); $icon = $request->getStr('icon'); @@ -152,25 +191,47 @@ final class PhabricatorCalendarEventEditController PhabricatorCalendarEventTransaction::TYPE_NAME) ->setNewValue($name); - $xactions[] = id(new PhabricatorCalendarEventTransaction()) - ->setTransactionType( - PhabricatorCalendarEventTransaction::TYPE_ALL_DAY) - ->setNewValue($is_all_day); + if ($is_parent && $this->isCreate()) { + $xactions[] = id(new PhabricatorCalendarEventTransaction()) + ->setTransactionType( + PhabricatorCalendarEventTransaction::TYPE_RECURRING) + ->setNewValue($is_recurring); - $xactions[] = id(new PhabricatorCalendarEventTransaction()) - ->setTransactionType( - PhabricatorCalendarEventTransaction::TYPE_ICON) - ->setNewValue($icon); + $xactions[] = id(new PhabricatorCalendarEventTransaction()) + ->setTransactionType( + PhabricatorCalendarEventTransaction::TYPE_FREQUENCY) + ->setNewValue(array('rule' => $frequency)); - $xactions[] = id(new PhabricatorCalendarEventTransaction()) - ->setTransactionType( - PhabricatorCalendarEventTransaction::TYPE_START_DATE) - ->setNewValue($start_value); + if (!$recurrence_end_date_value->isDisabled()) { + $xactions[] = id(new PhabricatorCalendarEventTransaction()) + ->setTransactionType( + PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE) + ->setNewValue($recurrence_end_date_value); + } + } + + if (($is_parent && $this->isCreate()) || !$is_parent) { + $xactions[] = id(new PhabricatorCalendarEventTransaction()) + ->setTransactionType( + PhabricatorCalendarEventTransaction::TYPE_ALL_DAY) + ->setNewValue($is_all_day); + + $xactions[] = id(new PhabricatorCalendarEventTransaction()) + ->setTransactionType( + PhabricatorCalendarEventTransaction::TYPE_ICON) + ->setNewValue($icon); + + $xactions[] = id(new PhabricatorCalendarEventTransaction()) + ->setTransactionType( + PhabricatorCalendarEventTransaction::TYPE_START_DATE) + ->setNewValue($start_value); + + $xactions[] = id(new PhabricatorCalendarEventTransaction()) + ->setTransactionType( + PhabricatorCalendarEventTransaction::TYPE_END_DATE) + ->setNewValue($end_value); + } - $xactions[] = id(new PhabricatorCalendarEventTransaction()) - ->setTransactionType( - PhabricatorCalendarEventTransaction::TYPE_END_DATE) - ->setNewValue($end_value); $xactions[] = id(new PhabricatorCalendarEventTransaction()) ->setTransactionType( @@ -227,17 +288,23 @@ final class PhabricatorCalendarEventEditController PhabricatorCalendarEventTransaction::TYPE_START_DATE); $error_end_date = $ex->getShortMessage( PhabricatorCalendarEventTransaction::TYPE_END_DATE); + $error_recurrence_end_date = $ex->getShortMessage( + PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE); $event->setViewPolicy($view_policy); $event->setEditPolicy($edit_policy); } } - Javelin::initBehavior('event-all-day', array( - 'allDayID' => $all_day_id, - 'startDateID' => $start_date_id, - 'endDateID' => $end_date_id, - )); + $is_recurring_checkbox = null; + $recurrence_end_date_control = null; + $recurrence_frequency_select = null; + + $all_day_checkbox = null; + $start_control = null; + $end_control = null; + + $recurring_date_edit_label = null; $name = id(new AphrontFormTextControl()) ->setLabel(pht('Name')) @@ -245,32 +312,131 @@ final class PhabricatorCalendarEventEditController ->setValue($name) ->setError($error_name); - $all_day_checkbox = id(new AphrontFormCheckboxControl()) - ->addCheckbox( - 'isAllDay', - 1, - pht('All Day Event'), - $is_all_day, - $all_day_id); + if ($this->isCreate()) { + Javelin::initBehavior('recurring-edit', array( + 'isRecurring' => $is_recurring_id, + 'frequency' => $frequency_id, + 'recurrenceEndDate' => $recurrence_end_date_id, + )); - $start_control = id(new AphrontFormDateControl()) - ->setUser($viewer) - ->setName('start') - ->setLabel(pht('Start')) - ->setError($error_start_date) - ->setValue($start_value) - ->setID($start_date_id) - ->setIsTimeDisabled($is_all_day) - ->setEndDateID($end_date_id); + $is_recurring_checkbox = id(new AphrontFormCheckboxControl()) + ->addCheckbox( + 'isRecurring', + 1, + pht('Recurring Event'), + $is_recurring, + $is_recurring_id); - $end_control = id(new AphrontFormDateControl()) - ->setUser($viewer) - ->setName('end') - ->setLabel(pht('End')) - ->setError($error_end_date) - ->setValue($end_value) - ->setID($end_date_id) - ->setIsTimeDisabled($is_all_day); + $recurrence_end_date_control = id(new AphrontFormDateControl()) + ->setUser($viewer) + ->setName('recurrenceEndDate') + ->setLabel(pht('Recurrence End Date')) + ->setError($error_recurrence_end_date) + ->setValue($recurrence_end_date_value) + ->setID($recurrence_end_date_id) + ->setIsTimeDisabled(true) + ->setIsDisabled($recurrence_end_date_value->isDisabled()) + ->setAllowNull(true) + ->isRequired(false); + + $recurrence_frequency_select = id(new AphrontFormSelectControl()) + ->setName('frequency') + ->setOptions(array( + 'daily' => pht('Daily'), + 'weekly' => pht('Weekly'), + 'monthly' => pht('Monthly'), + 'yearly' => pht('Yearly'), + )) + ->setValue($frequency) + ->setLabel(pht('Recurring Event Frequency')) + ->setID($frequency_id) + ->setDisabled(!$is_recurring); + } + + if ($this->isCreate() || (!$is_parent && !$this->isCreate())) { + Javelin::initBehavior('event-all-day', array( + 'allDayID' => $all_day_id, + 'startDateID' => $start_date_id, + 'endDateID' => $end_date_id, + )); + + $all_day_checkbox = id(new AphrontFormCheckboxControl()) + ->addCheckbox( + 'isAllDay', + 1, + pht('All Day Event'), + $is_all_day, + $all_day_id); + + $start_control = id(new AphrontFormDateControl()) + ->setUser($viewer) + ->setName('start') + ->setLabel(pht('Start')) + ->setError($error_start_date) + ->setValue($start_value) + ->setID($start_date_id) + ->setIsTimeDisabled($is_all_day) + ->setEndDateID($end_date_id); + + $end_control = id(new AphrontFormDateControl()) + ->setUser($viewer) + ->setName('end') + ->setLabel(pht('End')) + ->setError($error_end_date) + ->setValue($end_value) + ->setID($end_date_id) + ->setIsTimeDisabled($is_all_day); + } else if ($is_parent) { + $recurring_date_edit_label = id(new AphrontFormStaticControl()) + ->setUser($viewer) + ->setValue(pht('Date and time of recurring event cannot be edited.')); + + if (!$recurrence_end_date_value->isDisabled()) { + $disabled_recurrence_end_date_value = + $recurrence_end_date_value->getValueAsFormat('M d, Y'); + $recurrence_end_date_control = id(new AphrontFormStaticControl()) + ->setUser($viewer) + ->setLabel(pht('Recurrence End Date')) + ->setValue($disabled_recurrence_end_date_value) + ->setDisabled(true); + } + + $recurrence_frequency_select = id(new AphrontFormSelectControl()) + ->setName('frequency') + ->setOptions(array( + 'daily' => pht('Daily'), + 'weekly' => pht('Weekly'), + 'monthly' => pht('Monthly'), + 'yearly' => pht('Yearly'), + )) + ->setValue($frequency) + ->setLabel(pht('Recurring Event Frequency')) + ->setID($frequency_id) + ->setDisabled(true); + + $all_day_checkbox = id(new AphrontFormCheckboxControl()) + ->addCheckbox( + 'isAllDay', + 1, + pht('All Day Event'), + $is_all_day, + $all_day_id) + ->setDisabled(true); + + $start_disabled = $start_value->getValueAsFormat('M d, Y, g:i A'); + $end_disabled = $end_value->getValueAsFormat('M d, Y, g:i A'); + + $start_control = id(new AphrontFormStaticControl()) + ->setUser($viewer) + ->setLabel(pht('Start')) + ->setValue($start_disabled) + ->setDisabled(true); + + $end_control = id(new AphrontFormStaticControl()) + ->setUser($viewer) + ->setLabel(pht('End')) + ->setValue($end_disabled); + } $description = id(new AphrontFormTextAreaControl()) ->setLabel(pht('Description')) @@ -322,7 +488,22 @@ final class PhabricatorCalendarEventEditController ->addHiddenInput('next', $next_workflow) ->addHiddenInput('query', $uri_query) ->setUser($viewer) - ->appendChild($name) + ->appendChild($name); + + if ($recurring_date_edit_label) { + $form->appendControl($recurring_date_edit_label); + } + if ($is_recurring_checkbox) { + $form->appendChild($is_recurring_checkbox); + } + if ($recurrence_end_date_control) { + $form->appendChild($recurrence_end_date_control); + } + if ($recurrence_frequency_select) { + $form->appendControl($recurrence_frequency_select); + } + + $form ->appendChild($all_day_checkbox) ->appendChild($start_control) ->appendChild($end_control) diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php index 656446c2a3..7409bc5586 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -17,6 +17,8 @@ final class PhabricatorCalendarEventViewController $request = $this->getRequest(); $viewer = $request->getUser(); + $sequence = $request->getURIData('sequence'); + $event = id(new PhabricatorCalendarEventQuery()) ->setViewer($viewer) ->withIDs(array($this->id)) @@ -25,10 +27,38 @@ final class PhabricatorCalendarEventViewController return new Aphront404Response(); } - $title = 'E'.$event->getID(); - $page_title = $title.' '.$event->getName(); - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb($title, '/E'.$event->getID()); + if ($sequence) { + $result = $this->getEventAtIndexForGhostPHID( + $viewer, + $event->getPHID(), + $sequence); + + if ($result) { + $parent_event = $event; + $event = $result; + $event->attachParentEvent($parent_event); + return id(new AphrontRedirectResponse()) + ->setURI('/E'.$result->getID()); + } else if ($sequence && $event->getIsRecurring()) { + $parent_event = $event; + $event = $event->generateNthGhost($sequence, $viewer); + $event->attachParentEvent($parent_event); + } else if ($sequence) { + return new Aphront404Response(); + } + + $title = $event->getMonogram().' ('.$sequence.')'; + $page_title = $title.' '.$event->getName(); + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb($title, '/'.$event->getMonogram().'/'.$sequence); + + + } else { + $title = 'E'.$event->getID(); + $page_title = $title.' '.$event->getName(); + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb($title, '/E'.$event->getID()); + } $timeline = $this->buildTransactionTimeline( $event, @@ -127,13 +157,30 @@ final class PhabricatorCalendarEventViewController $event, PhabricatorPolicyCapability::CAN_EDIT); - $actions->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Edit Event')) - ->setIcon('fa-pencil') - ->setHref($this->getApplicationURI("event/edit/{$id}/")) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit)); + $edit_label = false; + $edit_uri = false; + + if ($event->getIsGhostEvent()) { + $index = $event->getSequenceIndex(); + $edit_label = pht('Edit This Instance'); + $edit_uri = "event/edit/{$id}/{$index}/"; + } else if ($event->getIsRecurrenceException()) { + $edit_label = pht('Edit This Instance'); + $edit_uri = "event/edit/{$id}/"; + } else { + $edit_label = pht('Edit'); + $edit_uri = "event/edit/{$id}/"; + } + + if ($edit_label && $edit_uri) { + $actions->addAction( + id(new PhabricatorActionView()) + ->setName($edit_label) + ->setIcon('fa-pencil') + ->setHref($this->getApplicationURI($edit_uri)) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); + } if ($is_attending) { $actions->addAction( @@ -151,21 +198,46 @@ final class PhabricatorCalendarEventViewController ->setWorkflow(true)); } + $cancel_uri = $this->getApplicationURI("event/cancel/{$id}/"); + + if ($event->getIsGhostEvent()) { + $index = $event->getSequenceIndex(); + $can_reinstate = $event->getIsParentCancelled(); + + $cancel_label = pht('Cancel This Instance'); + $reinstate_label = pht('Reinstate This Instance'); + $cancel_disabled = (!$can_edit || $can_reinstate); + $cancel_uri = $this->getApplicationURI("event/cancel/{$id}/{$index}/"); + } else if ($event->getIsRecurrenceException()) { + $can_reinstate = $event->getIsParentCancelled(); + $cancel_label = pht('Cancel This Instance'); + $reinstate_label = pht('Reinstate This Instance'); + $cancel_disabled = (!$can_edit || $can_reinstate); + } else if ($event->getIsRecurrenceParent()) { + $cancel_label = pht('Cancel Recurrence'); + $reinstate_label = pht('Reinstate Recurrence'); + $cancel_disabled = !$can_edit; + } else { + $cancel_label = pht('Cancel Event'); + $reinstate_label = pht('Reinstate Event'); + $cancel_disabled = !$can_edit; + } + if ($is_cancelled) { $actions->addAction( id(new PhabricatorActionView()) - ->setName(pht('Reinstate Event')) + ->setName($reinstate_label) ->setIcon('fa-plus') - ->setHref($this->getApplicationURI("event/cancel/{$id}/")) - ->setDisabled(!$can_edit) + ->setHref($cancel_uri) + ->setDisabled($cancel_disabled) ->setWorkflow(true)); } else { $actions->addAction( id(new PhabricatorActionView()) - ->setName(pht('Cancel Event')) + ->setName($cancel_label) ->setIcon('fa-times') - ->setHref($this->getApplicationURI("event/cancel/{$id}/")) - ->setDisabled(!$can_edit) + ->setHref($cancel_uri) + ->setDisabled($cancel_disabled) ->setWorkflow(true)); } @@ -205,6 +277,24 @@ final class PhabricatorCalendarEventViewController phabricator_datetime($event->getDateTo(), $viewer)); } + if ($event->getIsRecurring()) { + $properties->addProperty( + pht('Recurs'), + ucwords(idx($event->getRecurrenceFrequency(), 'rule'))); + + if ($event->getRecurrenceEndDate()) { + $properties->addProperty( + pht('Recurrence Ends'), + phabricator_datetime($event->getRecurrenceEndDate(), $viewer)); + } + + if ($event->getInstanceOfEventPHID()) { + $properties->addProperty( + pht('Recurrence of Event'), + $viewer->renderHandle($event->getInstanceOfEventPHID())); + } + } + $properties->addProperty( pht('Host'), $viewer->renderHandle($event->getUserPHID())); diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php index 291f6c1865..d01946b370 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php @@ -23,6 +23,12 @@ final class PhabricatorCalendarEventEditor $types[] = PhabricatorCalendarEventTransaction::TYPE_ALL_DAY; $types[] = PhabricatorCalendarEventTransaction::TYPE_ICON; + $types[] = PhabricatorCalendarEventTransaction::TYPE_RECURRING; + $types[] = PhabricatorCalendarEventTransaction::TYPE_FREQUENCY; + $types[] = PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE; + $types[] = PhabricatorCalendarEventTransaction::TYPE_INSTANCE_OF_EVENT; + $types[] = PhabricatorCalendarEventTransaction::TYPE_SEQUENCE_INDEX; + $types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; @@ -34,6 +40,16 @@ final class PhabricatorCalendarEventEditor PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { + case PhabricatorCalendarEventTransaction::TYPE_RECURRING: + return $object->getIsRecurring(); + case PhabricatorCalendarEventTransaction::TYPE_FREQUENCY: + return $object->getRecurrenceFrequency(); + case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: + return $object->getRecurrenceEndDate(); + case PhabricatorCalendarEventTransaction::TYPE_INSTANCE_OF_EVENT: + return $object->getInstanceOfEventPHID(); + case PhabricatorCalendarEventTransaction::TYPE_SEQUENCE_INDEX: + return $object->getSequenceIndex(); case PhabricatorCalendarEventTransaction::TYPE_NAME: return $object->getName(); case PhabricatorCalendarEventTransaction::TYPE_START_DATE: @@ -72,6 +88,10 @@ final class PhabricatorCalendarEventEditor PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { + case PhabricatorCalendarEventTransaction::TYPE_RECURRING: + case PhabricatorCalendarEventTransaction::TYPE_FREQUENCY: + case PhabricatorCalendarEventTransaction::TYPE_INSTANCE_OF_EVENT: + case PhabricatorCalendarEventTransaction::TYPE_SEQUENCE_INDEX: case PhabricatorCalendarEventTransaction::TYPE_NAME: case PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION: case PhabricatorCalendarEventTransaction::TYPE_CANCEL: @@ -80,6 +100,7 @@ final class PhabricatorCalendarEventEditor return $xaction->getNewValue(); case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY: return (int)$xaction->getNewValue(); + case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: case PhabricatorCalendarEventTransaction::TYPE_START_DATE: case PhabricatorCalendarEventTransaction::TYPE_END_DATE: return $xaction->getNewValue()->getEpoch(); @@ -93,6 +114,14 @@ final class PhabricatorCalendarEventEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { + case PhabricatorCalendarEventTransaction::TYPE_RECURRING: + return $object->setIsRecurring($xaction->getNewValue()); + case PhabricatorCalendarEventTransaction::TYPE_FREQUENCY: + return $object->setRecurrenceFrequency($xaction->getNewValue()); + case PhabricatorCalendarEventTransaction::TYPE_INSTANCE_OF_EVENT: + return $object->setInstanceOfEventPHID($xaction->getNewValue()); + case PhabricatorCalendarEventTransaction::TYPE_SEQUENCE_INDEX: + return $object->setSequenceIndex($xaction->getNewValue()); case PhabricatorCalendarEventTransaction::TYPE_NAME: $object->setName($xaction->getNewValue()); return; @@ -102,6 +131,9 @@ final class PhabricatorCalendarEventEditor case PhabricatorCalendarEventTransaction::TYPE_END_DATE: $object->setDateTo($xaction->getNewValue()); return; + case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: + $object->setRecurrenceEndDate($xaction->getNewValue()); + return; case PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION: $object->setDescription($xaction->getNewValue()); return; @@ -126,6 +158,11 @@ final class PhabricatorCalendarEventEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { + case PhabricatorCalendarEventTransaction::TYPE_RECURRING: + case PhabricatorCalendarEventTransaction::TYPE_FREQUENCY: + case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: + case PhabricatorCalendarEventTransaction::TYPE_INSTANCE_OF_EVENT: + case PhabricatorCalendarEventTransaction::TYPE_SEQUENCE_INDEX: case PhabricatorCalendarEventTransaction::TYPE_NAME: case PhabricatorCalendarEventTransaction::TYPE_START_DATE: case PhabricatorCalendarEventTransaction::TYPE_END_DATE: @@ -181,6 +218,11 @@ final class PhabricatorCalendarEventEditor switch ($xaction->getTransactionType()) { case PhabricatorCalendarEventTransaction::TYPE_ICON: break; + case PhabricatorCalendarEventTransaction::TYPE_RECURRING: + case PhabricatorCalendarEventTransaction::TYPE_FREQUENCY: + case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: + case PhabricatorCalendarEventTransaction::TYPE_INSTANCE_OF_EVENT: + case PhabricatorCalendarEventTransaction::TYPE_SEQUENCE_INDEX: case PhabricatorCalendarEventTransaction::TYPE_START_DATE: case PhabricatorCalendarEventTransaction::TYPE_END_DATE: case PhabricatorCalendarEventTransaction::TYPE_CANCEL: @@ -223,10 +265,20 @@ final class PhabricatorCalendarEventEditor protected function validateAllTransactions( PhabricatorLiskDAO $object, array $xactions) { - $start_date_xaction = PhabricatorCalendarEventTransaction::TYPE_START_DATE; - $end_date_xaction = PhabricatorCalendarEventTransaction::TYPE_END_DATE; + $start_date_xaction = + PhabricatorCalendarEventTransaction::TYPE_START_DATE; + $end_date_xaction = + PhabricatorCalendarEventTransaction::TYPE_END_DATE; + $is_recurrence_xaction = + PhabricatorCalendarEventTransaction::TYPE_RECURRING; + $recurrence_end_xaction = + PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE; + $start_date = $object->getDateFrom(); $end_date = $object->getDateTo(); + $recurrence_end = $object->getRecurrenceEndDate(); + $is_recurring = $object->getIsRecurring(); + $errors = array(); foreach ($xactions as $xaction) { @@ -234,6 +286,10 @@ final class PhabricatorCalendarEventEditor $start_date = $xaction->getNewValue()->getEpoch(); } else if ($xaction->getTransactionType() == $end_date_xaction) { $end_date = $xaction->getNewValue()->getEpoch(); + } else if ($xaction->getTransactionType() == $recurrence_end_xaction) { + $recurrence_end = $xaction->getNewValue(); + } else if ($xaction->getTransactionType() == $is_recurrence_xaction) { + $is_recurring = $xaction->getNewValue(); } } if ($start_date > $end_date) { @@ -245,6 +301,16 @@ final class PhabricatorCalendarEventEditor null); } + if ($recurrence_end && !$is_recurring) { + $type = + PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE; + $errors[] = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht('Event must be recurring to have a recurrence end date.'). + null); + } + return $errors; } @@ -272,6 +338,7 @@ final class PhabricatorCalendarEventEditor $errors[] = $error; } break; + case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: case PhabricatorCalendarEventTransaction::TYPE_START_DATE: case PhabricatorCalendarEventTransaction::TYPE_END_DATE: foreach ($xactions as $xaction) { @@ -303,9 +370,7 @@ final class PhabricatorCalendarEventEditor protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { - - $xactions = mfilter($xactions, 'shouldHide', true); - return $xactions; + return true; } protected function getMailSubjectPrefix() { diff --git a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php index bdc87aaf26..8b4e8837b1 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php @@ -10,6 +10,15 @@ final class PhabricatorCalendarEventQuery private $inviteePHIDs; private $creatorPHIDs; private $isCancelled; + private $eventsWithNoParent; + private $instanceSequencePairs; + + private $generateGhosts = false; + + public function setGenerateGhosts($generate_ghosts) { + $this->generateGhosts = $generate_ghosts; + return $this; + } public function withIDs(array $ids) { $this->ids = $ids; @@ -42,6 +51,16 @@ final class PhabricatorCalendarEventQuery return $this; } + public function withEventsWithNoParent($events_with_no_parent) { + $this->eventsWithNoParent = $events_with_no_parent; + return $this; + } + + public function withInstanceSequencePairs(array $pairs) { + $this->instanceSequencePairs = $pairs; + return $this; + } + protected function getDefaultOrderVector() { return array('start', 'id'); } @@ -69,6 +88,7 @@ final class PhabricatorCalendarEventQuery protected function loadPage() { $table = new PhabricatorCalendarEvent(); $conn_r = $table->establishConnection('r'); + $viewer = $this->getViewer(); $data = queryfx_all( $conn_r, @@ -86,6 +106,131 @@ final class PhabricatorCalendarEventQuery $event->applyViewerTimezone($this->getViewer()); } + if (!$this->generateGhosts) { + return $events; + } + + $enforced_end = null; + + foreach ($events as $key => $event) { + $sequence_start = 0; + $sequence_end = null; + $duration = $event->getDateTo() - $event->getDateFrom(); + $end = null; + + $instance_of = $event->getInstanceOfEventPHID(); + + if ($instance_of == null && $this->isCancelled !== null) { + if ($event->getIsCancelled() != $this->isCancelled) { + unset($events[$key]); + continue; + } + } + + if ($event->getIsRecurring() && $instance_of == null) { + $frequency = $event->getFrequencyUnit(); + $modify_key = '+1 '.$frequency; + + if ($this->rangeBegin && $this->rangeBegin > $event->getDateFrom()) { + $max_date = $this->rangeBegin - $duration; + $date = $event->getDateFrom(); + $datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer); + + while ($date < $max_date) { + // TODO: optimize this to not loop through all off-screen events + $sequence_start++; + $datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer); + $date = $datetime->modify($modify_key)->format('U'); + } + + $start = $this->rangeBegin; + } else { + $start = $event->getDateFrom() - $duration; + } + + $date = $start; + $datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer); + + if (($this->rangeEnd && $event->getRecurrenceEndDate()) && + $this->rangeEnd < $event->getRecurrenceEndDate()) { + $end = $this->rangeEnd; + } else if ($event->getRecurrenceEndDate()) { + $end = $event->getRecurrenceEndDate(); + } else if ($this->rangeEnd) { + $end = $this->rangeEnd; + } else if ($enforced_end) { + if ($end) { + $end = min($end, $enforced_end); + } else { + $end = $enforced_end; + } + } + + if ($end) { + $sequence_end = $sequence_start; + while ($date < $end) { + $sequence_end++; + $datetime->modify($modify_key); + $date = $datetime->format('U'); + if ($sequence_end > $this->getRawResultLimit() + $sequence_start) { + break; + } + } + } else { + $sequence_end = $this->getRawResultLimit() + $sequence_start; + } + + $sequence_start = max(1, $sequence_start); + + for ($index = $sequence_start; $index < $sequence_end; $index++) { + $events[] = $event->generateNthGhost($index, $viewer); + } + + if (count($events) >= $this->getRawResultLimit()) { + $events = msort($events, 'getDateFrom'); + $events = array_slice($events, 0, $this->getRawResultLimit(), true); + $enforced_end = last($events)->getDateFrom(); + } + } + } + + $map = array(); + $instance_sequence_pairs = array(); + + foreach ($events as $key => $event) { + if ($event->getIsGhostEvent()) { + $index = $event->getSequenceIndex(); + $instance_sequence_pairs[] = array($event->getPHID(), $index); + $map[$event->getPHID()][$index] = $key; + } + } + + if (count($instance_sequence_pairs) > 0) { + $sub_query = id(new PhabricatorCalendarEventQuery()) + ->setViewer($viewer) + ->setParentQuery($this) + ->withInstanceSequencePairs($instance_sequence_pairs) + ->execute(); + + foreach ($sub_query as $edited_ghost) { + $indexes = idx($map, $edited_ghost->getInstanceOfEventPHID()); + $key = idx($indexes, $edited_ghost->getSequenceIndex()); + $events[$key] = $edited_ghost; + } + + $id_map = array(); + foreach ($events as $key => $event) { + if ($event->getIsGhostEvent()) { + continue; + } + if (isset($id_map[$event->getID()])) { + unset($events[$key]); + } else { + $id_map[$event->getID()] = true; + } + } + } + return $events; } @@ -122,7 +267,7 @@ final class PhabricatorCalendarEventQuery if ($this->rangeBegin) { $where[] = qsprintf( $conn_r, - 'event.dateTo >= %d', + 'event.dateTo >= %d OR event.isRecurring = 1', $this->rangeBegin); } @@ -154,6 +299,28 @@ final class PhabricatorCalendarEventQuery (int)$this->isCancelled); } + if ($this->eventsWithNoParent == true) { + $where[] = qsprintf( + $conn_r, + 'event.instanceOfEventPHID IS NULL'); + } + + if ($this->instanceSequencePairs !== null) { + $sql = array(); + + foreach ($this->instanceSequencePairs as $pair) { + $sql[] = qsprintf( + $conn_r, + '(event.instanceOfEventPHID = %s AND event.sequenceIndex = %d)', + $pair[0], + $pair[1]); + } + $where[] = qsprintf( + $conn_r, + '%Q', + implode(' OR ', $sql)); + } + $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); @@ -182,6 +349,9 @@ final class PhabricatorCalendarEventQuery protected function willFilterPage(array $events) { $range_start = $this->rangeBegin; $range_end = $this->rangeEnd; + $instance_of_event_phids = array(); + $recurring_events = array(); + $viewer = $this->getViewer(); foreach ($events as $key => $event) { $event_start = $event->getDateFrom(); @@ -199,17 +369,56 @@ final class PhabricatorCalendarEventQuery foreach ($events as $event) { $phids[] = $event->getPHID(); + $instance_of = $event->getInstanceOfEventPHID(); + + if ($instance_of) { + $instance_of_event_phids[] = $instance_of; + } } - $invitees = id(new PhabricatorCalendarEventInviteeQuery()) - ->setViewer($this->getViewer()) - ->withEventPHIDs($phids) - ->execute(); - $invitees = mgroup($invitees, 'getEventPHID'); + if (count($instance_of_event_phids) > 0) { + $recurring_events = id(new PhabricatorCalendarEventQuery()) + ->setViewer($viewer) + ->withPHIDs($instance_of_event_phids) + ->withEventsWithNoParent(true) + ->execute(); - foreach ($events as $event) { + $recurring_events = mpull($recurring_events, null, 'getPHID'); + } + + if ($events) { + $invitees = id(new PhabricatorCalendarEventInviteeQuery()) + ->setViewer($viewer) + ->withEventPHIDs($phids) + ->execute(); + $invitees = mgroup($invitees, 'getEventPHID'); + } else { + $invitees = array(); + } + + foreach ($events as $key => $event) { $event_invitees = idx($invitees, $event->getPHID(), array()); $event->attachInvitees($event_invitees); + + $instance_of = $event->getInstanceOfEventPHID(); + if (!$instance_of) { + continue; + } + $parent = idx($recurring_events, $instance_of); + + // should never get here + if (!$parent) { + unset($events[$key]); + continue; + } + $event->attachParentEvent($parent); + + if ($this->isCancelled !== null) { + if ($event->getIsCancelled() != $this->isCancelled) { + unset($events[$key]); + continue; + } + } } $events = msort($events, 'getDateFrom'); diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index b42b5dcdf8..e0f561b97f 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -50,7 +50,8 @@ final class PhabricatorCalendarEventSearchEngine } public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new PhabricatorCalendarEventQuery()); + $query = id(new PhabricatorCalendarEventQuery()) + ->setGenerateGhosts(true); $viewer = $this->requireViewer(); $timezone = new DateTimeZone($viewer->getTimezoneIdentifier()); @@ -132,7 +133,8 @@ final class PhabricatorCalendarEventSearchEngine $query->withCreatorPHIDs($creator_phids); } - $is_cancelled = $saved->getParameter('isCancelled'); + $is_cancelled = $saved->getParameter('isCancelled', 'active'); + switch ($is_cancelled) { case 'active': $query->withIsCancelled(false); @@ -305,14 +307,13 @@ final class PhabricatorCalendarEventSearchEngine $viewer = $this->requireViewer(); $list = new PHUIObjectItemListView(); foreach ($events as $event) { - $href = '/E'.$event->getID(); $from = phabricator_datetime($event->getDateFrom(), $viewer); $to = phabricator_datetime($event->getDateTo(), $viewer); $creator_handle = $handles[$event->getUserPHID()]; $item = id(new PHUIObjectItemView()) ->setHeader($event->getName()) - ->setHref($href) + ->setHref($event->getURI()) ->addByline(pht('Creator: %s', $creator_handle->renderLink())) ->addAttribute(pht('From %s to %s', $from, $to)) ->addAttribute(id(new PhutilUTF8StringTruncator()) @@ -371,7 +372,7 @@ final class PhabricatorCalendarEventSearchEngine $event->setUserPHID($status->getUserPHID()); $event->setDescription(pht('%s (%s)', $name_text, $status_text)); $event->setName($status_text); - $event->setEventID($status->getID()); + $event->setURI($status->getURI()); $event->setViewerIsInvited($viewer_is_invited); $month_view->addEvent($event); } @@ -423,7 +424,7 @@ final class PhabricatorCalendarEventSearchEngine $event->setViewerIsInvited($viewer_is_invited); $event->setName($status->getName()); - $event->setURI('/'.$status->getMonogram()); + $event->setURI($status->getURI()); $day_view->addEvent($event); } @@ -461,7 +462,11 @@ final class PhabricatorCalendarEventSearchEngine } public function getPageSize(PhabricatorSavedQuery $saved) { - return $saved->getParameter('limit', 1000); + if ($this->isMonthView($saved) || $this->isDayView($saved)) { + return $saved->getParameter('limit', 1000); + } else { + return $saved->getParameter('limit', 100); + } } private function getDateFrom(PhabricatorSavedQuery $saved) { diff --git a/src/applications/calendar/search/PhabricatorCalendarEventSearchIndexer.php b/src/applications/calendar/search/PhabricatorCalendarEventSearchIndexer.php index 696e78accd..95bd85267c 100644 --- a/src/applications/calendar/search/PhabricatorCalendarEventSearchIndexer.php +++ b/src/applications/calendar/search/PhabricatorCalendarEventSearchIndexer.php @@ -18,7 +18,7 @@ final class PhabricatorCalendarEventSearchIndexer $doc->setDocumentModified($event->getDateModified()); $doc->addField( - PhabricatorSearchField::FIELD_BODY, + PhabricatorSearchDocumentFieldType::FIELD_BODY, $event->getDescription()); $doc->addRelationship( diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index cd829369d8..ea6c6a48ef 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -20,11 +20,20 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO protected $icon; protected $mailKey; + protected $isRecurring = 0; + protected $recurrenceFrequency = array(); + protected $recurrenceEndDate; + + private $isGhostEvent = false; + protected $instanceOfEventPHID; + protected $sequenceIndex; + protected $viewPolicy; protected $editPolicy; const DEFAULT_ICON = 'fa-calendar'; + private $parentEvent = self::ATTACHABLE; private $invitees = self::ATTACHABLE; private $appliedViewer; @@ -36,8 +45,13 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO ->withClasses(array('PhabricatorCalendarApplication')) ->executeOne(); + $view_policy = null; + $is_recurring = 0; + if ($mode == 'public') { $view_policy = PhabricatorPolicies::getMostOpenPolicy(); + } else if ($mode == 'recurring') { + $is_recurring = true; } else { $view_policy = $actor->getPHID(); } @@ -46,6 +60,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO ->setUserPHID($actor->getPHID()) ->setIsCancelled(0) ->setIsAllDay(0) + ->setIsRecurring($is_recurring) ->setIcon(self::DEFAULT_ICON) ->setViewPolicy($view_policy) ->setEditPolicy($actor->getPHID()) @@ -180,11 +195,22 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO 'isAllDay' => 'bool', 'icon' => 'text32', 'mailKey' => 'bytes20', + 'isRecurring' => 'bool', + 'recurrenceEndDate' => 'epoch?', + 'instanceOfEventPHID' => 'phid?', + 'sequenceIndex' => 'uint32?', ), self::CONFIG_KEY_SCHEMA => array( 'userPHID_dateFrom' => array( 'columns' => array('userPHID', 'dateTo'), ), + 'key_instance' => array( + 'columns' => array('instanceOfEventPHID', 'sequenceIndex'), + 'unique' => true, + ), + ), + self::CONFIG_SERIALIZATION => array( + 'recurrenceFrequency' => self::SERIALIZATION_JSON, ), ) + parent::getConfiguration(); } @@ -238,6 +264,115 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return true; } + public function getIsGhostEvent() { + return $this->isGhostEvent; + } + + public function setIsGhostEvent($is_ghost_event) { + $this->isGhostEvent = $is_ghost_event; + return $this; + } + + public function generateNthGhost( + $sequence_index, + PhabricatorUser $actor) { + + $frequency = $this->getFrequencyUnit(); + $modify_key = '+'.$sequence_index.' '.$frequency; + + $instance_of = ($this->getPHID()) ? + $this->getPHID() : $this->instanceOfEventPHID; + + $date = $this->dateFrom; + $date_time = PhabricatorTime::getDateTimeFromEpoch($date, $actor); + $date_time->modify($modify_key); + $date = $date_time->format('U'); + + $duration = $this->dateTo - $this->dateFrom; + + $edit_policy = PhabricatorPolicies::POLICY_NOONE; + + $ghost_event = id(clone $this) + ->setIsGhostEvent(true) + ->setDateFrom($date) + ->setDateTo($date + $duration) + ->setIsRecurring(true) + ->setRecurrenceFrequency($this->recurrenceFrequency) + ->setInstanceOfEventPHID($instance_of) + ->setSequenceIndex($sequence_index) + ->setEditPolicy($edit_policy); + + return $ghost_event; + } + + public function getFrequencyUnit() { + $frequency = idx($this->recurrenceFrequency, 'rule'); + + switch ($frequency) { + case 'daily': + return 'day'; + case 'weekly': + return 'week'; + case 'monthly': + return 'month'; + case 'yearly': + return 'yearly'; + default: + return 'day'; + } + } + + public function getURI() { + $uri = '/'.$this->getMonogram(); + if ($this->isGhostEvent) { + $uri = $uri.'/'.$this->sequenceIndex; + } + return $uri; + } + + public function getParentEvent() { + return $this->assertAttached($this->parentEvent); + } + + public function attachParentEvent($event) { + $this->parentEvent = $event; + return $this; + } + + public function getIsCancelled() { + $instance_of = $this->instanceOfEventPHID; + if ($instance_of != null && $this->getIsParentCancelled()) { + return true; + } + return $this->isCancelled; + } + + public function getIsRecurrenceParent() { + if ($this->isRecurring && !$this->instanceOfEventPHID) { + return true; + } + return false; + } + + public function getIsRecurrenceException() { + if ($this->instanceOfEventPHID && !$this->isGhostEvent) { + return true; + } + return false; + } + + public function getIsParentCancelled() { + if ($this->instanceOfEventPHID == null) { + return false; + } + + $recurring_event = $this->getParentEvent(); + if ($recurring_event->getIsCancelled()) { + return true; + } + return false; + } + /* -( Markup Interface )--------------------------------------------------- */ @@ -328,7 +463,8 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO public function describeAutomaticCapability($capability) { return pht('The owner of an event can always view and edit it, - and invitees can always view it.'); + and invitees can always view it, except if the event is an + instance of a recurring event.'); } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php index 767208648e..2d24281562 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php @@ -12,6 +12,13 @@ final class PhabricatorCalendarEventTransaction const TYPE_ICON = 'calendar.icon'; const TYPE_INVITE = 'calendar.invite'; + const TYPE_RECURRING = 'calendar.recurring'; + const TYPE_FREQUENCY = 'calendar.frequency'; + const TYPE_RECURRENCE_END_DATE = 'calendar.recurrenceenddate'; + + const TYPE_INSTANCE_OF_EVENT = 'calendar.instanceofevent'; + const TYPE_SEQUENCE_INDEX = 'calendar.sequenceindex'; + const MAILTAG_RESCHEDULE = 'calendar-reschedule'; const MAILTAG_CONTENT = 'calendar-content'; const MAILTAG_OTHER = 'calendar-other'; @@ -38,6 +45,11 @@ final class PhabricatorCalendarEventTransaction case self::TYPE_DESCRIPTION: case self::TYPE_CANCEL: case self::TYPE_ALL_DAY: + case self::TYPE_RECURRING: + case self::TYPE_FREQUENCY: + case self::TYPE_RECURRENCE_END_DATE: + case self::TYPE_INSTANCE_OF_EVENT: + case self::TYPE_SEQUENCE_INDEX: $phids[] = $this->getObjectPHID(); break; case self::TYPE_INVITE: @@ -60,6 +72,11 @@ final class PhabricatorCalendarEventTransaction case self::TYPE_CANCEL: case self::TYPE_ALL_DAY: case self::TYPE_INVITE: + case self::TYPE_RECURRING: + case self::TYPE_FREQUENCY: + case self::TYPE_RECURRENCE_END_DATE: + case self::TYPE_INSTANCE_OF_EVENT: + case self::TYPE_SEQUENCE_INDEX: return ($old === null); } return parent::shouldHide(); @@ -75,6 +92,11 @@ final class PhabricatorCalendarEventTransaction case self::TYPE_DESCRIPTION: case self::TYPE_ALL_DAY: case self::TYPE_CANCEL: + case self::TYPE_RECURRING: + case self::TYPE_FREQUENCY: + case self::TYPE_RECURRENCE_END_DATE: + case self::TYPE_INSTANCE_OF_EVENT: + case self::TYPE_SEQUENCE_INDEX: return 'fa-pencil'; break; case self::TYPE_INVITE: @@ -231,6 +253,12 @@ final class PhabricatorCalendarEventTransaction } } return $text; + case self::TYPE_RECURRING: + case self::TYPE_FREQUENCY: + case self::TYPE_RECURRENCE_END_DATE: + case self::TYPE_INSTANCE_OF_EVENT: + case self::TYPE_SEQUENCE_INDEX: + return pht('Recurring event has been updated'); } return parent::getTitle(); } @@ -365,8 +393,6 @@ final class PhabricatorCalendarEventTransaction $added = array(); $uninvited = array(); - // $event = $this->renderHandleLink($object_phid); - foreach ($new as $phid => $status) { if ($status == PhabricatorCalendarEventInvitee::STATUS_INVITED || $status == PhabricatorCalendarEventInvitee::STATUS_ATTENDING) { @@ -411,6 +437,12 @@ final class PhabricatorCalendarEventTransaction } } return $text; + case self::TYPE_RECURRING: + case self::TYPE_FREQUENCY: + case self::TYPE_RECURRENCE_END_DATE: + case self::TYPE_INSTANCE_OF_EVENT: + case self::TYPE_SEQUENCE_INDEX: + return pht('Recurring event has been updated'); } return parent::getTitleForFeed(); diff --git a/src/applications/celerity/controller/CelerityResourceController.php b/src/applications/celerity/controller/CelerityResourceController.php index 717e20f9f9..d369981990 100644 --- a/src/applications/celerity/controller/CelerityResourceController.php +++ b/src/applications/celerity/controller/CelerityResourceController.php @@ -165,7 +165,7 @@ abstract class CelerityResourceController extends PhabricatorController { } private function getCacheKey($path) { - return 'celerity:'.$path; + return 'celerity:'.PhabricatorHash::digestToLength($path, 64); } } diff --git a/src/applications/conduit/controller/PhabricatorConduitAPIController.php b/src/applications/conduit/controller/PhabricatorConduitAPIController.php index 8b7080075a..41106d110b 100644 --- a/src/applications/conduit/controller/PhabricatorConduitAPIController.php +++ b/src/applications/conduit/controller/PhabricatorConduitAPIController.php @@ -60,10 +60,6 @@ final class PhabricatorConduitAPIController // CSRF validation or are using a non-web authentication mechanism. $allow_unguarded_writes = true; - if (isset($metadata['actAsUser'])) { - $this->actAsUser($api_request, $metadata['actAsUser']); - } - if ($auth_error === null) { $conduit_user = $api_request->getUser(); if ($conduit_user && $conduit_user->getPHID()) { @@ -163,44 +159,6 @@ final class PhabricatorConduitAPIController } } - /** - * Change the api request user to the user that we want to act as. - * Only admins can use actAsUser - * - * @param ConduitAPIRequest Request being executed. - * @param string The username of the user we want to act as - */ - private function actAsUser( - ConduitAPIRequest $api_request, - $user_name) { - - $config_key = 'security.allow-conduit-act-as-user'; - if (!PhabricatorEnv::getEnvConfig($config_key)) { - throw new Exception(pht('%s is disabled.', $config_key)); - } - - if (!$api_request->getUser()->getIsAdmin()) { - throw new Exception( - pht( - 'Only administrators can use %s.', - __FUNCTION__)); - } - - $user = id(new PhabricatorUser())->loadOneWhere( - 'userName = %s', - $user_name); - - if (!$user) { - throw new Exception( - pht( - "The %s username '%s' is not a valid user.", - __FUNCTION__, - $user_name)); - } - - $api_request->setUser($user); - } - /** * Authenticate the client making the request to a Phabricator user account. * @@ -517,10 +475,10 @@ final class PhabricatorConduitAPIController ConduitAPIRequest $request, PhabricatorUser $user) { - if (!$user->isUserActivated()) { + if (!$user->canEstablishAPISessions()) { return array( - 'ERR-USER-DISABLED', - pht('User account is not activated.'), + 'ERR-INVALID-AUTH', + pht('User account is not permitted to use the API.'), ); } diff --git a/src/applications/conduit/method/ConduitAPIMethod.php b/src/applications/conduit/method/ConduitAPIMethod.php index 0643ca72d1..8b9a67f4be 100644 --- a/src/applications/conduit/method/ConduitAPIMethod.php +++ b/src/applications/conduit/method/ConduitAPIMethod.php @@ -90,7 +90,7 @@ abstract class ConduitAPIMethod return $this->execute($request); } - public abstract function getAPIMethodName(); + abstract public function getAPIMethodName(); /** * Return a key which sorts methods by application name, then method status, diff --git a/src/applications/conduit/settings/PhabricatorConduitTokensSettingsPanel.php b/src/applications/conduit/settings/PhabricatorConduitTokensSettingsPanel.php index faae8a8913..b41e689d37 100644 --- a/src/applications/conduit/settings/PhabricatorConduitTokensSettingsPanel.php +++ b/src/applications/conduit/settings/PhabricatorConduitTokensSettingsPanel.php @@ -20,6 +20,10 @@ final class PhabricatorConduitTokensSettingsPanel } public function isEnabled() { + if ($this->getUser()->getIsMailingList()) { + return false; + } + return true; } diff --git a/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php b/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php index b3c294b3e6..9301329394 100644 --- a/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php +++ b/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php @@ -271,6 +271,9 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck { 'metamta.maniphest.public-create-email' => $public_mail_reason, 'metamta.maniphest.default-public-author' => $public_mail_reason, 'metamta.paste.public-create-email' => $public_mail_reason, + + 'security.allow-conduit-act-as-user' => pht( + 'Impersonating users over the API is no longer supported.'), ); return $ancient_config; diff --git a/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php b/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php index 55c8c5689c..929f02be2d 100644 --- a/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php +++ b/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php @@ -54,6 +54,8 @@ alincoln", "To: usgrant", "To: htaft"). The major advantages and disadvantages of each approach are: - One mail to everyone: + - This violates policy controls. The body of the mail is generated without + respect for object policies. - Recipients can see To/Cc at a glance. - If you use mailing lists, you won't get duplicate mail if you're a normal recipient and also Cc'd on a mailing list. @@ -65,6 +67,7 @@ of each approach are: - Not supported with a private reply-to address. - Mails are sent in the server default translation. - One mail to each user: + - Policy controls work correctly and are enforced per-user. - Recipients need to look in the mail body to see To/Cc. - If you use mailing lists, recipients may sometimes get duplicate mail. @@ -74,8 +77,6 @@ of each approach are: - Required if private reply-to addresses are configured. - Mails are sent in the language of user preference. -In the code, splitting one outbound email into one-per-recipient is sometimes -referred to as "multiplexing". EODOC )); @@ -222,6 +223,7 @@ EODOC 'metamta.one-mail-per-recipient', 'bool', true) + ->setLocked(true) ->setBoolOptions( array( pht('Send Mail To Each Recipient'), diff --git a/src/applications/config/option/PhabricatorSecurityConfigOptions.php b/src/applications/config/option/PhabricatorSecurityConfigOptions.php index 63e43b3081..b8ff3ccc48 100644 --- a/src/applications/config/option/PhabricatorSecurityConfigOptions.php +++ b/src/applications/config/option/PhabricatorSecurityConfigOptions.php @@ -278,22 +278,6 @@ final class PhabricatorSecurityConfigOptions 'unsecured content over plain HTTP. It is very difficult to '. 'undo this change once users\' browsers have accepted the '. 'setting.')), - $this->newOption('security.allow-conduit-act-as-user', 'bool', false) - ->setBoolOptions( - array( - pht('Allow'), - pht('Disallow'), - )) - ->setLocked(true) - ->setSummary( - pht('Allow administrators to use the Conduit API as other users.')) - ->setDescription( - pht( - 'DEPRECATED - if you enable this, you are allowing '. - 'administrators to act as any user via the Conduit API. '. - 'Enabling this is not advised as it introduces a huge policy '. - 'violation and has been obsoleted in functionality.')), - ); } diff --git a/src/applications/conpherence/ConpherenceTransactionRenderer.php b/src/applications/conpherence/ConpherenceTransactionRenderer.php index 1b14f5f0d6..65bfacb3c9 100644 --- a/src/applications/conpherence/ConpherenceTransactionRenderer.php +++ b/src/applications/conpherence/ConpherenceTransactionRenderer.php @@ -61,7 +61,7 @@ final class ConpherenceTransactionRenderer { // between days. some setup required! $previous_transaction = null; $date_marker_transaction = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransactionType::TYPE_DATE_MARKER) + ->setTransactionType(ConpherenceTransaction::TYPE_DATE_MARKER) ->makeEphemeral(); $date_marker_transaction_view = id(new ConpherenceTransactionView()) ->setUser($user) @@ -74,7 +74,8 @@ final class ConpherenceTransactionRenderer { ->setUser($user) ->setConpherenceThread($conpherence) ->setHandles($handles) - ->setMarkupEngine($engine); + ->setMarkupEngine($engine) + ->setFullDisplay($full_display); foreach ($transactions as $transaction) { if ($previous_transaction) { @@ -96,21 +97,6 @@ final class ConpherenceTransactionRenderer { } $transaction_view = id(clone $transaction_view_template) ->setConpherenceTransaction($transaction); - if ($full_display) { - $transaction_view - ->setAnchor( - $transaction->getID(), - phabricator_time($transaction->getDateCreated(), $user)); - $transaction_view->setContentSource($transaction->getContentSource()); - $transaction_view->setShowImages(true); - } else { - $transaction_view - ->setEpoch( - $transaction->getDateCreated(), - '/'.$conpherence->getMonogram().'#'.$transaction->getID()) - ->setTimeOnly(true); - $transaction_view->setShowImages(false); - } $rendered_transactions[] = $transaction_view->render(); $previous_transaction = $transaction; diff --git a/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php b/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php index d5e6caa322..4c66a48348 100644 --- a/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php +++ b/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php @@ -157,11 +157,11 @@ final class ConpherenceRoomTestCase extends ConpherenceTestCase { $xactions = array(); $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransactionType::TYPE_PARTICIPANTS) + ->setTransactionType(ConpherenceTransaction::TYPE_PARTICIPANTS) ->setNewValue(array('+' => $participant_phids)); $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransactionType::TYPE_TITLE) - ->setNewValue('Test'); + ->setTransactionType(ConpherenceTransaction::TYPE_TITLE) + ->setNewValue(pht('Test')); id(new ConpherenceEditor()) ->setActor($creator) diff --git a/src/applications/conpherence/__tests__/ConpherenceTestCase.php b/src/applications/conpherence/__tests__/ConpherenceTestCase.php index 97852755ae..8de135f24e 100644 --- a/src/applications/conpherence/__tests__/ConpherenceTestCase.php +++ b/src/applications/conpherence/__tests__/ConpherenceTestCase.php @@ -8,7 +8,7 @@ abstract class ConpherenceTestCase extends PhabricatorTestCase { array $participant_phids) { $xactions = array(id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransactionType::TYPE_PARTICIPANTS) + ->setTransactionType(ConpherenceTransaction::TYPE_PARTICIPANTS) ->setNewValue(array('+' => $participant_phids)), ); $editor = id(new ConpherenceEditor()) @@ -24,7 +24,7 @@ abstract class ConpherenceTestCase extends PhabricatorTestCase { array $participant_phids) { $xactions = array(id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransactionType::TYPE_PARTICIPANTS) + ->setTransactionType(ConpherenceTransaction::TYPE_PARTICIPANTS) ->setNewValue(array('-' => $participant_phids)), ); $editor = id(new ConpherenceEditor()) diff --git a/src/applications/conpherence/__tests__/ConpherenceThreadTestCase.php b/src/applications/conpherence/__tests__/ConpherenceThreadTestCase.php index 1f60f1e149..3013e998fa 100644 --- a/src/applications/conpherence/__tests__/ConpherenceThreadTestCase.php +++ b/src/applications/conpherence/__tests__/ConpherenceThreadTestCase.php @@ -160,8 +160,8 @@ final class ConpherenceThreadTestCase extends ConpherenceTestCase { list($errors, $conpherence) = ConpherenceEditor::createThread( $creator, $participant_phids, - 'Test', - 'Test', + pht('Test'), + pht('Test'), PhabricatorContentSource::newConsoleSource()); return $conpherence; } diff --git a/src/applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php b/src/applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php index e183d44699..66bbdab7e3 100644 --- a/src/applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php +++ b/src/applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php @@ -71,7 +71,7 @@ final class ConpherenceUpdateThreadConduitAPIMethod if ($add_participant_phids) { $xactions[] = id(new ConpherenceTransaction()) ->setTransactionType( - ConpherenceTransactionType::TYPE_PARTICIPANTS) + ConpherenceTransaction::TYPE_PARTICIPANTS) ->setNewValue(array('+' => $add_participant_phids)); } if ($remove_participant_phid) { @@ -80,12 +80,12 @@ final class ConpherenceUpdateThreadConduitAPIMethod } $xactions[] = id(new ConpherenceTransaction()) ->setTransactionType( - ConpherenceTransactionType::TYPE_PARTICIPANTS) + ConpherenceTransaction::TYPE_PARTICIPANTS) ->setNewValue(array('-' => array($remove_participant_phid))); } if ($title) { $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransactionType::TYPE_TITLE) + ->setTransactionType(ConpherenceTransaction::TYPE_TITLE) ->setNewValue($title); } if ($message) { diff --git a/src/applications/conpherence/constants/ConpherenceTransactionType.php b/src/applications/conpherence/constants/ConpherenceTransactionType.php deleted file mode 100644 index b0ebe44df8..0000000000 --- a/src/applications/conpherence/constants/ConpherenceTransactionType.php +++ /dev/null @@ -1,12 +0,0 @@ -setTransactionType(ConpherenceTransactionType::TYPE_PARTICIPANTS) + ->setTransactionType(ConpherenceTransaction::TYPE_PARTICIPANTS) ->setNewValue(array('+' => array($user->getPHID()))); $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransactionType::TYPE_TITLE) + ->setTransactionType(ConpherenceTransaction::TYPE_TITLE) ->setNewValue($request->getStr('title')); $xactions[] = id(new ConpherenceTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) @@ -41,7 +41,7 @@ final class ConpherenceNewRoomController extends ConpherenceController { } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; - $e_title = $ex->getShortMessage(ConpherenceTransactionType::TYPE_TITLE); + $e_title = $ex->getShortMessage(ConpherenceTransaction::TYPE_TITLE); $conpherence->setViewPolicy($request->getStr('viewPolicy')); $conpherence->setEditPolicy($request->getStr('editPolicy')); diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php index 57df50c828..c88aee23d7 100644 --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -67,7 +67,7 @@ final class ConpherenceUpdateController case ConpherenceUpdateActions::JOIN_ROOM: $xactions[] = id(new ConpherenceTransaction()) ->setTransactionType( - ConpherenceTransactionType::TYPE_PARTICIPANTS) + ConpherenceTransaction::TYPE_PARTICIPANTS) ->setNewValue(array('+' => array($user->getPHID()))); $delete_draft = true; $message = $request->getStr('text'); @@ -101,7 +101,7 @@ final class ConpherenceUpdateController if (!empty($person_phids)) { $xactions[] = id(new ConpherenceTransaction()) ->setTransactionType( - ConpherenceTransactionType::TYPE_PARTICIPANTS) + ConpherenceTransaction::TYPE_PARTICIPANTS) ->setNewValue(array('+' => $person_phids)); } break; @@ -114,7 +114,7 @@ final class ConpherenceUpdateController if ($person_phid && $person_phid == $user->getPHID()) { $xactions[] = id(new ConpherenceTransaction()) ->setTransactionType( - ConpherenceTransactionType::TYPE_PARTICIPANTS) + ConpherenceTransaction::TYPE_PARTICIPANTS) ->setNewValue(array('-' => array($person_phid))); $response_mode = 'go-home'; } @@ -144,7 +144,7 @@ final class ConpherenceUpdateController ->withIDs(array($file_id)) ->executeOne(); $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransactionType::TYPE_PICTURE) + ->setTransactionType(ConpherenceTransaction::TYPE_PICTURE) ->setNewValue($orig_file); $okay = $orig_file->isTransformableImage(); if ($okay) { @@ -157,7 +157,7 @@ final class ConpherenceUpdateController ConpherenceImageData::CROP_HEIGHT); $xactions[] = id(new ConpherenceTransaction()) ->setTransactionType( - ConpherenceTransactionType::TYPE_PICTURE_CROP) + ConpherenceTransaction::TYPE_PICTURE_CROP) ->setNewValue($crop_file->getPHID()); } $response_mode = 'redirect'; @@ -181,12 +181,12 @@ final class ConpherenceUpdateController $xactions[] = id(new ConpherenceTransaction()) ->setTransactionType( - ConpherenceTransactionType::TYPE_PICTURE_CROP) + ConpherenceTransaction::TYPE_PICTURE_CROP) ->setNewValue($image_phid); } $title = $request->getStr('title'); $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransactionType::TYPE_TITLE) + ->setTransactionType(ConpherenceTransaction::TYPE_TITLE) ->setNewValue($title); if ($conpherence->getIsRoom()) { $xactions[] = id(new ConpherenceTransaction()) diff --git a/src/applications/conpherence/editor/ConpherenceEditor.php b/src/applications/conpherence/editor/ConpherenceEditor.php index d44ebf912b..c9445216fe 100644 --- a/src/applications/conpherence/editor/ConpherenceEditor.php +++ b/src/applications/conpherence/editor/ConpherenceEditor.php @@ -47,16 +47,16 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { if (!$errors) { $xactions = array(); $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransactionType::TYPE_PARTICIPANTS) + ->setTransactionType(ConpherenceTransaction::TYPE_PARTICIPANTS) ->setNewValue(array('+' => $participant_phids)); if ($files) { $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransactionType::TYPE_FILES) + ->setTransactionType(ConpherenceTransaction::TYPE_FILES) ->setNewValue(array('+' => mpull($files, 'getPHID'))); } if ($title) { $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransactionType::TYPE_TITLE) + ->setTransactionType(ConpherenceTransaction::TYPE_TITLE) ->setNewValue($title); } @@ -100,7 +100,7 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { $xactions = array(); if ($files) { $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransactionType::TYPE_FILES) + ->setTransactionType(ConpherenceTransaction::TYPE_FILES) ->setNewValue(array('+' => mpull($files, 'getPHID'))); } $xactions[] = id(new ConpherenceTransaction()) @@ -117,11 +117,11 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { $types[] = PhabricatorTransactions::TYPE_COMMENT; - $types[] = ConpherenceTransactionType::TYPE_TITLE; - $types[] = ConpherenceTransactionType::TYPE_PARTICIPANTS; - $types[] = ConpherenceTransactionType::TYPE_FILES; - $types[] = ConpherenceTransactionType::TYPE_PICTURE; - $types[] = ConpherenceTransactionType::TYPE_PICTURE_CROP; + $types[] = ConpherenceTransaction::TYPE_TITLE; + $types[] = ConpherenceTransaction::TYPE_PARTICIPANTS; + $types[] = ConpherenceTransaction::TYPE_FILES; + $types[] = ConpherenceTransaction::TYPE_PICTURE; + $types[] = ConpherenceTransaction::TYPE_PICTURE_CROP; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; $types[] = PhabricatorTransactions::TYPE_JOIN_POLICY; @@ -134,18 +134,18 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case ConpherenceTransactionType::TYPE_TITLE: + case ConpherenceTransaction::TYPE_TITLE: return $object->getTitle(); - case ConpherenceTransactionType::TYPE_PICTURE: + case ConpherenceTransaction::TYPE_PICTURE: return $object->getImagePHID(ConpherenceImageData::SIZE_ORIG); - case ConpherenceTransactionType::TYPE_PICTURE_CROP: + case ConpherenceTransaction::TYPE_PICTURE_CROP: return $object->getImagePHID(ConpherenceImageData::SIZE_CROP); - case ConpherenceTransactionType::TYPE_PARTICIPANTS: + case ConpherenceTransaction::TYPE_PARTICIPANTS: if ($this->getIsNewObject()) { return array(); } return $object->getParticipantPHIDs(); - case ConpherenceTransactionType::TYPE_FILES: + case ConpherenceTransaction::TYPE_FILES: return $object->getFilePHIDs(); } } @@ -155,14 +155,14 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case ConpherenceTransactionType::TYPE_TITLE: - case ConpherenceTransactionType::TYPE_PICTURE_CROP: + case ConpherenceTransaction::TYPE_TITLE: + case ConpherenceTransaction::TYPE_PICTURE_CROP: return $xaction->getNewValue(); - case ConpherenceTransactionType::TYPE_PICTURE: + case ConpherenceTransaction::TYPE_PICTURE: $file = $xaction->getNewValue(); return $file->getPHID(); - case ConpherenceTransactionType::TYPE_PARTICIPANTS: - case ConpherenceTransactionType::TYPE_FILES: + case ConpherenceTransaction::TYPE_PARTICIPANTS: + case ConpherenceTransaction::TYPE_FILES: return $this->getPHIDTransactionNewValue($xaction); } } @@ -207,7 +207,7 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case ConpherenceTransactionType::TYPE_PARTICIPANTS: + case ConpherenceTransaction::TYPE_PARTICIPANTS: // Since this is a new ConpherenceThread, we have to create the // participation data asap to pass policy checks. For existing // ConpherenceThreads, the existing participation is correct @@ -247,20 +247,20 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { $make_author_recent_participant = true; switch ($xaction->getTransactionType()) { - case ConpherenceTransactionType::TYPE_TITLE: + case ConpherenceTransaction::TYPE_TITLE: $object->setTitle($xaction->getNewValue()); break; - case ConpherenceTransactionType::TYPE_PICTURE: + case ConpherenceTransaction::TYPE_PICTURE: $object->setImagePHID( $xaction->getNewValue(), ConpherenceImageData::SIZE_ORIG); break; - case ConpherenceTransactionType::TYPE_PICTURE_CROP: + case ConpherenceTransaction::TYPE_PICTURE_CROP: $object->setImagePHID( $xaction->getNewValue(), ConpherenceImageData::SIZE_CROP); break; - case ConpherenceTransactionType::TYPE_PARTICIPANTS: + case ConpherenceTransaction::TYPE_PARTICIPANTS: if (!$this->getIsNewObject()) { $old_map = array_fuse($xaction->getOldValue()); $new_map = array_fuse($xaction->getNewValue()); @@ -322,7 +322,7 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case ConpherenceTransactionType::TYPE_FILES: + case ConpherenceTransaction::TYPE_FILES: $editor = new PhabricatorEdgeEditor(); $edge_type = PhabricatorObjectHasFileEdgeType::EDGECONST; $old = array_fill_keys($xaction->getOldValue(), true); @@ -343,7 +343,7 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { } $editor->save(); break; - case ConpherenceTransactionType::TYPE_PARTICIPANTS: + case ConpherenceTransaction::TYPE_PARTICIPANTS: if ($this->getIsNewObject()) { continue; } @@ -443,7 +443,7 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { parent::requireCapabilities($object, $xaction); switch ($xaction->getTransactionType()) { - case ConpherenceTransactionType::TYPE_PARTICIPANTS: + case ConpherenceTransaction::TYPE_PARTICIPANTS: $old_map = array_fuse($xaction->getOldValue()); $new_map = array_fuse($xaction->getNewValue()); @@ -473,13 +473,13 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { break; // This is similar to PhabricatorTransactions::TYPE_COMMENT so // use CAN_VIEW - case ConpherenceTransactionType::TYPE_FILES: + case ConpherenceTransaction::TYPE_FILES: PhabricatorPolicyFilter::requireCapability( $this->requireActor(), $object, PhabricatorPolicyCapability::CAN_VIEW); break; - case ConpherenceTransactionType::TYPE_TITLE: + case ConpherenceTransaction::TYPE_TITLE: PhabricatorPolicyFilter::requireCapability( $this->requireActor(), $object, @@ -494,10 +494,10 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { $type = $u->getTransactionType(); switch ($type) { - case ConpherenceTransactionType::TYPE_TITLE: + case ConpherenceTransaction::TYPE_TITLE: return $v; - case ConpherenceTransactionType::TYPE_FILES: - case ConpherenceTransactionType::TYPE_PARTICIPANTS: + case ConpherenceTransaction::TYPE_FILES: + case ConpherenceTransaction::TYPE_PARTICIPANTS: return $this->mergePHIDOrEdgeTransactions($u, $v); } @@ -576,6 +576,21 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { return $body; } + protected function addEmailPreferenceSectionToMailBody( + PhabricatorMetaMTAMailBody $body, + PhabricatorLiskDAO $object, + array $xactions) { + + $href = PhabricatorEnv::getProductionURI( + '/'.$object->getMonogram().'?settings'); + if ($object->getIsRoom()) { + $label = pht('EMAIL PREFERENCES FOR THIS ROOM'); + } else { + $label = pht('EMAIL PREFERENCES FOR THIS MESSAGE'); + } + $body->addLinkSection($label, $href); + } + protected function getMailSubjectPrefix() { return PhabricatorEnv::getEnvConfig('metamta.conpherence.subject-prefix'); } @@ -611,9 +626,9 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case ConpherenceTransactionType::TYPE_PICTURE: + case ConpherenceTransaction::TYPE_PICTURE: return array($xaction->getNewValue()->getPHID()); - case ConpherenceTransactionType::TYPE_PICTURE_CROP: + case ConpherenceTransaction::TYPE_PICTURE_CROP: return array($xaction->getNewValue()); } @@ -628,7 +643,7 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { $errors = parent::validateTransaction($object, $type, $xactions); switch ($type) { - case ConpherenceTransactionType::TYPE_TITLE: + case ConpherenceTransaction::TYPE_TITLE: if (!$object->getIsRoom()) { continue; } @@ -652,7 +667,7 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { $errors[] = $error; } break; - case ConpherenceTransactionType::TYPE_PICTURE: + case ConpherenceTransaction::TYPE_PICTURE: foreach ($xactions as $xaction) { $file = $xaction->getNewValue(); if (!$file->isTransformableImage()) { @@ -667,7 +682,7 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { } } break; - case ConpherenceTransactionType::TYPE_PARTICIPANTS: + case ConpherenceTransaction::TYPE_PARTICIPANTS: foreach ($xactions as $xaction) { $new_phids = $this->getPHIDTransactionNewValue($xaction, array()); $old_phids = nonempty($object->getParticipantPHIDs(), array()); diff --git a/src/applications/conpherence/mail/ConpherenceReplyHandler.php b/src/applications/conpherence/mail/ConpherenceReplyHandler.php index 61f925e150..7ec5d7f445 100644 --- a/src/applications/conpherence/mail/ConpherenceReplyHandler.php +++ b/src/applications/conpherence/mail/ConpherenceReplyHandler.php @@ -21,9 +21,8 @@ final class ConpherenceReplyHandler extends PhabricatorMailReplyHandler { } } - public function getPrivateReplyHandlerEmailAddress( - PhabricatorObjectHandle $handle) { - return $this->getDefaultPrivateReplyHandlerEmailAddress($handle, 'Z'); + public function getPrivateReplyHandlerEmailAddress(PhabricatorUser $user) { + return $this->getDefaultPrivateReplyHandlerEmailAddress($user, 'Z'); } public function getPublicReplyHandlerEmailAddress() { @@ -66,7 +65,7 @@ final class ConpherenceReplyHandler extends PhabricatorMailReplyHandler { $xactions = array(); if ($this->getMailAddedParticipantPHIDs()) { $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransactionType::TYPE_PARTICIPANTS) + ->setTransactionType(ConpherenceTransaction::TYPE_PARTICIPANTS) ->setNewValue(array('+' => $this->getMailAddedParticipantPHIDs())); } diff --git a/src/applications/conpherence/phid/PhabricatorConpherenceThreadPHIDType.php b/src/applications/conpherence/phid/PhabricatorConpherenceThreadPHIDType.php index ccc2756557..81a5a49043 100644 --- a/src/applications/conpherence/phid/PhabricatorConpherenceThreadPHIDType.php +++ b/src/applications/conpherence/phid/PhabricatorConpherenceThreadPHIDType.php @@ -19,9 +19,7 @@ final class PhabricatorConpherenceThreadPHIDType extends PhabricatorPHIDType { protected function buildQueryForObjects( PhabricatorObjectQuery $query, array $phids) { - return id(new ConpherenceThreadQuery()) - ->needParticipantCache(true) ->withPHIDs($phids); } @@ -33,7 +31,7 @@ final class PhabricatorConpherenceThreadPHIDType extends PhabricatorPHIDType { foreach ($handles as $phid => $handle) { $thread = $objects[$phid]; - $title = $thread->getDisplayTitle($query->getViewer()); + $title = $thread->getStaticTitle(); $monogram = $thread->getMonogram(); $handle->setName($title); diff --git a/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php b/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php index f2a2d920bd..d647248c87 100644 --- a/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php +++ b/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php @@ -227,15 +227,13 @@ final class ConpherenceThreadSearchEngine continue; } - $history_href = '/'.$monogram.'#'.$xaction->getID(); - $view = id(new ConpherenceTransactionView()) ->setUser($viewer) ->setHandles($handles) ->setMarkupEngine($engines[$conpherence_phid]) ->setConpherenceThread($conpherence) ->setConpherenceTransaction($xaction) - ->setEpoch($xaction->getDateCreated(), $history_href) + ->setFullDisplay(false) ->addClass('conpherence-fulltext-result'); if ($message['match']) { diff --git a/src/applications/conpherence/storage/ConpherenceThread.php b/src/applications/conpherence/storage/ConpherenceThread.php index 36654713d5..2600e719e7 100644 --- a/src/applications/conpherence/storage/ConpherenceThread.php +++ b/src/applications/conpherence/storage/ConpherenceThread.php @@ -199,6 +199,23 @@ final class ConpherenceThread extends ConpherenceDAO return PhabricatorUser::getDefaultProfileImageURI(); } + /** + * Get a thread title which doesn't require handles to be attached. + * + * This is a less rich title than @{method:getDisplayTitle}, but does not + * require handles to be attached. We use it to build thread handles without + * risking cycles or recursion while querying. + * + * @return string Lower quality human-readable title. + */ + public function getStaticTitle() { + $title = $this->getTitle(); + if (strlen($title)) { + return $title; + } + + return pht('Private Correspondence'); + } /** * Get the thread's display title for a user. diff --git a/src/applications/conpherence/storage/ConpherenceTransaction.php b/src/applications/conpherence/storage/ConpherenceTransaction.php index 3a0a6abb61..d4af549d37 100644 --- a/src/applications/conpherence/storage/ConpherenceTransaction.php +++ b/src/applications/conpherence/storage/ConpherenceTransaction.php @@ -2,6 +2,13 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction { + const TYPE_FILES = 'files'; + const TYPE_TITLE = 'title'; + const TYPE_PARTICIPANTS = 'participants'; + const TYPE_DATE_MARKER = 'date-marker'; + const TYPE_PICTURE = 'picture'; + const TYPE_PICTURE_CROP = 'picture-crop'; + public function getApplicationName() { return 'conpherence'; } @@ -16,7 +23,7 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction { public function getNoEffectDescription() { switch ($this->getTransactionType()) { - case ConpherenceTransactionType::TYPE_PARTICIPANTS: + case self::TYPE_PARTICIPANTS: return pht( 'You can not add a participant who has already been added.'); break; @@ -29,15 +36,15 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction { $old = $this->getOldValue(); switch ($this->getTransactionType()) { - case ConpherenceTransactionType::TYPE_PARTICIPANTS: + case self::TYPE_PARTICIPANTS: return ($old === null); - case ConpherenceTransactionType::TYPE_TITLE: - case ConpherenceTransactionType::TYPE_PICTURE: - case ConpherenceTransactionType::TYPE_DATE_MARKER: + case self::TYPE_TITLE: + case self::TYPE_PICTURE: + case self::TYPE_DATE_MARKER: return false; - case ConpherenceTransactionType::TYPE_FILES: + case self::TYPE_FILES: return true; - case ConpherenceTransactionType::TYPE_PICTURE_CROP: + case self::TYPE_PICTURE_CROP: return true; } @@ -51,18 +58,18 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction { $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case ConpherenceTransactionType::TYPE_TITLE: + case self::TYPE_TITLE: case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: case PhabricatorTransactions::TYPE_JOIN_POLICY: - case ConpherenceTransactionType::TYPE_PICTURE: + case self::TYPE_PICTURE: if ($this->getObject()->getIsRoom()) { return $this->getRoomTitle(); } else { return $this->getThreadTitle(); } break; - case ConpherenceTransactionType::TYPE_FILES: + case self::TYPE_FILES: $add = array_diff($new, $old); $rem = array_diff($old, $new); @@ -85,7 +92,7 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction { } return $title; break; - case ConpherenceTransactionType::TYPE_PARTICIPANTS: + case self::TYPE_PARTICIPANTS: $add = array_diff($new, $old); $rem = array_diff($old, $new); @@ -124,7 +131,7 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction { $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case ConpherenceTransactionType::TYPE_TITLE: + case self::TYPE_TITLE: if ($old && $new) { $title = pht( '%s renamed this room from "%s" to "%s".', @@ -144,7 +151,7 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction { } return $title; break; - case ConpherenceTransactionType::TYPE_PICTURE: + case self::TYPE_PICTURE: return pht( '%s updated the room image.', $this->renderHandleLink($author_phid)); @@ -180,7 +187,7 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction { $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case ConpherenceTransactionType::TYPE_TITLE: + case self::TYPE_TITLE: if ($old && $new) { $title = pht( '%s renamed this thread from "%s" to "%s".', @@ -200,7 +207,7 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction { } return $title; break; - case ConpherenceTransactionType::TYPE_PICTURE: + case self::TYPE_PICTURE: return pht( '%s updated the room image.', $this->renderHandleLink($author_phid)); @@ -237,12 +244,12 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction { $phids[] = $this->getAuthorPHID(); switch ($this->getTransactionType()) { - case ConpherenceTransactionType::TYPE_TITLE: - case ConpherenceTransactionType::TYPE_PICTURE: - case ConpherenceTransactionType::TYPE_FILES: - case ConpherenceTransactionType::TYPE_DATE_MARKER: + case self::TYPE_TITLE: + case self::TYPE_PICTURE: + case self::TYPE_FILES: + case self::TYPE_DATE_MARKER: break; - case ConpherenceTransactionType::TYPE_PARTICIPANTS: + case self::TYPE_PARTICIPANTS: $phids = array_merge($phids, $this->getOldValue()); $phids = array_merge($phids, $this->getNewValue()); break; diff --git a/src/applications/conpherence/view/ConpherenceTransactionView.php b/src/applications/conpherence/view/ConpherenceTransactionView.php index 2dba770e54..57bec773fc 100644 --- a/src/applications/conpherence/view/ConpherenceTransactionView.php +++ b/src/applications/conpherence/view/ConpherenceTransactionView.php @@ -6,14 +6,9 @@ final class ConpherenceTransactionView extends AphrontView { private $conpherenceTransaction; private $handles; private $markupEngine; - private $epoch; - private $epochHref; - private $contentSource; - private $anchorName; - private $anchorText; + private $fullDisplay; private $classes = array(); private $timeOnly; - private $showImages = true; public function setConpherenceThread(ConpherenceThread $t) { $this->conpherenceThread = $t; @@ -52,25 +47,13 @@ final class ConpherenceTransactionView extends AphrontView { return $this->markupEngine; } - public function setEpoch($epoch, $epoch_href = null) { - $this->epoch = $epoch; - $this->epochHref = $epoch_href; + public function setFullDisplay($bool) { + $this->fullDisplay = $bool; return $this; } - public function setContentSource(PhabricatorContentSource $source) { - $this->contentSource = $source; - return $this; - } - - private function getContentSource() { - return $this->contentSource; - } - - public function setAnchor($anchor_name, $anchor_text) { - $this->anchorName = $anchor_name; - $this->anchorText = $anchor_text; - return $this; + private function getFullDisplay() { + return $this->fullDisplay; } public function addClass($class) { @@ -78,23 +61,9 @@ final class ConpherenceTransactionView extends AphrontView { return $this; } - public function setTimeOnly($time) { - $this->timeOnly = $time; - return $this; - } - - public function setShowImages($bool) { - $this->showImages = $bool; - return $this; - } - - private function getShowImages() { - return $this->showImages; - } - public function render() { - $user = $this->getUser(); - if (!$user) { + $viewer = $this->getUser(); + if (!$viewer) { throw new Exception(pht('Call setUser() before render()!')); } @@ -102,7 +71,7 @@ final class ConpherenceTransactionView extends AphrontView { $transaction = $this->getConpherenceTransaction(); switch ($transaction->getTransactionType()) { - case ConpherenceTransactionType::TYPE_DATE_MARKER: + case ConpherenceTransaction::TYPE_DATE_MARKER: return javelin_tag( 'div', array( @@ -120,7 +89,7 @@ final class ConpherenceTransactionView extends AphrontView { ), phabricator_format_local_time( $transaction->getDateCreated(), - $user, + $viewer, 'M jS, Y')), )); break; @@ -132,7 +101,10 @@ final class ConpherenceTransactionView extends AphrontView { $content = $this->renderTransactionContent(); $classes = implode(' ', $this->classes); - $transaction_id = $this->anchorName ? 'anchor-'.$this->anchorName : null; + $transaction_dom_id = null; + if ($this->getFullDisplay()) { + $transaction_dom_id = 'anchor-'.$transaction->getID(); + } $header = phutil_tag_div( 'conpherence-transaction-header grouped', @@ -142,7 +114,7 @@ final class ConpherenceTransactionView extends AphrontView { 'div', array( 'class' => 'conpherence-transaction-view '.$classes, - 'id' => $transaction_id, + 'id' => $transaction_dom_id, 'sigil' => 'conpherence-transaction-view', 'meta' => array( 'id' => $transaction->getID(), @@ -156,53 +128,60 @@ final class ConpherenceTransactionView extends AphrontView { } private function renderTransactionInfo() { + $viewer = $this->getUser(); + $thread = $this->getConpherenceThread(); + $transaction = $this->getConpherenceTransaction(); $info = array(); - if ($this->getContentSource()) { + if ($this->getFullDisplay() && $transaction->getContentSource()) { $content_source = id(new PhabricatorContentSourceView()) - ->setContentSource($this->getContentSource()) - ->setUser($this->user) + ->setContentSource($transaction->getContentSource()) + ->setUser($viewer) ->render(); if ($content_source) { $info[] = $content_source; } } - if ($this->epoch) { - if ($this->timeOnly) { - $epoch = phabricator_time($this->epoch, $this->user); - } else { - $epoch = phabricator_datetime($this->epoch, $this->user); - } - if ($this->epochHref) { - $epoch = phutil_tag( - 'a', - array( - 'href' => $this->epochHref, - 'class' => 'epoch-link', - ), - $epoch); - } - $info[] = $epoch; - } - - if ($this->anchorName) { + Javelin::initBehavior('phabricator-tooltips'); + $tip = phabricator_datetime($transaction->getDateCreated(), $viewer); + $label = phabricator_time($transaction->getDateCreated(), $viewer); + $width = 360; + if ($this->getFullDisplay()) { Javelin::initBehavior('phabricator-watch-anchor'); - $anchor = id(new PhabricatorAnchorView()) - ->setAnchorName($this->anchorName) + ->setAnchorName($transaction->getID()) ->render(); $info[] = hsprintf( '%s%s', $anchor, - phutil_tag( + javelin_tag( 'a', array( - 'href' => '#'.$this->anchorName, + 'href' => '#'.$transaction->getID(), 'class' => 'anchor-link', + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $tip, + 'size' => $width, + ), ), - $this->anchorText)); + $label)); + } else { + $href = '/'.$thread->getMonogram().'#'.$transaction->getID(); + $info[] = javelin_tag( + 'a', + array( + 'href' => $href, + 'class' => 'epoch-link', + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $tip, + 'size' => $width, + ), + ), + $label); } $info = phutil_implode_html(" \xC2\xB7 ", $info); @@ -234,7 +213,7 @@ final class ConpherenceTransactionView extends AphrontView { private function renderTransactionImage() { $image = null; - if ($this->getShowImages()) { + if ($this->getFullDisplay()) { $transaction = $this->getConpherenceTransaction(); switch ($transaction->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: @@ -260,13 +239,13 @@ final class ConpherenceTransactionView extends AphrontView { $content = null; $handles = $this->getHandles(); switch ($transaction->getTransactionType()) { - case ConpherenceTransactionType::TYPE_FILES: + case ConpherenceTransaction::TYPE_FILES: $content = $transaction->getTitle(); break; - case ConpherenceTransactionType::TYPE_TITLE: - case ConpherenceTransactionType::TYPE_PICTURE: - case ConpherenceTransactionType::TYPE_PICTURE_CROP: - case ConpherenceTransactionType::TYPE_PARTICIPANTS: + case ConpherenceTransaction::TYPE_TITLE: + case ConpherenceTransaction::TYPE_PICTURE: + case ConpherenceTransaction::TYPE_PICTURE_CROP: + case ConpherenceTransaction::TYPE_PARTICIPANTS: case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: case PhabricatorTransactions::TYPE_JOIN_POLICY: @@ -282,7 +261,7 @@ final class ConpherenceTransactionView extends AphrontView { $comment, PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT); $content_class = 'conpherence-message'; - break; + break; } $this->appendChild( diff --git a/src/applications/daemon/management/PhabricatorDaemonManagementWorkflow.php b/src/applications/daemon/management/PhabricatorDaemonManagementWorkflow.php index 63303d6fb7..f62dcecff9 100644 --- a/src/applications/daemon/management/PhabricatorDaemonManagementWorkflow.php +++ b/src/applications/daemon/management/PhabricatorDaemonManagementWorkflow.php @@ -5,7 +5,7 @@ abstract class PhabricatorDaemonManagementWorkflow private $runDaemonsAsUser = null; - protected final function loadAvailableDaemonClasses() { + final protected function loadAvailableDaemonClasses() { $loader = new PhutilSymbolLoader(); return $loader ->setAncestorClass('PhutilDaemon') @@ -13,12 +13,12 @@ abstract class PhabricatorDaemonManagementWorkflow ->selectSymbolsWithoutLoading(); } - protected final function getPIDDirectory() { + final protected function getPIDDirectory() { $path = PhabricatorEnv::getEnvConfig('phd.pid-directory'); return $this->getControlDirectory($path); } - protected final function getLogDirectory() { + final protected function getLogDirectory() { $path = PhabricatorEnv::getEnvConfig('phd.log-directory'); return $this->getControlDirectory($path); } @@ -42,7 +42,7 @@ abstract class PhabricatorDaemonManagementWorkflow return $path; } - protected final function loadRunningDaemons() { + final protected function loadRunningDaemons() { $daemons = array(); $pid_dir = $this->getPIDDirectory(); @@ -56,7 +56,7 @@ abstract class PhabricatorDaemonManagementWorkflow return array_mergev($daemons); } - protected final function loadAllRunningDaemons() { + final protected function loadAllRunningDaemons() { $local_daemons = $this->loadRunningDaemons(); $local_ids = array(); @@ -114,7 +114,7 @@ abstract class PhabricatorDaemonManagementWorkflow return head($match); } - protected final function launchDaemons( + final protected function launchDaemons( array $daemons, $debug, $run_as_current_user = false) { @@ -307,7 +307,7 @@ abstract class PhabricatorDaemonManagementWorkflow /* -( Commands )----------------------------------------------------------- */ - protected final function executeStartCommand(array $options) { + final protected function executeStartCommand(array $options) { PhutilTypeSpec::checkMap( $options, array( @@ -377,7 +377,7 @@ abstract class PhabricatorDaemonManagementWorkflow return 0; } - protected final function executeStopCommand( + final protected function executeStopCommand( array $pids, array $options) { @@ -454,7 +454,7 @@ abstract class PhabricatorDaemonManagementWorkflow return 0; } - protected final function executeReloadCommand(array $pids) { + final protected function executeReloadCommand(array $pids) { $console = PhutilConsole::getConsole(); $daemons = $this->loadRunningDaemons(); diff --git a/src/applications/differential/customfield/DifferentialSubscribersField.php b/src/applications/differential/customfield/DifferentialSubscribersField.php index dac2040d77..a1e8d1dbd6 100644 --- a/src/applications/differential/customfield/DifferentialSubscribersField.php +++ b/src/applications/differential/customfield/DifferentialSubscribersField.php @@ -78,7 +78,6 @@ final class DifferentialSubscribersField array( PhabricatorPeopleUserPHIDType::TYPECONST, PhabricatorProjectProjectPHIDType::TYPECONST, - PhabricatorMailingListListPHIDType::TYPECONST, )); } diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 6a43f35d5a..b9405faff9 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -1258,19 +1258,19 @@ final class DifferentialTransactionEditor public function getMailTagsMap() { return array( - MetaMTANotificationType::TYPE_DIFFERENTIAL_REVIEW_REQUEST => + DifferentialTransaction::MAILTAG_REVIEW_REQUEST => pht('A revision is created.'), - MetaMTANotificationType::TYPE_DIFFERENTIAL_UPDATED => + DifferentialTransaction::MAILTAG_UPDATED => pht('A revision is updated.'), - MetaMTANotificationType::TYPE_DIFFERENTIAL_COMMENT => + DifferentialTransaction::MAILTAG_COMMENT => pht('Someone comments on a revision.'), - MetaMTANotificationType::TYPE_DIFFERENTIAL_CLOSED => + DifferentialTransaction::MAILTAG_CLOSED => pht('A revision is closed.'), - MetaMTANotificationType::TYPE_DIFFERENTIAL_REVIEWERS => + DifferentialTransaction::MAILTAG_REVIEWERS => pht("A revision's reviewers change."), - MetaMTANotificationType::TYPE_DIFFERENTIAL_CC => + DifferentialTransaction::MAILTAG_CC => pht("A revision's CCs change."), - MetaMTANotificationType::TYPE_DIFFERENTIAL_OTHER => + DifferentialTransaction::MAILTAG_OTHER => pht('Other revision activity not listed above occurs.'), ); } @@ -1554,13 +1554,6 @@ final class DifferentialTransactionEditor PhabricatorLiskDAO $object, array $xactions) { - $unsubscribed_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( - $object->getPHID(), - PhabricatorObjectHasUnsubscriberEdgeType::EDGECONST); - - $subscribed_phids = PhabricatorSubscribersQuery::loadSubscribersForPHID( - $object->getPHID()); - $revision = id(new DifferentialRevisionQuery()) ->setViewer($this->getActor()) ->withPHIDs(array($object->getPHID())) @@ -1579,9 +1572,7 @@ final class DifferentialTransactionEditor $reviewers = $revision->getReviewerStatus(); $reviewer_phids = mpull($reviewers, 'getReviewerPHID'); - $adapter->setExplicitCCs($subscribed_phids); $adapter->setExplicitReviewers($reviewer_phids); - $adapter->setForbiddenCCs($unsubscribed_phids); return $adapter; } @@ -1593,24 +1584,6 @@ final class DifferentialTransactionEditor $xactions = array(); - // Build a transaction to adjust CCs. - $ccs = array( - '+' => array_keys($adapter->getCCsAddedByHerald()), - '-' => array_keys($adapter->getCCsRemovedByHerald()), - ); - $value = array(); - foreach ($ccs as $type => $phids) { - foreach ($phids as $phid) { - $value[$type][$phid] = $phid; - } - } - - if ($value) { - $xactions[] = id(new DifferentialTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS) - ->setNewValue($value); - } - // Build a transaction to adjust reviewers. $reviewers = array( DifferentialReviewerStatus::STATUS_ADDED => @@ -1902,4 +1875,25 @@ final class DifferentialTransactionEditor return $section; } + protected function willPublish(PhabricatorLiskDAO $object, array $xactions) { + // Reload to pick up the active diff and reviewer status. + return id(new DifferentialRevisionQuery()) + ->setViewer($this->getActor()) + ->needReviewerStatus(true) + ->needActiveDiffs(true) + ->withIDs(array($object->getID())) + ->executeOne(); + } + + protected function getCustomWorkerState() { + return array( + 'changedPriorToCommitURI' => $this->changedPriorToCommitURI, + ); + } + + protected function loadCustomWorkerState(array $state) { + $this->changedPriorToCommitURI = idx($state, 'changedPriorToCommitURI'); + return $this; + } + } diff --git a/src/applications/differential/landing/DifferentialLandingStrategy.php b/src/applications/differential/landing/DifferentialLandingStrategy.php index d404511ed3..c138c3ebc7 100644 --- a/src/applications/differential/landing/DifferentialLandingStrategy.php +++ b/src/applications/differential/landing/DifferentialLandingStrategy.php @@ -2,7 +2,7 @@ abstract class DifferentialLandingStrategy { - public abstract function processLandRequest( + abstract public function processLandRequest( AphrontRequest $request, DifferentialRevision $revision, PhabricatorRepository $repository); @@ -10,7 +10,7 @@ abstract class DifferentialLandingStrategy { /** * @return PhabricatorActionView or null. */ - public abstract function createMenuItem( + abstract public function createMenuItem( PhabricatorUser $viewer, DifferentialRevision $revision, PhabricatorRepository $repository); diff --git a/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php b/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php index dfc990d4d4..a53846aeaa 100644 --- a/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php +++ b/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php @@ -225,7 +225,7 @@ final class DifferentialChangesetOneUpRenderer 'file' => $new_file, 'line' => 1, 'oline' => ($old_file ? 1 : null), - 'render' => $this->renderImageStage($old_file), + 'render' => $this->renderImageStage($new_file), ); } diff --git a/src/applications/differential/storage/DifferentialDiff.php b/src/applications/differential/storage/DifferentialDiff.php index 6c67cff51e..360e1ff119 100644 --- a/src/applications/differential/storage/DifferentialDiff.php +++ b/src/applications/differential/storage/DifferentialDiff.php @@ -37,7 +37,6 @@ final class DifferentialDiff private $unsavedChangesets = array(); private $changesets = self::ATTACHABLE; - private $arcanistProject = self::ATTACHABLE; private $revision = self::ATTACHABLE; private $properties = array(); @@ -108,25 +107,6 @@ final class DifferentialDiff $this->getID()); } - public function attachArcanistProject( - PhabricatorRepositoryArcanistProject $project = null) { - $this->arcanistProject = $project; - return $this; - } - - public function getArcanistProject() { - return $this->assertAttached($this->arcanistProject); - } - - public function getArcanistProjectName() { - $name = ''; - if ($this->arcanistProject) { - $project = $this->getArcanistProject(); - $name = $project->getName(); - } - return $name; - } - public function save() { $this->openTransaction(); $ret = parent::save(); diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index 0a4d459568..5f2131475b 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -4,6 +4,7 @@ final class DifferentialRevision extends DifferentialDAO implements PhabricatorTokenReceiverInterface, PhabricatorPolicyInterface, + PhabricatorExtendedPolicyInterface, PhabricatorFlaggableInterface, PhrequentTrackableInterface, HarbormasterBuildableInterface, @@ -64,6 +65,7 @@ final class DifferentialRevision extends DifferentialDAO ->setViewPolicy($view_policy) ->setAuthorPHID($actor->getPHID()) ->attachRelationships(array()) + ->attachRepository(null) ->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVIEW); } @@ -280,6 +282,10 @@ final class DifferentialRevision extends DifferentialDAO return $this; } + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, @@ -326,6 +332,45 @@ final class DifferentialRevision extends DifferentialDAO return $description; } + +/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */ + + + public function getExtendedPolicy($capability, PhabricatorUser $viewer) { + $extended = array(); + + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + // NOTE: In Differential, an automatic capability on a revision (being + // an author) is sufficient to view it, even if you can not see the + // repository the revision belongs to. We can bail out early in this + // case. + if ($this->hasAutomaticCapability($capability, $viewer)) { + break; + } + + $repository_phid = $this->getRepositoryPHID(); + $repository = $this->getRepository(); + + // Try to use the object if we have it, since it will save us some + // data fetching later on. In some cases, we might not have it. + $repository_ref = nonempty($repository, $repository_phid); + if ($repository_ref) { + $extended[] = array( + $repository_ref, + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + break; + } + + return $extended; + } + + +/* -( PhabricatorTokenReceiverInterface )---------------------------------- */ + + public function getUsersToNotifyOfTokenGiven() { return array( $this->getAuthorPHID(), diff --git a/src/applications/differential/storage/DifferentialTransaction.php b/src/applications/differential/storage/DifferentialTransaction.php index 23f6a45c2a..bc52227590 100644 --- a/src/applications/differential/storage/DifferentialTransaction.php +++ b/src/applications/differential/storage/DifferentialTransaction.php @@ -4,6 +4,19 @@ final class DifferentialTransaction extends PhabricatorApplicationTransaction { private $isCommandeerSideEffect; + const TYPE_INLINE = 'differential:inline'; + const TYPE_UPDATE = 'differential:update'; + const TYPE_ACTION = 'differential:action'; + const TYPE_STATUS = 'differential:status'; + + const MAILTAG_REVIEWERS = 'differential-reviewers'; + const MAILTAG_CLOSED = 'differential-committed'; + const MAILTAG_CC = 'differential-cc'; + const MAILTAG_COMMENT = 'differential-comment'; + const MAILTAG_UPDATED = 'differential-updated'; + const MAILTAG_REVIEW_REQUEST = 'differential-review-request'; + const MAILTAG_OTHER = 'differential-other'; + public function setIsCommandeerSideEffect($is_side_effect) { $this->isCommandeerSideEffect = $is_side_effect; @@ -14,11 +27,6 @@ final class DifferentialTransaction extends PhabricatorApplicationTransaction { return $this->isCommandeerSideEffect; } - const TYPE_INLINE = 'differential:inline'; - const TYPE_UPDATE = 'differential:update'; - const TYPE_ACTION = 'differential:action'; - const TYPE_STATUS = 'differential:status'; - public function getApplicationName() { return 'differential'; } @@ -109,7 +117,6 @@ final class DifferentialTransaction extends PhabricatorApplicationTransaction { } public function getActionStrength() { - switch ($this->getTransactionType()) { case self::TYPE_ACTION: return 3; @@ -160,38 +167,38 @@ final class DifferentialTransaction extends PhabricatorApplicationTransaction { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_SUBSCRIBERS; - $tags[] = MetaMTANotificationType::TYPE_DIFFERENTIAL_CC; + $tags[] = self::MAILTAG_CC; break; case self::TYPE_ACTION: switch ($this->getNewValue()) { case DifferentialAction::ACTION_CLOSE: - $tags[] = MetaMTANotificationType::TYPE_DIFFERENTIAL_CLOSED; + $tags[] = self::MAILTAG_CLOSED; break; } break; case self::TYPE_UPDATE: $old = $this->getOldValue(); if ($old === null) { - $tags[] = MetaMTANotificationType::TYPE_DIFFERENTIAL_REVIEW_REQUEST; + $tags[] = self::MAILTAG_REVIEW_REQUEST; } else { - $tags[] = MetaMTANotificationType::TYPE_DIFFERENTIAL_UPDATED; + $tags[] = self::MAILTAG_UPDATED; } break; case PhabricatorTransactions::TYPE_EDGE: switch ($this->getMetadataValue('edge:type')) { case DifferentialRevisionHasReviewerEdgeType::EDGECONST: - $tags[] = MetaMTANotificationType::TYPE_DIFFERENTIAL_REVIEWERS; + $tags[] = self::MAILTAG_REVIEWERS; break; } break; case PhabricatorTransactions::TYPE_COMMENT: case self::TYPE_INLINE: - $tags[] = MetaMTANotificationType::TYPE_DIFFERENTIAL_COMMENT; + $tags[] = self::MAILTAG_COMMENT; break; } if (!$tags) { - $tags[] = MetaMTANotificationType::TYPE_DIFFERENTIAL_OTHER; + $tags[] = self::MAILTAG_OTHER; } return $tags; @@ -280,7 +287,7 @@ final class DifferentialTransaction extends PhabricatorApplicationTransaction { case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: return pht('This revision now requires review to proceed.'); } - } + } return parent::getTitle(); } @@ -438,7 +445,7 @@ final class DifferentialTransaction extends PhabricatorApplicationTransaction { '%s now requires review to proceed.', $object_link); } - } + } return parent::getTitleForFeed(); } diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index 5b00a8e5b5..f849cdc347 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -190,7 +190,7 @@ final class DifferentialChangesetListView extends AphrontView { } else { $detail->setAutoload(isset($this->visibleChangesets[$key])); if (isset($this->visibleChangesets[$key])) { - $load = 'Loading...'; + $load = pht('Loading...'); } else { $load = javelin_tag( 'a', diff --git a/src/applications/differential/view/DifferentialResultsTableView.php b/src/applications/differential/view/DifferentialResultsTableView.php index 226c68484c..f05b24216b 100644 --- a/src/applications/differential/view/DifferentialResultsTableView.php +++ b/src/applications/differential/view/DifferentialResultsTableView.php @@ -78,7 +78,7 @@ final class DifferentialResultsTableView extends AphrontView { 'href' => '#', 'mustcapture' => true, ), - 'Hide'); + pht('Hide')); $rows[] = javelin_tag( 'tr', diff --git a/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php b/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php index 2c7e0f47dd..27d071aa68 100644 --- a/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php +++ b/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php @@ -36,15 +36,14 @@ final class DifferentialRevisionUpdateHistoryView extends AphrontView { } public function render() { - $this->requireResource('differential-core-view-css'); $this->requireResource('differential-revision-history-css'); $data = array( array( - 'name' => 'Base', + 'name' => pht('Base'), 'id' => null, - 'desc' => 'Base', + 'desc' => pht('Base'), 'age' => null, 'obj' => null, ), @@ -53,7 +52,7 @@ final class DifferentialRevisionUpdateHistoryView extends AphrontView { $seq = 0; foreach ($this->diffs as $diff) { $data[] = array( - 'name' => 'Diff '.(++$seq), + 'name' => pht('Diff %d', ++$seq), 'id' => $diff->getID(), 'desc' => $diff->getDescription(), 'age' => $diff->getDateCreated(), diff --git a/src/applications/diffusion/controller/DiffusionLintController.php b/src/applications/diffusion/controller/DiffusionLintController.php index 18ebe7fc1d..b5bf561f8f 100644 --- a/src/applications/diffusion/controller/DiffusionLintController.php +++ b/src/applications/diffusion/controller/DiffusionLintController.php @@ -116,7 +116,7 @@ final class DiffusionLintController extends DiffusionController { ->setValue($owners)) ->appendChild( id(new AphrontFormSubmitControl()) - ->setValue('Filter')); + ->setValue(pht('Filter'))); $content[] = id(new AphrontListFilterView())->appendChild($form); } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php index 2aece855e9..1fcf366967 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php @@ -181,7 +181,7 @@ final class DiffusionRepositoryEditHostingController '%s: This repository is hosted elsewhere, so Phabricator can not '. 'perform writes. This mode will act like "Read Only" for '. 'repositories hosted elsewhere.', - phutil_tag('strong', array(), 'WARNING')), + phutil_tag('strong', array(), pht('WARNING'))), ); } diff --git a/src/applications/diffusion/controller/DiffusionSymbolController.php b/src/applications/diffusion/controller/DiffusionSymbolController.php index ede39a2f45..9011003fc7 100644 --- a/src/applications/diffusion/controller/DiffusionSymbolController.php +++ b/src/applications/diffusion/controller/DiffusionSymbolController.php @@ -12,7 +12,7 @@ final class DiffusionSymbolController extends DiffusionController { ->setViewer($user) ->setName($this->name); - if ($request->getStr('context') !== null) { + if ($request->getStr('context')) { $query->setContext($request->getStr('context')); } @@ -47,63 +47,69 @@ final class DiffusionSymbolController extends DiffusionController { $symbols = $query->execute(); - // For PHP builtins, jump to php.net documentation. - if ($request->getBool('jump') && count($symbols) == 0) { - if ($request->getStr('lang', 'php') == 'php') { - if ($request->getStr('type', 'function') == 'function') { - $functions = get_defined_functions(); - if (in_array($this->name, $functions['internal'])) { - return id(new AphrontRedirectResponse()) - ->setIsExternal(true) - ->setURI('http://www.php.net/function.'.$this->name); - } - } - if ($request->getStr('type', 'class') == 'class') { - if (class_exists($this->name, false) || - interface_exists($this->name, false)) { - if (id(new ReflectionClass($this->name))->isInternal()) { - return id(new AphrontRedirectResponse()) - ->setIsExternal(true) - ->setURI('http://www.php.net/class.'.$this->name); - } - } - } - } + + + $external_query = id(new DiffusionExternalSymbolQuery()) + ->withNames(array($this->name)); + + if ($request->getStr('context')) { + $external_query->withContexts(array($request->getStr('context'))); + } + + if ($request->getStr('type')) { + $external_query->withTypes(array($request->getStr('type'))); + } + + if ($request->getStr('lang')) { + $external_query->withLanguages(array($request->getStr('lang'))); + } + + $external_sources = id(new PhutilSymbolLoader()) + ->setAncestorClass('DiffusionExternalSymbolsSource') + ->loadObjects(); + $results = array($symbols); + foreach ($external_sources as $source) { + $results[] = $source->executeQuery($external_query); + } + $symbols = array_mergev($results); + + if ($request->getBool('jump') && count($symbols) == 1) { + // If this is a clickthrough from Differential, just jump them + // straight to the target if we got a single hit. + $symbol = head($symbols); + return id(new AphrontRedirectResponse()) + ->setIsExternal($symbol->isExternal()) + ->setURI($symbol->getURI()); } $rows = array(); foreach ($symbols as $symbol) { - $file = $symbol->getPath(); - $line = $symbol->getLineNumber(); + $href = $symbol->getURI(); - $repo = $symbol->getRepository(); - if ($repo) { - $href = $symbol->getURI(); - - if ($request->getBool('jump') && count($symbols) == 1) { - // If this is a clickthrough from Differential, just jump them - // straight to the target if we got a single hit. - return id(new AphrontRedirectResponse())->setURI($href); - } - - $location = phutil_tag( - 'a', - array( - 'href' => $href, - ), - $file.':'.$line); - } else if ($file) { - $location = $file.':'.$line; + if ($symbol->isExternal()) { + $source = $symbol->getSource(); + $location = $symbol->getLocation(); } else { - $location = '?'; + $repo = $symbol->getRepository(); + $file = $symbol->getPath(); + $line = $symbol->getLineNumber(); + + $source = $repo->getMonogram(); + $location = $file.':'.$line; } + $location = phutil_tag( + 'a', + array( + 'href' => $href, + ), + $location); $rows[] = array( $symbol->getSymbolType(), $symbol->getSymbolContext(), $symbol->getSymbolName(), $symbol->getSymbolLanguage(), - $repo->getMonogram(), + $source, $location, ); } @@ -115,8 +121,8 @@ final class DiffusionSymbolController extends DiffusionController { pht('Context'), pht('Name'), pht('Language'), - pht('Repository'), - pht('File'), + pht('Source'), + pht('Location'), )); $table->setColumnClasses( array( diff --git a/src/applications/diffusion/edge/DiffusionCommitRevertedByCommitEdgeType.php b/src/applications/diffusion/edge/DiffusionCommitRevertedByCommitEdgeType.php index 923c602e57..53c39e54c9 100644 --- a/src/applications/diffusion/edge/DiffusionCommitRevertedByCommitEdgeType.php +++ b/src/applications/diffusion/edge/DiffusionCommitRevertedByCommitEdgeType.php @@ -19,7 +19,7 @@ final class DiffusionCommitRevertedByCommitEdgeType $add_edges) { return pht( - '%s added %s reverted commit(s): %s.', + '%s added %s reverting commit(s): %s.', $actor, $add_count, $add_edges); @@ -31,7 +31,7 @@ final class DiffusionCommitRevertedByCommitEdgeType $rem_edges) { return pht( - '%s removed %s reverted commit(s): %s.', + '%s removed %s reverting commit(s): %s.', $actor, $rem_count, $rem_edges); @@ -46,7 +46,7 @@ final class DiffusionCommitRevertedByCommitEdgeType $rem_edges) { return pht( - '%s edited reverted commit(s), added %s: %s; removed %s: %s.', + '%s edited reverting commit(s), added %s: %s; removed %s: %s.', $actor, $add_count, $add_edges, @@ -61,7 +61,7 @@ final class DiffusionCommitRevertedByCommitEdgeType $add_edges) { return pht( - '%s added %s reverted commit(s) for %s: %s.', + '%s added %s reverting commit(s) for %s: %s.', $actor, $add_count, $object, @@ -75,7 +75,7 @@ final class DiffusionCommitRevertedByCommitEdgeType $rem_edges) { return pht( - '%s removed %s reverted commit(s) for %s: %s.', + '%s removed %s reverting commit(s) for %s: %s.', $actor, $rem_count, $object, @@ -92,7 +92,7 @@ final class DiffusionCommitRevertedByCommitEdgeType $rem_edges) { return pht( - '%s edited reverted commit(s) for %s, added %s: %s; removed %s: %s.', + '%s edited reverting commit(s) for %s, added %s: %s; removed %s: %s.', $actor, $object, $add_count, diff --git a/src/applications/diffusion/edge/DiffusionCommitRevertsCommitEdgeType.php b/src/applications/diffusion/edge/DiffusionCommitRevertsCommitEdgeType.php index 2984d2f526..8f32797173 100644 --- a/src/applications/diffusion/edge/DiffusionCommitRevertsCommitEdgeType.php +++ b/src/applications/diffusion/edge/DiffusionCommitRevertsCommitEdgeType.php @@ -22,7 +22,7 @@ final class DiffusionCommitRevertsCommitEdgeType extends PhabricatorEdgeType { $add_edges) { return pht( - '%s added %s reverting commit(s): %s.', + '%s added %s reverted commit(s): %s.', $actor, $add_count, $add_edges); @@ -34,7 +34,7 @@ final class DiffusionCommitRevertsCommitEdgeType extends PhabricatorEdgeType { $rem_edges) { return pht( - '%s removed %s reverting commit(s): %s.', + '%s removed %s reverted commit(s): %s.', $actor, $rem_count, $rem_edges); @@ -49,7 +49,7 @@ final class DiffusionCommitRevertsCommitEdgeType extends PhabricatorEdgeType { $rem_edges) { return pht( - '%s edited reverting commit(s), added %s: %s; removed %s: %s.', + '%s edited reverted commit(s), added %s: %s; removed %s: %s.', $actor, $add_count, $add_edges, @@ -64,7 +64,7 @@ final class DiffusionCommitRevertsCommitEdgeType extends PhabricatorEdgeType { $add_edges) { return pht( - '%s added %s reverting commit(s) for %s: %s.', + '%s added %s reverted commit(s) for %s: %s.', $actor, $add_count, $object, @@ -78,7 +78,7 @@ final class DiffusionCommitRevertsCommitEdgeType extends PhabricatorEdgeType { $rem_edges) { return pht( - '%s removed %s reverting commit(s) for %s: %s.', + '%s removed %s reverted commit(s) for %s: %s.', $actor, $rem_count, $object, @@ -95,7 +95,7 @@ final class DiffusionCommitRevertsCommitEdgeType extends PhabricatorEdgeType { $rem_edges) { return pht( - '%s edited reverting commit(s) for %s, added %s: %s; removed %s: %s.', + '%s edited reverted commit(s) for %s, added %s: %s; removed %s: %s.', $actor, $object, $add_count, diff --git a/src/applications/diffusion/herald/HeraldPreCommitAdapter.php b/src/applications/diffusion/herald/HeraldPreCommitAdapter.php index d1fc7c6be4..8e8f89a88e 100644 --- a/src/applications/diffusion/herald/HeraldPreCommitAdapter.php +++ b/src/applications/diffusion/herald/HeraldPreCommitAdapter.php @@ -95,12 +95,6 @@ abstract class HeraldPreCommitAdapter extends HeraldAdapter { foreach ($effects as $effect) { $action = $effect->getAction(); switch ($action) { - case self::ACTION_NOTHING: - $result[] = new HeraldApplyTranscript( - $effect, - true, - pht('Did nothing.')); - break; case self::ACTION_BLOCK: $result[] = new HeraldApplyTranscript( $effect, diff --git a/src/applications/diffusion/panel/DiffusionSetPasswordSettingsPanel.php b/src/applications/diffusion/panel/DiffusionSetPasswordSettingsPanel.php index 9271056c7b..454ed01570 100644 --- a/src/applications/diffusion/panel/DiffusionSetPasswordSettingsPanel.php +++ b/src/applications/diffusion/panel/DiffusionSetPasswordSettingsPanel.php @@ -19,6 +19,10 @@ final class DiffusionSetPasswordSettingsPanel extends PhabricatorSettingsPanel { } public function isEnabled() { + if ($this->getUser()->getIsMailingList()) { + return false; + } + return PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth'); } diff --git a/src/applications/diffusion/symbol/DiffusionExternalSymbolQuery.php b/src/applications/diffusion/symbol/DiffusionExternalSymbolQuery.php new file mode 100644 index 0000000000..f8ca15aabd --- /dev/null +++ b/src/applications/diffusion/symbol/DiffusionExternalSymbolQuery.php @@ -0,0 +1,46 @@ +languages = $languages; + return $this; + } + public function withTypes(array $types) { + $this->types = $types; + return $this; + } + public function withNames(array $names) { + $this->names = $names; + return $this; + } + public function withContexts(array $contexts) { + $this->contexts = $contexts; + return $this; + } + + + public function getLanguages() { + return $this->languages; + } + public function getTypes() { + return $this->types; + } + public function getNames() { + return $this->names; + } + public function getContexts() { + return $this->contexts; + } + + public function matchesAnyLanguage(array $languages) { + return (!$this->languages) || array_intersect($languages, $this->languages); + } + public function matchesAnyType(array $types) { + return (!$this->types) || array_intersect($types, $this->types); + } +} diff --git a/src/applications/diffusion/symbol/DiffusionExternalSymbolsSource.php b/src/applications/diffusion/symbol/DiffusionExternalSymbolsSource.php new file mode 100644 index 0000000000..ba9ad6e5ad --- /dev/null +++ b/src/applications/diffusion/symbol/DiffusionExternalSymbolsSource.php @@ -0,0 +1,15 @@ +setIsExternal(true) + ->makeEphemeral(); + } +} diff --git a/src/applications/diffusion/symbol/DiffusionPhpExternalSymbolsSource.php b/src/applications/diffusion/symbol/DiffusionPhpExternalSymbolsSource.php new file mode 100644 index 0000000000..455d237175 --- /dev/null +++ b/src/applications/diffusion/symbol/DiffusionPhpExternalSymbolsSource.php @@ -0,0 +1,49 @@ +matchesAnyLanguage(array('php'))) { + return $symbols; + } + + $names = $query->getNames(); + + if ($query->matchesAnyType(array('function'))) { + $functions = get_defined_functions(); + $functions = $functions['internal']; + + foreach ($names as $name) { + if (in_array($name, $functions)) { + $symbols[] = $this->buildExternalSymbol() + ->setSymbolName($name) + ->setSymbolType('function') + ->setSource(pht('PHP')) + ->setLocation(pht('Manual at php.net')) + ->setSymbolLanguage('php') + ->setExternalURI('http://www.php.net/function.'.$name); + } + } + } + if ($query->matchesAnyType(array('class'))) { + foreach ($names as $name) { + if (class_exists($name, false) || interface_exists($name, false)) { + if (id(new ReflectionClass($name))->isInternal()) { + $symbols[] = $this->buildExternalSymbol() + ->setSymbolName($name) + ->setSymbolType('class') + ->setSource(pht('PHP')) + ->setLocation(pht('Manual at php.net')) + ->setSymbolLanguage('php') + ->setExternalURI('http://www.php.net/class.'.$name); + } + } + } + } + + return $symbols; + } +} diff --git a/src/applications/diffusion/symbol/DiffusionPythonExternalSymbolsSource.php b/src/applications/diffusion/symbol/DiffusionPythonExternalSymbolsSource.php new file mode 100644 index 0000000000..dc8be8aa3f --- /dev/null +++ b/src/applications/diffusion/symbol/DiffusionPythonExternalSymbolsSource.php @@ -0,0 +1,134 @@ +matchesAnyLanguage(array('py', 'python'))) { + return $symbols; + } + + if (!$query->matchesAnyType(array('builtin', 'function'))) { + return $symbols; + } + + $names = $query->getNames(); + + foreach ($names as $name) { + if (idx(self::$python2Builtins, $name)) { + $symbols[] = $this->buildExternalSymbol() + ->setSymbolName($name) + ->setSymbolType('function') + ->setSource(pht('Standard Library')) + ->setLocation(pht('The Python 2 Standard Library')) + ->setSymbolLanguage('py') + ->setExternalURI( + 'https://docs.python.org/2/library/functions.html#'.$name); + } + if (idx(self::$python3Builtins, $name)) { + $symbols[] = $this->buildExternalSymbol() + ->setSymbolName($name) + ->setSymbolType('function') + ->setSource(pht('Standard Library')) + ->setLocation(pht('The Python 3 Standard Library')) + ->setSymbolLanguage('py') + ->setExternalURI( + 'https://docs.python.org/3/library/functions.html#'.$name); + } + } + return $symbols; + } + + private static $python2Builtins = array( + '__import__' => true, + 'abs' => true, + 'all' => true, + 'any' => true, + 'basestring' => true, + 'bin' => true, + 'bool' => true, + 'bytearray' => true, + 'callable' => true, + 'chr' => true, + 'classmethod' => true, + 'cmp' => true, + 'compile' => true, + 'complex' => true, + 'delattr' => true, + 'dict' => true, + 'dir' => true, + 'divmod' => true, + 'enumerate' => true, + 'eval' => true, + 'execfile' => true, + 'file' => true, + 'filter' => true, + 'float' => true, + 'format' => true, + 'frozenset' => true, + 'getattr' => true, + 'globals' => true, + 'hasattr' => true, + 'hash' => true, + 'help' => true, + 'hex' => true, + 'id' => true, + 'input' => true, + 'int' => true, + 'isinstance' => true, + 'issubclass' => true, + 'iter' => true, + 'len' => true, + 'list' => true, + 'locals' => true, + 'long' => true, + 'map' => true, + 'max' => true, + 'memoryview' => true, + 'min' => true, + 'next' => true, + 'object' => true, + 'oct' => true, + 'open' => true, + 'ord' => true, + 'pow' => true, + 'print' => true, + 'property' => true, + 'range' => true, + 'raw_input' => true, + 'reduce' => true, + 'reload' => true, + 'repr' => true, + 'reversed' => true, + 'round' => true, + 'set' => true, + 'setattr' => true, + 'slice' => true, + 'sorted' => true, + 'staticmethod' => true, + 'str' => true, + 'sum' => true, + 'super' => true, + 'tuple' => true, + 'type' => true, + 'unichr' => true, + 'unicode' => true, + 'vars' => true, + 'xrange' => true, + 'zip' => true, + ); + + // This list only contains functions that are new or changed between the + // Python versions. + private static $python3Builtins = array( + 'ascii' => true, + 'bytes' => true, + 'filter' => true, + 'map' => true, + 'next' => true, + 'range' => true, + 'super' => true, + 'zip' => true, + ); +} diff --git a/src/applications/diffusion/view/DiffusionHistoryTableView.php b/src/applications/diffusion/view/DiffusionHistoryTableView.php index 80220f7c21..ce1a693696 100644 --- a/src/applications/diffusion/view/DiffusionHistoryTableView.php +++ b/src/applications/diffusion/view/DiffusionHistoryTableView.php @@ -149,7 +149,7 @@ final class DiffusionHistoryTableView extends DiffusionView { $summary = AphrontTableView::renderSingleDisplayLine( $history->getSummary()); } else { - $summary = phutil_tag('em', array(), "Importing\xE2\x80\xA6"); + $summary = phutil_tag('em', array(), pht("Importing\xE2\x80\xA6")); } $build = null; diff --git a/src/applications/diviner/application/PhabricatorDivinerApplication.php b/src/applications/diviner/application/PhabricatorDivinerApplication.php index 895545a1cf..68b569213a 100644 --- a/src/applications/diviner/application/PhabricatorDivinerApplication.php +++ b/src/applications/diviner/application/PhabricatorDivinerApplication.php @@ -58,4 +58,11 @@ final class PhabricatorDivinerApplication extends PhabricatorApplication { ); } + public function getApplicationSearchDocumentTypes() { + return array( + DivinerAtomPHIDType::TYPECONST, + DivinerBookPHIDType::TYPECONST, + ); + } + } diff --git a/src/applications/diviner/controller/DivinerAtomController.php b/src/applications/diviner/controller/DivinerAtomController.php index 798c524367..a332b63fab 100644 --- a/src/applications/diviner/controller/DivinerAtomController.php +++ b/src/applications/diviner/controller/DivinerAtomController.php @@ -35,11 +35,6 @@ final class DivinerAtomController extends DivinerController { return new Aphront404Response(); } - // TODO: This query won't load ghosts, because they'll fail `needAtoms()`. - // Instead, we might want to load ghosts and render a message like - // "this thing existed in an older version, but no longer does", especially - // if we add content like comments. - $symbol = id(new DivinerAtomQuery()) ->setViewer($viewer) ->withBookPHIDs(array($book->getPHID())) @@ -47,6 +42,7 @@ final class DivinerAtomController extends DivinerController { ->withNames(array($this->atomName)) ->withContexts(array($this->atomContext)) ->withIndexes(array($this->atomIndex)) + ->withIsDocumentable(true) ->needAtoms(true) ->needExtends(true) ->needChildren(true) @@ -64,9 +60,9 @@ final class DivinerAtomController extends DivinerController { $book->getShortTitle(), '/book/'.$book->getName().'/'); - $atom_short_title = $atom->getDocblockMetaValue( - 'short', - $symbol->getTitle()); + $atom_short_title = $atom + ? $atom->getDocblockMetaValue('short', $symbol->getTitle()) + : $symbol->getTitle(); $crumbs->addTextCrumb($atom_short_title); @@ -76,26 +72,38 @@ final class DivinerAtomController extends DivinerController { id(new PHUITagView()) ->setType(PHUITagView::TYPE_STATE) ->setBackgroundColor(PHUITagView::COLOR_BLUE) - ->setName(DivinerAtom::getAtomTypeNameString($atom->getType()))); + ->setName(DivinerAtom::getAtomTypeNameString( + $atom ? $atom->getType() : $symbol->getType()))); $properties = id(new PHUIPropertyListView()); - $group = $atom->getProperty('group'); + $group = $atom ? $atom->getProperty('group') : $symbol->getGroupName(); if ($group) { $group_name = $book->getGroupName($group); } else { $group_name = null; } - $this->buildDefined($properties, $symbol); - $this->buildExtendsAndImplements($properties, $symbol); + $document = id(new PHUIDocumentView()) + ->setBook($book->getTitle(), $group_name) + ->setHeader($header) + ->addClass('diviner-view') + ->setFontKit(PHUIDocumentView::FONT_SOURCE_SANS) + ->appendChild($properties); - $warnings = $atom->getWarnings(); - if ($warnings) { - $warnings = id(new PHUIInfoView()) - ->setErrors($warnings) - ->setTitle(pht('Documentation Warnings')) - ->setSeverity(PHUIInfoView::SEVERITY_WARNING); + if ($atom) { + $this->buildDefined($properties, $symbol); + $this->buildExtendsAndImplements($properties, $symbol); + + $warnings = $atom->getWarnings(); + if ($warnings) { + $warnings = id(new PHUIInfoView()) + ->setErrors($warnings) + ->setTitle(pht('Documentation Warnings')) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING); + } + + $document->appendChild($warnings); } $methods = $this->composeMethods($symbol); @@ -111,7 +119,10 @@ final class DivinerAtomController extends DivinerController { } $engine->process(); - $content = $this->renderDocumentationText($symbol, $engine); + if ($atom) { + $content = $this->renderDocumentationText($symbol, $engine); + $document->appendChild($content); + } $toc = $engine->getEngineMetadata( $symbol, @@ -119,16 +130,18 @@ final class DivinerAtomController extends DivinerController { PhutilRemarkupHeaderBlockRule::KEY_HEADER_TOC, array()); - $document = id(new PHUIDocumentView()) - ->setBook($book->getTitle(), $group_name) - ->setHeader($header) - ->addClass('diviner-view') - ->setFontKit(PHUIDocumentView::FONT_SOURCE_SANS) - ->appendChild($properties) - ->appendChild($warnings) - ->appendChild($content); + if (!$atom) { + $document->appendChild( + id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) + ->appendChild( + pht( + 'This atom no longer exists.'))); + } - $document->appendChild($this->buildParametersAndReturn(array($symbol))); + if ($atom) { + $document->appendChild($this->buildParametersAndReturn(array($symbol))); + } if ($methods) { $tasks = $this->composeTasks($symbol); @@ -200,7 +213,7 @@ final class DivinerAtomController extends DivinerController { } $section = id(new DivinerSectionView()) - ->setHeader(pht('Methods')); + ->setHeader(pht('Methods')); foreach ($methods as $spec) { $matom = last($spec['atoms']); @@ -471,20 +484,23 @@ final class DivinerAtomController extends DivinerController { $atom = $symbol->getAtom(); $out = array(); - if ($atom->getProperty('final')) { - $out[] = 'final'; - } - if ($atom->getProperty('abstract')) { - $out[] = 'abstract'; - } + if ($atom) { + if ($atom->getProperty('final')) { + $out[] = 'final'; + } - if ($atom->getProperty('access')) { - $out[] = $atom->getProperty('access'); - } + if ($atom->getProperty('abstract')) { + $out[] = 'abstract'; + } - if ($atom->getProperty('static')) { - $out[] = 'static'; + if ($atom->getProperty('access')) { + $out[] = $atom->getProperty('access'); + } + + if ($atom->getProperty('static')) { + $out[] = 'static'; + } } switch ($symbol->getType()) { @@ -528,13 +544,15 @@ final class DivinerAtomController extends DivinerController { $out = phutil_implode_html(' ', $out); - $parameters = $atom->getProperty('parameters'); - if ($parameters !== null) { - $pout = array(); - foreach ($parameters as $parameter) { - $pout[] = idx($parameter, 'name', '...'); + if ($atom) { + $parameters = $atom->getProperty('parameters'); + if ($parameters !== null) { + $pout = array(); + foreach ($parameters as $parameter) { + $pout[] = idx($parameter, 'name', '...'); + } + $out = array($out, '('.implode(', ', $pout).')'); } - $out = array($out, '('.implode(', ', $pout).')'); } return phutil_tag( diff --git a/src/applications/diviner/controller/DivinerBookController.php b/src/applications/diviner/controller/DivinerBookController.php index fcc9aa822b..125edbb6ab 100644 --- a/src/applications/diviner/controller/DivinerBookController.php +++ b/src/applications/diviner/controller/DivinerBookController.php @@ -46,6 +46,8 @@ final class DivinerBookController extends DivinerController { $atoms = id(new DivinerAtomQuery()) ->setViewer($viewer) ->withBookPHIDs(array($book->getPHID())) + ->withGhosts(false) + ->withIsDocumentable(true) ->execute(); $atoms = msort($atoms, 'getSortKey'); diff --git a/src/applications/diviner/controller/DivinerFindController.php b/src/applications/diviner/controller/DivinerFindController.php index 0f73164da6..061e357b9f 100644 --- a/src/applications/diviner/controller/DivinerFindController.php +++ b/src/applications/diviner/controller/DivinerFindController.php @@ -41,6 +41,9 @@ final class DivinerFindController extends DivinerController { $query->withTypes(array($type)); } + $query->withGhosts(false); + $query->withIsDocumentable(true); + $name_query = clone $query; $name_query->withNames( diff --git a/src/applications/diviner/phid/DivinerAtomPHIDType.php b/src/applications/diviner/phid/DivinerAtomPHIDType.php index 21f0b105d3..308efedde5 100644 --- a/src/applications/diviner/phid/DivinerAtomPHIDType.php +++ b/src/applications/diviner/phid/DivinerAtomPHIDType.php @@ -5,7 +5,7 @@ final class DivinerAtomPHIDType extends PhabricatorPHIDType { const TYPECONST = 'ATOM'; public function getTypeName() { - return pht('Atom'); + return pht('Diviner Atom'); } public function newObject() { @@ -28,8 +28,17 @@ final class DivinerAtomPHIDType extends PhabricatorPHIDType { foreach ($handles as $phid => $handle) { $atom = $objects[$phid]; - $handle->setName($atom->getTitle()); - $handle->setURI($atom->getName()); + $book = $atom->getBook()->getName(); + $name = $atom->getName(); + $type = $atom->getType(); + + $handle + ->setName($atom->getName()) + ->setTitle($atom->getTitle()) + ->setURI("/book/{$book}/{$type}/{$name}/") + ->setStatus($atom->getGraphHash() + ? PhabricatorObjectHandle::STATUS_OPEN + : PhabricatorObjectHandle::STATUS_CLOSED); } } diff --git a/src/applications/diviner/phid/DivinerBookPHIDType.php b/src/applications/diviner/phid/DivinerBookPHIDType.php index 9828b3e981..7eda61a976 100644 --- a/src/applications/diviner/phid/DivinerBookPHIDType.php +++ b/src/applications/diviner/phid/DivinerBookPHIDType.php @@ -5,7 +5,7 @@ final class DivinerBookPHIDType extends PhabricatorPHIDType { const TYPECONST = 'BOOK'; public function getTypeName() { - return pht('Book'); + return pht('Diviner Book'); } public function newObject() { @@ -30,9 +30,10 @@ final class DivinerBookPHIDType extends PhabricatorPHIDType { $name = $book->getName(); - $handle->setName($book->getShortTitle()); - $handle->setFullName($book->getTitle()); - $handle->setURI("/diviner/book/{$name}/"); + $handle + ->setName($book->getShortTitle()) + ->setFullName($book->getTitle()) + ->setURI("/book/{$name}/"); } } diff --git a/src/applications/diviner/publisher/DivinerLivePublisher.php b/src/applications/diviner/publisher/DivinerLivePublisher.php index 9e3ea7d1ef..1bad2dcb8e 100644 --- a/src/applications/diviner/publisher/DivinerLivePublisher.php +++ b/src/applications/diviner/publisher/DivinerLivePublisher.php @@ -18,6 +18,9 @@ final class DivinerLivePublisher extends DivinerPublisher { $book->setConfigurationData($this->getConfigurationData())->save(); $this->book = $book; + + id(new PhabricatorSearchIndexer()) + ->queueDocumentForIndexing($book->getPHID()); } return $this->book; @@ -31,8 +34,6 @@ final class DivinerLivePublisher extends DivinerPublisher { ->withNames(array($atom->getName())) ->withContexts(array($atom->getContext())) ->withIndexes(array($this->getAtomSimilarIndex($atom))) - ->withIncludeUndocumentable(true) - ->withIncludeGhosts(true) ->executeOne(); if ($symbol) { @@ -64,7 +65,7 @@ final class DivinerLivePublisher extends DivinerPublisher { $symbols = id(new DivinerAtomQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withBookPHIDs(array($this->loadBook()->getPHID())) - ->withIncludeUndocumentable(true) + ->withGhosts(false) ->execute(); return mpull($symbols, 'getGraphHash'); @@ -124,6 +125,9 @@ final class DivinerLivePublisher extends DivinerPublisher { $symbol->save(); + id(new PhabricatorSearchIndexer()) + ->queueDocumentForIndexing($symbol->getPHID()); + // TODO: We probably need a finer-grained sense of what "documentable" // atoms are. Neither files nor methods are currently considered // documentable, but for different reasons: files appear nowhere, while diff --git a/src/applications/diviner/publisher/DivinerPublisher.php b/src/applications/diviner/publisher/DivinerPublisher.php index b98cec9965..1c0a56aaf4 100644 --- a/src/applications/diviner/publisher/DivinerPublisher.php +++ b/src/applications/diviner/publisher/DivinerPublisher.php @@ -10,42 +10,42 @@ abstract class DivinerPublisher { private $symbolReverseMap; private $dropCaches; - public final function setDropCaches($drop_caches) { + final public function setDropCaches($drop_caches) { $this->dropCaches = $drop_caches; return $this; } - public final function setRenderer(DivinerRenderer $renderer) { + final public function setRenderer(DivinerRenderer $renderer) { $renderer->setPublisher($this); $this->renderer = $renderer; return $this; } - public final function getRenderer() { + final public function getRenderer() { return $this->renderer; } - public final function setConfig(array $config) { + final public function setConfig(array $config) { $this->config = $config; return $this; } - public final function getConfig($key, $default = null) { + final public function getConfig($key, $default = null) { return idx($this->config, $key, $default); } - public final function getConfigurationData() { + final public function getConfigurationData() { return $this->config; } - public final function setAtomCache(DivinerAtomCache $cache) { + final public function setAtomCache(DivinerAtomCache $cache) { $this->atomCache = $cache; $graph_map = $this->atomCache->getGraphMap(); $this->atomGraphHashToNodeHashMap = array_flip($graph_map); return $this; } - protected final function getAtomFromGraphHash($graph_hash) { + final protected function getAtomFromGraphHash($graph_hash) { if (empty($this->atomGraphHashToNodeHashMap[$graph_hash])) { throw new Exception(pht("No such atom '%s'!", $graph_hash)); } @@ -54,7 +54,7 @@ abstract class DivinerPublisher { $this->atomGraphHashToNodeHashMap[$graph_hash]); } - protected final function getAtomFromNodeHash($node_hash) { + final protected function getAtomFromNodeHash($node_hash) { if (empty($this->atomMap[$node_hash])) { $dict = $this->atomCache->getAtom($node_hash); $this->atomMap[$node_hash] = DivinerAtom::newFromDictionary($dict); @@ -62,7 +62,7 @@ abstract class DivinerPublisher { return $this->atomMap[$node_hash]; } - protected final function getSimilarAtoms(DivinerAtom $atom) { + final protected function getSimilarAtoms(DivinerAtom $atom) { if ($this->symbolReverseMap === null) { $rmap = array(); $smap = $this->atomCache->getSymbolMap(); @@ -94,7 +94,7 @@ abstract class DivinerPublisher { * `f()`, we assign them an arbitrary (but fairly stable) order and publish * them as `function/f/1/`, `function/f/2/`, etc., or similar. */ - protected final function getAtomSimilarIndex(DivinerAtom $atom) { + final protected function getAtomSimilarIndex(DivinerAtom $atom) { $atoms = $this->getSimilarAtoms($atom); if (count($atoms) == 1) { return 0; @@ -116,7 +116,7 @@ abstract class DivinerPublisher { abstract protected function createDocumentsByHash(array $hashes); abstract public function findAtomByRef(DivinerAtomRef $ref); - public final function publishAtoms(array $hashes) { + final public function publishAtoms(array $hashes) { $existing = $this->loadAllPublishedHashes(); if ($this->dropCaches) { @@ -140,7 +140,7 @@ abstract class DivinerPublisher { $this->createDocumentsByHash($created); } - protected final function shouldGenerateDocumentForAtom(DivinerAtom $atom) { + final protected function shouldGenerateDocumentForAtom(DivinerAtom $atom) { switch ($atom->getType()) { case DivinerAtom::TYPE_METHOD: case DivinerAtom::TYPE_FILE: diff --git a/src/applications/diviner/query/DivinerAtomQuery.php b/src/applications/diviner/query/DivinerAtomQuery.php index 70759a2cc1..e01df2022d 100644 --- a/src/applications/diviner/query/DivinerAtomQuery.php +++ b/src/applications/diviner/query/DivinerAtomQuery.php @@ -9,8 +9,8 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery { private $types; private $contexts; private $indexes; - private $includeUndocumentable; - private $includeGhosts; + private $isDocumentable; + private $isGhost; private $nodeHashes; private $titles; private $nameContains; @@ -81,9 +81,9 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery { /** - * Include "ghosts", which are symbols which used to exist but do not exist - * currently (for example, a function which existed in an older version of - * the codebase but was deleted). + * Include or exclude "ghosts", which are symbols which used to exist but do + * not exist currently (for example, a function which existed in an older + * version of the codebase but was deleted). * * These symbols had PHIDs assigned to them, and may have other sorts of * metadata that we don't want to lose (like comments or flags), so we don't @@ -92,14 +92,11 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery { * have been generated incorrectly by accident. In these cases, we can * restore the original data. * - * However, most callers are not interested in these symbols, so they are - * excluded by default. You can use this method to include them in results. - * - * @param bool True to include ghosts. + * @param bool * @return this */ - public function withIncludeGhosts($include) { - $this->includeGhosts = $include; + public function withGhosts($ghosts) { + $this->isGhost = $ghosts; return $this; } @@ -108,8 +105,8 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery { return $this; } - public function withIncludeUndocumentable($include) { - $this->includeUndocumentable = $include; + public function withIsDocumentable($documentable) { + $this->isDocumentable = $documentable; return $this; } @@ -154,10 +151,6 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery { foreach ($atoms as $key => $atom) { $data = idx($atom_data, $atom->getPHID()); - if (!$data) { - unset($atoms[$key]); - continue; - } $atom->attachAtom($data); } } @@ -173,6 +166,10 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery { $names = array(); foreach ($atoms as $atom) { + if (!$atom->getAtom()) { + continue; + } + foreach ($atom->getAtom()->getExtends() as $xref) { $names[] = $xref->getName(); } @@ -192,10 +189,17 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery { } foreach ($atoms as $atom) { - $alang = $atom->getAtom()->getLanguage(); - $extends = array(); - foreach ($atom->getAtom()->getExtends() as $xref) { + $atom_lang = null; + $atom_extends = array(); + if ($atom->getAtom()) { + $atom_lang = $atom->getAtom()->getLanguage(); + $atom_extends = $atom->getAtom()->getExtends(); + } + + $extends = array(); + + foreach ($atom_extends as $xref) { // If there are no symbols of the matching name and type, we can't // resolve this. if (empty($xatoms[$xref->getName()][$xref->getType()])) { @@ -219,7 +223,7 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery { // classes can not implement JS classes. $same_lang = array(); foreach ($maybe as $xatom) { - if ($xatom->getAtom()->getLanguage() == $alang) { + if ($xatom->getAtom()->getLanguage() == $atom_lang) { $same_lang[] = $xatom; } } @@ -243,7 +247,6 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery { if ($child_hashes) { $children = id(new DivinerAtomQuery()) ->setViewer($this->getViewer()) - ->withIncludeUndocumentable(true) ->withNodeHashes($child_hashes) ->needAtoms($this->needAtoms) ->execute(); @@ -346,16 +349,19 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery { $this->indexes); } - if (!$this->includeUndocumentable) { + if ($this->isDocumentable !== null) { $where[] = qsprintf( $conn_r, - 'isDocumentable = 1'); + 'isDocumentable = %d', + (int)$this->isDocumentable); } - if (!$this->includeGhosts) { - $where[] = qsprintf( - $conn_r, - 'graphHash IS NOT NULL'); + if ($this->isGhost !== null) { + if ($this->isGhost) { + $where[] = qsprintf($conn_r, 'graphHash IS NULL'); + } else { + $where[] = qsprintf($conn_r, 'graphHash IS NOT NULL'); + } } if ($this->nodeHashes) { @@ -397,7 +403,13 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery { $hashes = array(); foreach ($symbols as $symbol) { - foreach ($symbol->getAtom()->getChildHashes() as $hash) { + $child_hashes = array(); + + if ($symbol->getAtom()) { + $child_hashes = $symbol->getAtom()->getChildHashes(); + } + + foreach ($child_hashes as $hash) { $hashes[$hash] = $hash; } if ($recurse_up) { @@ -427,8 +439,14 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery { assert_instances_of($children, 'DivinerLiveSymbol'); foreach ($symbols as $symbol) { + $child_hashes = array(); $symbol_children = array(); - foreach ($symbol->getAtom()->getChildHashes() as $hash) { + + if ($symbol->getAtom()) { + $child_hashes = $symbol->getAtom()->getChildHashes(); + } + + foreach ($child_hashes as $hash) { if (isset($children[$hash])) { $symbol_children[] = $children[$hash]; } diff --git a/src/applications/diviner/search/DivinerAtomSearchIndexer.php b/src/applications/diviner/search/DivinerAtomSearchIndexer.php new file mode 100644 index 0000000000..0e77c6e64c --- /dev/null +++ b/src/applications/diviner/search/DivinerAtomSearchIndexer.php @@ -0,0 +1,31 @@ +loadDocumentByPHID($phid); + $book = $atom->getBook(); + + $doc = $this->newDocument($phid) + ->setDocumentTitle($atom->getTitle()) + ->setDocumentCreated($book->getDateCreated()) + ->setDocumentModified($book->getDateModified()); + + $doc->addField( + PhabricatorSearchDocumentFieldType::FIELD_BODY, + $atom->getSummary()); + + $doc->addRelationship( + PhabricatorSearchRelationship::RELATIONSHIP_BOOK, + $atom->getBookPHID(), + DivinerBookPHIDType::TYPECONST, + $book->getDateCreated()); + + return $doc; + } + +} diff --git a/src/applications/diviner/search/DivinerBookSearchIndexer.php b/src/applications/diviner/search/DivinerBookSearchIndexer.php new file mode 100644 index 0000000000..b08042a4f8 --- /dev/null +++ b/src/applications/diviner/search/DivinerBookSearchIndexer.php @@ -0,0 +1,25 @@ +loadDocumentByPHID($phid); + + $doc = $this->newDocument($phid) + ->setDocumentTitle($book->getTitle()) + ->setDocumentCreated($book->getDateCreated()) + ->setDocumentModified($book->getDateModified()); + + $doc->addField( + PhabricatorSearchDocumentFieldType::FIELD_BODY, + $book->getPreface()); + + return $doc; + } + + +} diff --git a/src/applications/diviner/storage/DivinerLiveBook.php b/src/applications/diviner/storage/DivinerLiveBook.php index ef7a9bb277..ddb03e9d14 100644 --- a/src/applications/diviner/storage/DivinerLiveBook.php +++ b/src/applications/diviner/storage/DivinerLiveBook.php @@ -42,8 +42,7 @@ final class DivinerLiveBook extends DivinerDAO } public function generatePHID() { - return PhabricatorPHID::generateNewPHID( - DivinerBookPHIDType::TYPECONST); + return PhabricatorPHID::generateNewPHID(DivinerBookPHIDType::TYPECONST); } public function getTitle() { @@ -93,8 +92,6 @@ final class DivinerLiveBook extends DivinerDAO $atoms = id(new DivinerAtomQuery()) ->setViewer($engine->getViewer()) ->withBookPHIDs(array($this->getPHID())) - ->withIncludeGhosts(true) - ->withIncludeUndocumentable(true) ->execute(); foreach ($atoms as $atom) { diff --git a/src/applications/diviner/storage/DivinerLiveSymbol.php b/src/applications/diviner/storage/DivinerLiveSymbol.php index ba35eed255..dc53b24f08 100644 --- a/src/applications/diviner/storage/DivinerLiveSymbol.php +++ b/src/applications/diviner/storage/DivinerLiveSymbol.php @@ -98,8 +98,12 @@ final class DivinerLiveSymbol extends DivinerDAO return $this->assertAttached($this->atom); } - public function attachAtom(DivinerLiveAtom $atom) { - $this->atom = DivinerAtom::newFromDictionary($atom->getAtomData()); + public function attachAtom(DivinerLiveAtom $atom = null) { + if ($atom === null) { + $this->atom = null; + } else { + $this->atom = DivinerAtom::newFromDictionary($atom->getAtomData()); + } return $this; } @@ -229,6 +233,10 @@ final class DivinerLiveSymbol extends DivinerDAO public function getMarkupText($field) { + if (!$this->getAtom()) { + return; + } + return $this->getAtom()->getDocblockText(); } diff --git a/src/applications/doorkeeper/application/PhabricatorDoorkeeperApplication.php b/src/applications/doorkeeper/application/PhabricatorDoorkeeperApplication.php index 711899d679..b0e155a38e 100644 --- a/src/applications/doorkeeper/application/PhabricatorDoorkeeperApplication.php +++ b/src/applications/doorkeeper/application/PhabricatorDoorkeeperApplication.php @@ -24,8 +24,8 @@ final class PhabricatorDoorkeeperApplication extends PhabricatorApplication { public function getRemarkupRules() { return array( - new DoorkeeperRemarkupRuleAsana(), - new DoorkeeperRemarkupRuleJIRA(), + new DoorkeeperAsanaRemarkupRule(), + new DoorkeeperJIRARemarkupRule(), ); } diff --git a/src/applications/doorkeeper/engine/DoorkeeperObjectRef.php b/src/applications/doorkeeper/engine/DoorkeeperObjectRef.php index 0244532bcc..3cf2a8dbe2 100644 --- a/src/applications/doorkeeper/engine/DoorkeeperObjectRef.php +++ b/src/applications/doorkeeper/engine/DoorkeeperObjectRef.php @@ -29,8 +29,7 @@ final class DoorkeeperObjectRef extends Phobject { public function getExternalObject() { if (!$this->externalObject) { - throw new Exception( - 'Call attachExternalObject() before getExternalObject()!'); + throw new PhutilInvalidStateException('attachExternalObject'); } return $this->externalObject; } diff --git a/src/applications/doorkeeper/remarkup/DoorkeeperRemarkupRuleAsana.php b/src/applications/doorkeeper/remarkup/DoorkeeperAsanaRemarkupRule.php similarity index 94% rename from src/applications/doorkeeper/remarkup/DoorkeeperRemarkupRuleAsana.php rename to src/applications/doorkeeper/remarkup/DoorkeeperAsanaRemarkupRule.php index a2ce76d2e8..90bef7744f 100644 --- a/src/applications/doorkeeper/remarkup/DoorkeeperRemarkupRuleAsana.php +++ b/src/applications/doorkeeper/remarkup/DoorkeeperAsanaRemarkupRule.php @@ -1,6 +1,6 @@ endReadLocking(); if ($allocated) { $resource->saveTransaction(); - $this->log('Allocated Lease'); + $this->log(pht('Allocated Lease')); } else { $resource->killTransaction(); $this->log(pht('Failed to Allocate Lease')); diff --git a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php index 54d2b3b0f0..264394f8ac 100644 --- a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php @@ -80,7 +80,9 @@ final class DrydockWorkingCopyBlueprintImplementation $this->log(pht('Complete.')); $resource = $this->newResourceTemplate( - 'Working Copy ('.$repository->getCallsign().')'); + pht( + 'Working Copy (%s)', + $repository->getCallsign())); $resource->setStatus(DrydockResourceStatus::STATUS_OPEN); $resource->setAttribute('lease.host', $host_lease->getID()); $resource->setAttribute('path', $path); diff --git a/src/applications/drydock/controller/DrydockController.php b/src/applications/drydock/controller/DrydockController.php index 8f79c89897..c2fa3dae72 100644 --- a/src/applications/drydock/controller/DrydockController.php +++ b/src/applications/drydock/controller/DrydockController.php @@ -2,7 +2,7 @@ abstract class DrydockController extends PhabricatorController { - public abstract function buildSideNavView(); + abstract public function buildSideNavView(); public function buildApplicationMenu() { return $this->buildSideNavView()->getMenu(); diff --git a/src/applications/fact/controller/PhabricatorFactHomeController.php b/src/applications/fact/controller/PhabricatorFactHomeController.php index 700b2ef926..b0e24cb79f 100644 --- a/src/applications/fact/controller/PhabricatorFactHomeController.php +++ b/src/applications/fact/controller/PhabricatorFactHomeController.php @@ -109,7 +109,7 @@ final class PhabricatorFactHomeController extends PhabricatorFactController { ->setUser($user) ->appendChild( id(new AphrontFormSelectControl()) - ->setLabel('Y-Axis') + ->setLabel(pht('Y-Axis')) ->setName('y1') ->setOptions($options)) ->appendChild( diff --git a/src/applications/fact/daemon/PhabricatorFactDaemon.php b/src/applications/fact/daemon/PhabricatorFactDaemon.php index d60a77de6a..384bb56df4 100644 --- a/src/applications/fact/daemon/PhabricatorFactDaemon.php +++ b/src/applications/fact/daemon/PhabricatorFactDaemon.php @@ -9,6 +9,8 @@ final class PhabricatorFactDaemon extends PhabricatorDaemon { protected function run() { $this->setEngines(PhabricatorFactEngine::loadAllEngines()); while (!$this->shouldExit()) { + PhabricatorCaches::destroyRequestCache(); + $iterators = $this->getAllApplicationIterators(); foreach ($iterators as $iterator_name => $iterator) { $this->processIteratorWithCursor($iterator_name, $iterator); diff --git a/src/applications/files/query/PhabricatorFileQuery.php b/src/applications/files/query/PhabricatorFileQuery.php index 594dd60983..ba72156061 100644 --- a/src/applications/files/query/PhabricatorFileQuery.php +++ b/src/applications/files/query/PhabricatorFileQuery.php @@ -117,20 +117,12 @@ final class PhabricatorFileQuery return $this; } + public function newResultObject() { + return new PhabricatorFile(); + } + protected function loadPage() { - $table = new PhabricatorFile(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT f.* FROM %T f %Q %Q %Q %Q', - $table->getTableName(), - $this->buildJoinClause($conn_r), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - $files = $table->loadAllFromArray($data); + $files = $this->loadStandardPage(new PhabricatorFile()); if (!$files) { return $files; @@ -150,6 +142,14 @@ final class PhabricatorFileQuery foreach ($files as $file) { $phids = array_keys($edges[$file->getPHID()][$edge_type]); $file->attachObjectPHIDs($phids); + + if ($file->getIsProfileImage()) { + // If this is a profile image, don't bother loading related files. + // It will always be visible, and we can get into trouble if we try + // to load objects and end up stuck in a cycle. See T8478. + continue; + } + foreach ($phids as $phid) { $object_phids[$phid] = true; } @@ -218,49 +218,48 @@ final class PhabricatorFileQuery return $files; } - protected function buildJoinClause(AphrontDatabaseConnection $conn_r) { - $joins = array(); + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $joins = parent::buildJoinClauseParts($conn); if ($this->transforms) { $joins[] = qsprintf( - $conn_r, + $conn, 'JOIN %T t ON t.transformedPHID = f.phid', id(new PhabricatorTransformedFile())->getTableName()); } - return implode(' ', $joins); + return $joins; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); - - $where[] = $this->buildPagingClause($conn_r); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'f.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'f.phid IN (%Ls)', $this->phids); } if ($this->authorPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'f.authorPHID IN (%Ls)', $this->authorPHIDs); } if ($this->explicitUploads !== null) { $where[] = qsprintf( - $conn_r, - 'f.isExplicitUpload = true'); + $conn, + 'f.isExplicitUpload = %d', + (int)$this->explicitUploads); } if ($this->transforms !== null) { @@ -268,70 +267,70 @@ final class PhabricatorFileQuery foreach ($this->transforms as $transform) { if ($transform['transform'] === true) { $clauses[] = qsprintf( - $conn_r, + $conn, '(t.originalPHID = %s)', $transform['originalPHID']); } else { $clauses[] = qsprintf( - $conn_r, + $conn, '(t.originalPHID = %s AND t.transform = %s)', $transform['originalPHID'], $transform['transform']); } } - $where[] = qsprintf($conn_r, '(%Q)', implode(') OR (', $clauses)); + $where[] = qsprintf($conn, '(%Q)', implode(') OR (', $clauses)); } if ($this->dateCreatedAfter !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'f.dateCreated >= %d', $this->dateCreatedAfter); } if ($this->dateCreatedBefore !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'f.dateCreated <= %d', $this->dateCreatedBefore); } if ($this->contentHashes !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'f.contentHash IN (%Ls)', $this->contentHashes); } if ($this->minLength !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'byteSize >= %d', $this->minLength); } if ($this->maxLength !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'byteSize <= %d', $this->maxLength); } if ($this->names !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'name in (%Ls)', $this->names); } if ($this->isPartial !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'isPartial = %d', (int)$this->isPartial); } - return $this->formatWhereClause($where); + return $where; } protected function getPrimaryTableAlias() { diff --git a/src/applications/files/query/PhabricatorFileSearchEngine.php b/src/applications/files/query/PhabricatorFileSearchEngine.php index 86df8b7b6d..57db0223bd 100644 --- a/src/applications/files/query/PhabricatorFileSearchEngine.php +++ b/src/applications/files/query/PhabricatorFileSearchEngine.php @@ -11,76 +11,62 @@ final class PhabricatorFileSearchEngine return 'PhabricatorFilesApplication'; } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - $saved->setParameter( - 'authorPHIDs', - $this->readUsersFromRequest($request, 'authors')); - - $saved->setParameter('explicit', $request->getBool('explicit')); - $saved->setParameter('createdStart', $request->getStr('createdStart')); - $saved->setParameter('createdEnd', $request->getStr('createdEnd')); - - return $saved; + public function newQuery() { + return new PhabricatorFileQuery(); } - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new PhabricatorFileQuery()); + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorSearchUsersField()) + ->setKey('authorPHIDs') + ->setAliases(array('author', 'authors')) + ->setLabel(pht('Authors')), + id(new PhabricatorSearchThreeStateField()) + ->setKey('explicit') + ->setLabel(pht('Upload Source')) + ->setOptions( + pht('(Show All)'), + pht('Show Only Manually Uploaded Files'), + pht('Hide Manually Uploaded Files')), + id(new PhabricatorSearchDateField()) + ->setKey('createdStart') + ->setLabel(pht('Created After')), + id(new PhabricatorSearchDateField()) + ->setKey('createdEnd') + ->setLabel(pht('Created Before')), + ); + } - $author_phids = $saved->getParameter('authorPHIDs', array()); - if ($author_phids) { - $query->withAuthorPHIDs($author_phids); + protected function getDefaultFieldOrder() { + return array( + '...', + 'createdStart', + 'createdEnd', + ); + } + + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); + + if ($map['authorPHIDs']) { + $query->withAuthorPHIDs($map['authorPHIDs']); } - if ($saved->getParameter('explicit')) { - $query->showOnlyExplicitUploads(true); + if ($map['explicit'] !== null) { + $query->showOnlyExplicitUploads($map['explicit']); } - $start = $this->parseDateTime($saved->getParameter('createdStart')); - $end = $this->parseDateTime($saved->getParameter('createdEnd')); - - if ($start) { - $query->withDateCreatedAfter($start); + if ($map['createdStart']) { + $query->withDateCreatedAfter($map['createdStart']); } - if ($end) { - $query->withDateCreatedBefore($end); + if ($map['createdEnd']) { + $query->withDateCreatedBefore($map['createdEnd']); } return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved_query) { - - $author_phids = $saved_query->getParameter('authorPHIDs', array()); - $explicit = $saved_query->getParameter('explicit'); - - $form - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorPeopleDatasource()) - ->setName('authors') - ->setLabel(pht('Authors')) - ->setValue($author_phids)) - ->appendChild( - id(new AphrontFormCheckboxControl()) - ->addCheckbox( - 'explicit', - 1, - pht('Show only manually uploaded files.'), - $explicit)); - - $this->buildDateRange( - $form, - $saved_query, - 'createdStart', - pht('Created After'), - 'createdEnd', - pht('Created Before')); - } - protected function getURI($path) { return '/file/'.$path; } diff --git a/src/applications/harbormaster/controller/HarbormasterPlanRunController.php b/src/applications/harbormaster/controller/HarbormasterPlanRunController.php index cb6a90d626..e4f94e05d1 100644 --- a/src/applications/harbormaster/controller/HarbormasterPlanRunController.php +++ b/src/applications/harbormaster/controller/HarbormasterPlanRunController.php @@ -82,7 +82,7 @@ final class HarbormasterPlanRunController extends HarbormasterController { $plan->getID())) ->appendChild( id(new AphrontFormTextControl()) - ->setLabel('Buildable Name') + ->setLabel(pht('Buildable Name')) ->setName('buildablePHID') ->setError($e_name) ->setValue($v_name)); diff --git a/src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php b/src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php index 4123941665..9caf01f54e 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php +++ b/src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php @@ -28,55 +28,46 @@ final class HarbormasterBuildPlanQuery return $this; } - protected function loadPage() { - $table = new HarbormasterBuildPlan(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT * FROM %T %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - return $table->loadAllFromArray($data); + public function newResultObject() { + return new HarbormasterBuildPlan(); } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } - if ($this->ids) { + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } - if ($this->phids) { + if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } - if ($this->statuses) { + if ($this->statuses !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'planStatus IN (%Ls)', $this->statuses); } if (strlen($this->datasourceQuery)) { $where[] = qsprintf( - $conn_r, + $conn, 'name LIKE %>', $this->datasourceQuery); } - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); + return $where; } public function getQueryApplicationClass() { diff --git a/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php b/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php index 0ab920a905..7555fdc258 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php +++ b/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php @@ -11,57 +11,34 @@ final class HarbormasterBuildPlanSearchEngine return 'PhabricatorHarbormasterApplication'; } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - - $saved->setParameter( - 'status', - $this->readListFromRequest($request, 'status')); - - $this->saveQueryOrder($saved, $request); - - return $saved; + public function newQuery() { + return new HarbormasterBuildPlanQuery(); } - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new HarbormasterBuildPlanQuery()); - $this->setQueryOrder($query, $saved); + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorSearchCheckboxesField()) + ->setLabel(pht('Status')) + ->setKey('status') + ->setAliases(array('statuses')) + ->setOptions( + array( + HarbormasterBuildPlan::STATUS_ACTIVE => pht('Active'), + HarbormasterBuildPlan::STATUS_DISABLED => pht('Disabled'), + )), + ); + } - $status = $saved->getParameter('status', array()); - if ($status) { - $query->withStatuses($status); + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); + + if ($map['status']) { + $query->withStatuses($map['status']); } return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved) { - - $status = $saved->getParameter('status', array()); - - $form - ->appendChild( - id(new AphrontFormCheckboxControl()) - ->setLabel('Status') - ->addCheckbox( - 'status[]', - HarbormasterBuildPlan::STATUS_ACTIVE, - pht('Active'), - in_array(HarbormasterBuildPlan::STATUS_ACTIVE, $status)) - ->addCheckbox( - 'status[]', - HarbormasterBuildPlan::STATUS_DISABLED, - pht('Disabled'), - in_array(HarbormasterBuildPlan::STATUS_DISABLED, $status))); - - $this->appendOrderFieldsToForm( - $form, - $saved, - new HarbormasterBuildPlanQuery()); - } - protected function getURI($path) { return '/harbormaster/plan/'.$path; } diff --git a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php index 4fe1128ac0..5262366ac9 100644 --- a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php @@ -72,7 +72,7 @@ abstract class HarbormasterBuildStepImplementation { * Loads the settings for this build step implementation from a build * step or target. */ - public final function loadSettings($build_object) { + final public function loadSettings($build_object) { $this->settings = $build_object->getDetails(); return $this; } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php b/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php index 47df26131c..f96ed6243c 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php @@ -77,7 +77,7 @@ final class HarbormasterBuildArtifact extends HarbormasterDAO ->setViewer($viewer) ->withIDs(array($data['drydock-lease'])) ->execute(); - $lease = $leases[$data['drydock-lease']]; + $lease = idx($leases, $data['drydock-lease']); return id(new PHUIObjectItemView()) ->setObjectName(pht('Drydock Lease')) diff --git a/src/applications/herald/adapter/HeraldAdapter.php b/src/applications/herald/adapter/HeraldAdapter.php index f494ad95fc..f50bdf4203 100644 --- a/src/applications/herald/adapter/HeraldAdapter.php +++ b/src/applications/herald/adapter/HeraldAdapter.php @@ -110,6 +110,7 @@ abstract class HeraldAdapter { private $queuedTransactions = array(); private $emailPHIDs = array(); private $forcedEmailPHIDs = array(); + private $unsubscribedPHIDs; public function getEmailPHIDs() { return array_values($this->emailPHIDs); @@ -196,6 +197,20 @@ abstract class HeraldAdapter { return true; case self::FIELD_IS_NEW_OBJECT: return $this->getIsNewObject(); + case self::FIELD_CC: + $object = $this->getObject(); + + if (!($object instanceof PhabricatorSubscribableInterface)) { + throw new Exception( + pht( + 'Adapter object (of class "%s") does not implement interface '. + '"%s", so the subscribers field value can not be determined.', + get_class($object), + 'PhabricatorSubscribableInterface')); + } + + $phid = $object->getPHID(); + return PhabricatorSubscribersQuery::loadSubscribersForPHID($phid); case self::FIELD_APPLICATION_EMAIL: $value = array(); // while there is only one match by implementation, we do set @@ -1541,10 +1556,15 @@ abstract class HeraldAdapter { case self::ACTION_ADD_PROJECTS: case self::ACTION_REMOVE_PROJECTS: return $this->applyProjectsEffect($effect); + case self::ACTION_ADD_CC: + case self::ACTION_REMOVE_CC: + return $this->applySubscribersEffect($effect); case self::ACTION_FLAG: return $this->applyFlagEffect($effect); case self::ACTION_EMAIL: return $this->applyEmailEffect($effect); + case self::ACTION_NOTHING: + return $this->applyNothingEffect($effect); default: break; } @@ -1563,6 +1583,12 @@ abstract class HeraldAdapter { return $result; } + private function applyNothingEffect(HeraldEffect $effect) { + return new HeraldApplyTranscript( + $effect, + true, + pht('Did nothing.')); + } /** * @task apply @@ -1593,6 +1619,97 @@ abstract class HeraldAdapter { pht('Added projects.')); } + /** + * @task apply + */ + private function applySubscribersEffect(HeraldEffect $effect) { + if ($effect->getAction() == self::ACTION_ADD_CC) { + $kind = '+'; + $is_add = true; + } else { + $kind = '-'; + $is_add = false; + } + + $subscriber_phids = array_fuse($effect->getTarget()); + if (!$subscriber_phids) { + return new HeraldApplyTranscript( + $effect, + false, + pht('This action lists no users or objects to affect.')); + } + + // The "Add Subscribers" rule only adds subscribers who haven't previously + // unsubscribed from the object explicitly. Filter these subscribers out + // before continuing. + $unsubscribed = array(); + if ($is_add) { + if ($this->unsubscribedPHIDs === null) { + $this->unsubscribedPHIDs = PhabricatorEdgeQuery::loadDestinationPHIDs( + $this->getObject()->getPHID(), + PhabricatorObjectHasUnsubscriberEdgeType::EDGECONST); + } + + foreach ($this->unsubscribedPHIDs as $phid) { + if (isset($subscriber_phids[$phid])) { + $unsubscribed[$phid] = $phid; + unset($subscriber_phids[$phid]); + } + } + } + + if (!$subscriber_phids) { + return new HeraldApplyTranscript( + $effect, + false, + pht('All targets have previously unsubscribed explicitly.')); + } + + // Filter out PHIDs which aren't valid subscribers. Lower levels of the + // stack will fail loudly if we try to add subscribers with invalid PHIDs + // or unknown PHID types, so drop them here. + $invalid = array(); + foreach ($subscriber_phids as $phid) { + $type = phid_get_type($phid); + switch ($type) { + case PhabricatorPeopleUserPHIDType::TYPECONST: + case PhabricatorProjectProjectPHIDType::TYPECONST: + break; + default: + $invalid[$phid] = $phid; + unset($subscriber_phids[$phid]); + break; + } + } + + if (!$subscriber_phids) { + return new HeraldApplyTranscript( + $effect, + false, + pht('All targets are invalid as subscribers.')); + } + + $xaction = $this->newTransaction() + ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS) + ->setNewValue( + array( + $kind => $subscriber_phids, + )); + + $this->queueTransaction($xaction); + + // TODO: We could be more detailed about this, but doing it meaningfully + // probably requires substantial changes to how transactions are rendered + // first. + if ($is_add) { + $message = pht('Subscribed targets.'); + } else { + $message = pht('Unsubscribed targets.'); + } + + return new HeraldApplyTranscript($effect, true, $message); + } + /** * @task apply diff --git a/src/applications/herald/adapter/HeraldCommitAdapter.php b/src/applications/herald/adapter/HeraldCommitAdapter.php index e26da09ab4..b420ddb57a 100644 --- a/src/applications/herald/adapter/HeraldCommitAdapter.php +++ b/src/applications/herald/adapter/HeraldCommitAdapter.php @@ -13,7 +13,6 @@ final class HeraldCommitAdapter extends HeraldAdapter { protected $commitData; private $commitDiff; - protected $addCCPHIDs = array(); protected $auditMap = array(); protected $buildPlans = array(); @@ -141,6 +140,7 @@ final class HeraldCommitAdapter extends HeraldAdapter { return array_merge( array( self::ACTION_ADD_CC, + self::ACTION_REMOVE_CC, self::ACTION_EMAIL, self::ACTION_AUDIT, self::ACTION_APPLY_BUILD_PLANS, @@ -151,6 +151,7 @@ final class HeraldCommitAdapter extends HeraldAdapter { return array_merge( array( self::ACTION_ADD_CC, + self::ACTION_REMOVE_CC, self::ACTION_EMAIL, self::ACTION_FLAG, self::ACTION_AUDIT, @@ -220,10 +221,6 @@ final class HeraldCommitAdapter extends HeraldAdapter { return $this->commit->getPHID(); } - public function getAddCCMap() { - return $this->addCCPHIDs; - } - public function getAuditMap() { return $this->auditMap; } @@ -491,24 +488,6 @@ final class HeraldCommitAdapter extends HeraldAdapter { foreach ($effects as $effect) { $action = $effect->getAction(); switch ($action) { - case self::ACTION_NOTHING: - $result[] = new HeraldApplyTranscript( - $effect, - true, - pht('Great success at doing nothing.')); - break; - case self::ACTION_ADD_CC: - foreach ($effect->getTarget() as $phid) { - if (empty($this->addCCPHIDs[$phid])) { - $this->addCCPHIDs[$phid] = array(); - } - $this->addCCPHIDs[$phid][] = $effect->getRule()->getID(); - } - $result[] = new HeraldApplyTranscript( - $effect, - true, - pht('Added address to CC.')); - break; case self::ACTION_AUDIT: foreach ($effect->getTarget() as $phid) { if (empty($this->auditMap[$phid])) { diff --git a/src/applications/herald/adapter/HeraldDifferentialDiffAdapter.php b/src/applications/herald/adapter/HeraldDifferentialDiffAdapter.php index 2a7a576e3e..c4c6587167 100644 --- a/src/applications/herald/adapter/HeraldDifferentialDiffAdapter.php +++ b/src/applications/herald/adapter/HeraldDifferentialDiffAdapter.php @@ -77,7 +77,7 @@ final class HeraldDifferentialDiffAdapter extends HeraldDifferentialAdapter { public function getActionNameMap($rule_type) { return array( self::ACTION_BLOCK => pht('Block diff with message'), - ); + ) + parent::getActionNameMap($rule_type); } public function getHeraldField($field) { @@ -141,12 +141,6 @@ final class HeraldDifferentialDiffAdapter extends HeraldDifferentialAdapter { foreach ($effects as $effect) { $action = $effect->getAction(); switch ($action) { - case self::ACTION_NOTHING: - $result[] = new HeraldApplyTranscript( - $effect, - true, - pht('Did nothing.')); - break; case self::ACTION_BLOCK: $result[] = new HeraldApplyTranscript( $effect, @@ -154,10 +148,8 @@ final class HeraldDifferentialDiffAdapter extends HeraldDifferentialAdapter { pht('Blocked diff.')); break; default: - $result[] = new HeraldApplyTranscript( - $effect, - false, - pht('No rules to handle action "%s"!', $action)); + $result[] = $this->applyStandardEffect($effect); + break; } } diff --git a/src/applications/herald/adapter/HeraldDifferentialRevisionAdapter.php b/src/applications/herald/adapter/HeraldDifferentialRevisionAdapter.php index e7ba56cf06..afd328b342 100644 --- a/src/applications/herald/adapter/HeraldDifferentialRevisionAdapter.php +++ b/src/applications/herald/adapter/HeraldDifferentialRevisionAdapter.php @@ -5,12 +5,7 @@ final class HeraldDifferentialRevisionAdapter protected $revision; - protected $explicitCCs; protected $explicitReviewers; - protected $forbiddenCCs; - - protected $newCCs = array(); - protected $remCCs = array(); protected $addReviewerPHIDs = array(); protected $blockingReviewerPHIDs = array(); protected $buildPlans = array(); @@ -110,29 +105,11 @@ final class HeraldDifferentialRevisionAdapter return $object; } - public function setExplicitCCs($explicit_ccs) { - $this->explicitCCs = $explicit_ccs; - return $this; - } - public function setExplicitReviewers($explicit_reviewers) { $this->explicitReviewers = $explicit_reviewers; return $this; } - public function setForbiddenCCs($forbidden_ccs) { - $this->forbiddenCCs = $forbidden_ccs; - return $this; - } - - public function getCCsAddedByHerald() { - return array_diff_key($this->newCCs, $this->remCCs); - } - - public function getCCsRemovedByHerald() { - return $this->remCCs; - } - public function getReviewersAddedByHerald() { return $this->addReviewerPHIDs; } @@ -221,12 +198,6 @@ final class HeraldDifferentialRevisionAdapter return mpull($projects, 'getPHID'); case self::FIELD_DIFF_FILE: return $this->loadAffectedPaths(); - case self::FIELD_CC: - if (isset($this->explicitCCs)) { - return array_keys($this->explicitCCs); - } else { - return $this->revision->getCCPHIDs(); - } case self::FIELD_REVIEWERS: if (isset($this->explicitReviewers)) { return array_keys($this->explicitReviewers); @@ -297,77 +268,10 @@ final class HeraldDifferentialRevisionAdapter assert_instances_of($effects, 'HeraldEffect'); $result = array(); - if ($this->explicitCCs) { - $effect = new HeraldEffect(); - $effect->setAction(self::ACTION_ADD_CC); - $effect->setTarget(array_keys($this->explicitCCs)); - $effect->setReason( - pht( - 'CCs provided explicitly by revision author or carried over '. - 'from a previous version of the revision.')); - $result[] = new HeraldApplyTranscript( - $effect, - true, - pht('Added addresses to CC list.')); - } - - $forbidden_ccs = array_fill_keys( - nonempty($this->forbiddenCCs, array()), - true); foreach ($effects as $effect) { $action = $effect->getAction(); switch ($action) { - case self::ACTION_NOTHING: - $result[] = new HeraldApplyTranscript( - $effect, - true, - pht('OK, did nothing.')); - break; - case self::ACTION_ADD_CC: - $base_target = $effect->getTarget(); - $forbidden = array(); - foreach ($base_target as $key => $fbid) { - if (isset($forbidden_ccs[$fbid])) { - $forbidden[] = $fbid; - unset($base_target[$key]); - } else { - $this->newCCs[$fbid] = true; - } - } - - if ($forbidden) { - $failed = clone $effect; - $failed->setTarget($forbidden); - if ($base_target) { - $effect->setTarget($base_target); - $result[] = new HeraldApplyTranscript( - $effect, - true, - pht( - 'Added these addresses to CC list. '. - 'Others could not be added.')); - } - $result[] = new HeraldApplyTranscript( - $failed, - false, - pht('CC forbidden, these addresses have unsubscribed.')); - } else { - $result[] = new HeraldApplyTranscript( - $effect, - true, - pht('Added addresses to CC list.')); - } - break; - case self::ACTION_REMOVE_CC: - foreach ($effect->getTarget() as $fbid) { - $this->remCCs[$fbid] = true; - } - $result[] = new HeraldApplyTranscript( - $effect, - true, - pht('Removed addresses from CC list.')); - break; case self::ACTION_ADD_REVIEWERS: foreach ($effect->getTarget() as $phid) { $this->addReviewerPHIDs[$phid] = true; diff --git a/src/applications/herald/adapter/HeraldManiphestTaskAdapter.php b/src/applications/herald/adapter/HeraldManiphestTaskAdapter.php index cb4c7988c0..a84130095a 100644 --- a/src/applications/herald/adapter/HeraldManiphestTaskAdapter.php +++ b/src/applications/herald/adapter/HeraldManiphestTaskAdapter.php @@ -3,7 +3,6 @@ final class HeraldManiphestTaskAdapter extends HeraldAdapter { private $task; - private $ccPHIDs = array(); private $assignPHID; protected function newObject() { @@ -48,14 +47,6 @@ final class HeraldManiphestTaskAdapter extends HeraldAdapter { return $this->task; } - private function setCcPHIDs(array $cc_phids) { - $this->ccPHIDs = $cc_phids; - return $this; - } - public function getCcPHIDs() { - return $this->ccPHIDs; - } - public function setAssignPHID($assign_phid) { $this->assignPHID = $assign_phid; return $this; @@ -92,6 +83,7 @@ final class HeraldManiphestTaskAdapter extends HeraldAdapter { return array_merge( array( self::ACTION_ADD_CC, + self::ACTION_REMOVE_CC, self::ACTION_EMAIL, self::ACTION_ASSIGN_TASK, self::ACTION_NOTHING, @@ -101,6 +93,7 @@ final class HeraldManiphestTaskAdapter extends HeraldAdapter { return array_merge( array( self::ACTION_ADD_CC, + self::ACTION_REMOVE_CC, self::ACTION_EMAIL, self::ACTION_FLAG, self::ACTION_ASSIGN_TASK, @@ -128,9 +121,6 @@ final class HeraldManiphestTaskAdapter extends HeraldAdapter { return $this->getTask()->getAuthorPHID(); case self::FIELD_ASSIGNEE: return $this->getTask()->getOwnerPHID(); - case self::FIELD_CC: - return PhabricatorSubscribersQuery::loadSubscribersForPHID( - $this->getTask()->getPHID()); case self::FIELD_PROJECTS: return PhabricatorEdgeQuery::loadDestinationPHIDs( $this->getTask()->getPHID(), @@ -151,21 +141,6 @@ final class HeraldManiphestTaskAdapter extends HeraldAdapter { foreach ($effects as $effect) { $action = $effect->getAction(); switch ($action) { - case self::ACTION_NOTHING: - $result[] = new HeraldApplyTranscript( - $effect, - true, - pht('Great success at doing nothing.')); - break; - case self::ACTION_ADD_CC: - foreach ($effect->getTarget() as $phid) { - $this->ccPHIDs[] = $phid; - } - $result[] = new HeraldApplyTranscript( - $effect, - true, - pht('Added addresses to cc list.')); - break; case self::ACTION_ASSIGN_TASK: $target_array = $effect->getTarget(); $assign_phid = reset($target_array); diff --git a/src/applications/herald/adapter/HeraldPholioMockAdapter.php b/src/applications/herald/adapter/HeraldPholioMockAdapter.php index 0b73b6cfc2..e4aab69e95 100644 --- a/src/applications/herald/adapter/HeraldPholioMockAdapter.php +++ b/src/applications/herald/adapter/HeraldPholioMockAdapter.php @@ -3,7 +3,6 @@ final class HeraldPholioMockAdapter extends HeraldAdapter { private $mock; - private $ccPHIDs = array(); public function getAdapterApplicationClass() { return 'PhabricatorPholioApplication'; @@ -29,14 +28,6 @@ final class HeraldPholioMockAdapter extends HeraldAdapter { return $this->mock; } - private function setCcPHIDs(array $cc_phids) { - $this->ccPHIDs = $cc_phids; - return $this; - } - public function getCcPHIDs() { - return $this->ccPHIDs; - } - public function getAdapterContentName() { return pht('Pholio Mocks'); } @@ -71,6 +62,7 @@ final class HeraldPholioMockAdapter extends HeraldAdapter { return array_merge( array( self::ACTION_ADD_CC, + self::ACTION_REMOVE_CC, self::ACTION_NOTHING, ), parent::getActions($rule_type)); @@ -78,6 +70,7 @@ final class HeraldPholioMockAdapter extends HeraldAdapter { return array_merge( array( self::ACTION_ADD_CC, + self::ACTION_REMOVE_CC, self::ACTION_FLAG, self::ACTION_NOTHING, ), @@ -101,9 +94,6 @@ final class HeraldPholioMockAdapter extends HeraldAdapter { return $this->getMock()->getDescription(); case self::FIELD_AUTHOR: return $this->getMock()->getAuthorPHID(); - case self::FIELD_CC: - return PhabricatorSubscribersQuery::loadSubscribersForPHID( - $this->getMock()->getPHID()); case self::FIELD_PROJECTS: return PhabricatorEdgeQuery::loadDestinationPHIDs( $this->getMock()->getPHID(), @@ -120,21 +110,6 @@ final class HeraldPholioMockAdapter extends HeraldAdapter { foreach ($effects as $effect) { $action = $effect->getAction(); switch ($action) { - case self::ACTION_NOTHING: - $result[] = new HeraldApplyTranscript( - $effect, - true, - pht('Great success at doing nothing.')); - break; - case self::ACTION_ADD_CC: - foreach ($effect->getTarget() as $phid) { - $this->ccPHIDs[] = $phid; - } - $result[] = new HeraldApplyTranscript( - $effect, - true, - pht('Added address to cc list.')); - break; default: $result[] = $this->applyStandardEffect($effect); break; diff --git a/src/applications/herald/extension/HeraldCustomAction.php b/src/applications/herald/extension/HeraldCustomAction.php index 07638e0ef1..6d38494fc0 100644 --- a/src/applications/herald/extension/HeraldCustomAction.php +++ b/src/applications/herald/extension/HeraldCustomAction.php @@ -2,17 +2,17 @@ abstract class HeraldCustomAction { - public abstract function appliesToAdapter(HeraldAdapter $adapter); + abstract public function appliesToAdapter(HeraldAdapter $adapter); - public abstract function appliesToRuleType($rule_type); + abstract public function appliesToRuleType($rule_type); - public abstract function getActionKey(); + abstract public function getActionKey(); - public abstract function getActionName(); + abstract public function getActionName(); - public abstract function getActionType(); + abstract public function getActionType(); - public abstract function applyEffect( + abstract public function applyEffect( HeraldAdapter $adapter, $object, HeraldEffect $effect); diff --git a/src/applications/herald/storage/transcript/HeraldTranscript.php b/src/applications/herald/storage/transcript/HeraldTranscript.php index 749bbf633a..8b458c93ff 100644 --- a/src/applications/herald/storage/transcript/HeraldTranscript.php +++ b/src/applications/herald/storage/transcript/HeraldTranscript.php @@ -183,9 +183,9 @@ final class HeraldTranscript extends HeraldDAO public function getMetadataMap() { return array( - 'Run At Epoch' => date('F jS, g:i:s A', $this->time), - 'Run On Host' => $this->host, - 'Run Duration' => (int)(1000 * $this->duration).' ms', + pht('Run At Epoch') => date('F jS, g:i:s A', $this->time), + pht('Run On Host') => $this->host, + pht('Run Duration') => (int)(1000 * $this->duration).' ms', ); } diff --git a/src/applications/legalpad/constants/LegalpadConstants.php b/src/applications/legalpad/constants/LegalpadConstants.php deleted file mode 100644 index c2f54b4f00..0000000000 --- a/src/applications/legalpad/constants/LegalpadConstants.php +++ /dev/null @@ -1,3 +0,0 @@ -setTransactionType(LegalpadTransactionType::TYPE_TITLE) + ->setTransactionType(LegalpadTransaction::TYPE_TITLE) ->setNewValue($title); } @@ -67,7 +67,7 @@ final class LegalpadDocumentEditController extends LegalpadController { $errors[] = pht('The document may not be blank.'); } else { $xactions[] = id(new LegalpadTransaction()) - ->setTransactionType(LegalpadTransactionType::TYPE_TEXT) + ->setTransactionType(LegalpadTransaction::TYPE_TEXT) ->setNewValue($text); } @@ -83,13 +83,13 @@ final class LegalpadDocumentEditController extends LegalpadController { if ($is_create) { $v_signature_type = $request->getStr('signatureType'); $xactions[] = id(new LegalpadTransaction()) - ->setTransactionType(LegalpadTransactionType::TYPE_SIGNATURE_TYPE) + ->setTransactionType(LegalpadTransaction::TYPE_SIGNATURE_TYPE) ->setNewValue($v_signature_type); } $v_preamble = $request->getStr('preamble'); $xactions[] = id(new LegalpadTransaction()) - ->setTransactionType(LegalpadTransactionType::TYPE_PREAMBLE) + ->setTransactionType(LegalpadTransaction::TYPE_PREAMBLE) ->setNewValue($v_preamble); $v_require_signature = $request->getBool('requireSignature', 0); @@ -106,7 +106,7 @@ final class LegalpadDocumentEditController extends LegalpadController { } if ($user->getIsAdmin()) { $xactions[] = id(new LegalpadTransaction()) - ->setTransactionType(LegalpadTransactionType::TYPE_REQUIRE_SIGNATURE) + ->setTransactionType(LegalpadTransaction::TYPE_REQUIRE_SIGNATURE) ->setNewValue($v_require_signature); } diff --git a/src/applications/legalpad/editor/LegalpadDocumentEditor.php b/src/applications/legalpad/editor/LegalpadDocumentEditor.php index 7b0f50b462..27207a3aa2 100644 --- a/src/applications/legalpad/editor/LegalpadDocumentEditor.php +++ b/src/applications/legalpad/editor/LegalpadDocumentEditor.php @@ -28,11 +28,11 @@ final class LegalpadDocumentEditor $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; - $types[] = LegalpadTransactionType::TYPE_TITLE; - $types[] = LegalpadTransactionType::TYPE_TEXT; - $types[] = LegalpadTransactionType::TYPE_SIGNATURE_TYPE; - $types[] = LegalpadTransactionType::TYPE_PREAMBLE; - $types[] = LegalpadTransactionType::TYPE_REQUIRE_SIGNATURE; + $types[] = LegalpadTransaction::TYPE_TITLE; + $types[] = LegalpadTransaction::TYPE_TEXT; + $types[] = LegalpadTransaction::TYPE_SIGNATURE_TYPE; + $types[] = LegalpadTransaction::TYPE_PREAMBLE; + $types[] = LegalpadTransaction::TYPE_REQUIRE_SIGNATURE; return $types; } @@ -42,15 +42,15 @@ final class LegalpadDocumentEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case LegalpadTransactionType::TYPE_TITLE: + case LegalpadTransaction::TYPE_TITLE: return $object->getDocumentBody()->getTitle(); - case LegalpadTransactionType::TYPE_TEXT: + case LegalpadTransaction::TYPE_TEXT: return $object->getDocumentBody()->getText(); - case LegalpadTransactionType::TYPE_SIGNATURE_TYPE: + case LegalpadTransaction::TYPE_SIGNATURE_TYPE: return $object->getSignatureType(); - case LegalpadTransactionType::TYPE_PREAMBLE: + case LegalpadTransaction::TYPE_PREAMBLE: return $object->getPreamble(); - case LegalpadTransactionType::TYPE_REQUIRE_SIGNATURE: + case LegalpadTransaction::TYPE_REQUIRE_SIGNATURE: return (bool)$object->getRequireSignature(); } } @@ -60,12 +60,12 @@ final class LegalpadDocumentEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case LegalpadTransactionType::TYPE_TITLE: - case LegalpadTransactionType::TYPE_TEXT: - case LegalpadTransactionType::TYPE_SIGNATURE_TYPE: - case LegalpadTransactionType::TYPE_PREAMBLE: + case LegalpadTransaction::TYPE_TITLE: + case LegalpadTransaction::TYPE_TEXT: + case LegalpadTransaction::TYPE_SIGNATURE_TYPE: + case LegalpadTransaction::TYPE_PREAMBLE: return $xaction->getNewValue(); - case LegalpadTransactionType::TYPE_REQUIRE_SIGNATURE: + case LegalpadTransaction::TYPE_REQUIRE_SIGNATURE: return (bool)$xaction->getNewValue(); } } @@ -75,24 +75,24 @@ final class LegalpadDocumentEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case LegalpadTransactionType::TYPE_TITLE: + case LegalpadTransaction::TYPE_TITLE: $object->setTitle($xaction->getNewValue()); $body = $object->getDocumentBody(); $body->setTitle($xaction->getNewValue()); $this->setIsContribution(true); break; - case LegalpadTransactionType::TYPE_TEXT: + case LegalpadTransaction::TYPE_TEXT: $body = $object->getDocumentBody(); $body->setText($xaction->getNewValue()); $this->setIsContribution(true); break; - case LegalpadTransactionType::TYPE_SIGNATURE_TYPE: + case LegalpadTransaction::TYPE_SIGNATURE_TYPE: $object->setSignatureType($xaction->getNewValue()); break; - case LegalpadTransactionType::TYPE_PREAMBLE: + case LegalpadTransaction::TYPE_PREAMBLE: $object->setPreamble($xaction->getNewValue()); break; - case LegalpadTransactionType::TYPE_REQUIRE_SIGNATURE: + case LegalpadTransaction::TYPE_REQUIRE_SIGNATURE: $object->setRequireSignature((int)$xaction->getNewValue()); break; } @@ -103,7 +103,7 @@ final class LegalpadDocumentEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case LegalpadTransactionType::TYPE_REQUIRE_SIGNATURE: + case LegalpadTransaction::TYPE_REQUIRE_SIGNATURE: if ($xaction->getNewValue()) { $session = new PhabricatorAuthSession(); queryfx( @@ -154,11 +154,11 @@ final class LegalpadDocumentEditor $type = $u->getTransactionType(); switch ($type) { - case LegalpadTransactionType::TYPE_TITLE: - case LegalpadTransactionType::TYPE_TEXT: - case LegalpadTransactionType::TYPE_SIGNATURE_TYPE: - case LegalpadTransactionType::TYPE_PREAMBLE: - case LegalpadTransactionType::TYPE_REQUIRE_SIGNATURE: + case LegalpadTransaction::TYPE_TITLE: + case LegalpadTransaction::TYPE_TEXT: + case LegalpadTransaction::TYPE_SIGNATURE_TYPE: + case LegalpadTransaction::TYPE_PREAMBLE: + case LegalpadTransaction::TYPE_REQUIRE_SIGNATURE: return $v; } @@ -200,10 +200,10 @@ final class LegalpadDocumentEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case LegalpadTransactionType::TYPE_TEXT: - case LegalpadTransactionType::TYPE_TITLE: - case LegalpadTransactionType::TYPE_PREAMBLE: - case LegalpadTransactionType::TYPE_REQUIRE_SIGNATURE: + case LegalpadTransaction::TYPE_TEXT: + case LegalpadTransaction::TYPE_TITLE: + case LegalpadTransaction::TYPE_PREAMBLE: + case LegalpadTransaction::TYPE_REQUIRE_SIGNATURE: return true; } diff --git a/src/applications/legalpad/storage/LegalpadTransaction.php b/src/applications/legalpad/storage/LegalpadTransaction.php index 37c81148f3..f85c569279 100644 --- a/src/applications/legalpad/storage/LegalpadTransaction.php +++ b/src/applications/legalpad/storage/LegalpadTransaction.php @@ -2,6 +2,12 @@ final class LegalpadTransaction extends PhabricatorApplicationTransaction { + const TYPE_TITLE = 'title'; + const TYPE_TEXT = 'text'; + const TYPE_SIGNATURE_TYPE = 'legalpad:signature-type'; + const TYPE_PREAMBLE = 'legalpad:premable'; + const TYPE_REQUIRE_SIGNATURE = 'legalpad:require-signature'; + public function getApplicationName() { return 'legalpad'; } @@ -22,10 +28,10 @@ final class LegalpadTransaction extends PhabricatorApplicationTransaction { $old = $this->getOldValue(); switch ($this->getTransactionType()) { - case LegalpadTransactionType::TYPE_TITLE: - case LegalpadTransactionType::TYPE_TEXT: + case self::TYPE_TITLE: + case self::TYPE_TEXT: return ($old === null); - case LegalpadTransactionType::TYPE_SIGNATURE_TYPE: + case self::TYPE_SIGNATURE_TYPE: return true; } @@ -40,21 +46,21 @@ final class LegalpadTransaction extends PhabricatorApplicationTransaction { $type = $this->getTransactionType(); switch ($type) { - case LegalpadTransactionType::TYPE_TITLE: + case self::TYPE_TITLE: return pht( '%s renamed this document from "%s" to "%s".', $this->renderHandleLink($author_phid), $old, $new); - case LegalpadTransactionType::TYPE_TEXT: + case self::TYPE_TEXT: return pht( "%s updated the document's text.", $this->renderHandleLink($author_phid)); - case LegalpadTransactionType::TYPE_PREAMBLE: + case self::TYPE_PREAMBLE: return pht( '%s updated the preamble.', $this->renderHandleLink($author_phid)); - case LegalpadTransactionType::TYPE_REQUIRE_SIGNATURE: + case self::TYPE_REQUIRE_SIGNATURE: if ($new) { $text = pht( '%s set the document to require signatures.', @@ -72,9 +78,9 @@ final class LegalpadTransaction extends PhabricatorApplicationTransaction { public function hasChangeDetails() { switch ($this->getTransactionType()) { - case LegalpadTransactionType::TYPE_TITLE: - case LegalpadTransactionType::TYPE_TEXT: - case LegalpadTransactionType::TYPE_PREAMBLE: + case self::TYPE_TITLE: + case self::TYPE_TEXT: + case self::TYPE_PREAMBLE: return true; } return parent::hasChangeDetails(); diff --git a/src/applications/macro/constants/PhabricatorMacroTransactionType.php b/src/applications/macro/constants/PhabricatorMacroTransactionType.php deleted file mode 100644 index 1de00b73cd..0000000000 --- a/src/applications/macro/constants/PhabricatorMacroTransactionType.php +++ /dev/null @@ -1,12 +0,0 @@ -getBool('behaviorForm')) { $xactions[] = id(new PhabricatorMacroTransaction()) ->setTransactionType( - PhabricatorMacroTransactionType::TYPE_AUDIO_BEHAVIOR) + PhabricatorMacroTransaction::TYPE_AUDIO_BEHAVIOR) ->setNewValue($request->getStr('audioBehavior')); } else { $file = null; @@ -60,7 +60,7 @@ final class PhabricatorMacroAudioController extends PhabricatorMacroController { $e_file = pht('Invalid'); } else { $xactions[] = id(new PhabricatorMacroTransaction()) - ->setTransactionType(PhabricatorMacroTransactionType::TYPE_AUDIO) + ->setTransactionType(PhabricatorMacroTransaction::TYPE_AUDIO) ->setNewValue($file->getPHID()); } } else { diff --git a/src/applications/macro/controller/PhabricatorMacroDisableController.php b/src/applications/macro/controller/PhabricatorMacroDisableController.php index b75e67e1f8..0804e119e5 100644 --- a/src/applications/macro/controller/PhabricatorMacroDisableController.php +++ b/src/applications/macro/controller/PhabricatorMacroDisableController.php @@ -28,7 +28,7 @@ final class PhabricatorMacroDisableController if ($request->isDialogFormPost() || $macro->getIsDisabled()) { $xaction = id(new PhabricatorMacroTransaction()) - ->setTransactionType(PhabricatorMacroTransactionType::TYPE_DISABLED) + ->setTransactionType(PhabricatorMacroTransaction::TYPE_DISABLED) ->setNewValue($macro->getIsDisabled() ? 0 : 1); $editor = id(new PhabricatorMacroEditor()) diff --git a/src/applications/macro/controller/PhabricatorMacroEditController.php b/src/applications/macro/controller/PhabricatorMacroEditController.php index 8122a7243b..f15e5364c2 100644 --- a/src/applications/macro/controller/PhabricatorMacroEditController.php +++ b/src/applications/macro/controller/PhabricatorMacroEditController.php @@ -141,13 +141,13 @@ final class PhabricatorMacroEditController extends PhabricatorMacroController { if ($new_name !== null) { $xactions[] = id(new PhabricatorMacroTransaction()) - ->setTransactionType(PhabricatorMacroTransactionType::TYPE_NAME) + ->setTransactionType(PhabricatorMacroTransaction::TYPE_NAME) ->setNewValue($new_name); } if ($file) { $xactions[] = id(new PhabricatorMacroTransaction()) - ->setTransactionType(PhabricatorMacroTransactionType::TYPE_FILE) + ->setTransactionType(PhabricatorMacroTransaction::TYPE_FILE) ->setNewValue($file->getPHID()); } diff --git a/src/applications/macro/editor/PhabricatorMacroEditor.php b/src/applications/macro/editor/PhabricatorMacroEditor.php index fe5259a1d2..1bb139a4de 100644 --- a/src/applications/macro/editor/PhabricatorMacroEditor.php +++ b/src/applications/macro/editor/PhabricatorMacroEditor.php @@ -15,11 +15,11 @@ final class PhabricatorMacroEditor $types = parent::getTransactionTypes(); $types[] = PhabricatorTransactions::TYPE_COMMENT; - $types[] = PhabricatorMacroTransactionType::TYPE_NAME; - $types[] = PhabricatorMacroTransactionType::TYPE_DISABLED; - $types[] = PhabricatorMacroTransactionType::TYPE_FILE; - $types[] = PhabricatorMacroTransactionType::TYPE_AUDIO; - $types[] = PhabricatorMacroTransactionType::TYPE_AUDIO_BEHAVIOR; + $types[] = PhabricatorMacroTransaction::TYPE_NAME; + $types[] = PhabricatorMacroTransaction::TYPE_DISABLED; + $types[] = PhabricatorMacroTransaction::TYPE_FILE; + $types[] = PhabricatorMacroTransaction::TYPE_AUDIO; + $types[] = PhabricatorMacroTransaction::TYPE_AUDIO_BEHAVIOR; return $types; } @@ -29,15 +29,15 @@ final class PhabricatorMacroEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorMacroTransactionType::TYPE_NAME: + case PhabricatorMacroTransaction::TYPE_NAME: return $object->getName(); - case PhabricatorMacroTransactionType::TYPE_DISABLED: + case PhabricatorMacroTransaction::TYPE_DISABLED: return $object->getIsDisabled(); - case PhabricatorMacroTransactionType::TYPE_FILE: + case PhabricatorMacroTransaction::TYPE_FILE: return $object->getFilePHID(); - case PhabricatorMacroTransactionType::TYPE_AUDIO: + case PhabricatorMacroTransaction::TYPE_AUDIO: return $object->getAudioPHID(); - case PhabricatorMacroTransactionType::TYPE_AUDIO_BEHAVIOR: + case PhabricatorMacroTransaction::TYPE_AUDIO_BEHAVIOR: return $object->getAudioBehavior(); } } @@ -47,11 +47,11 @@ final class PhabricatorMacroEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorMacroTransactionType::TYPE_NAME: - case PhabricatorMacroTransactionType::TYPE_DISABLED: - case PhabricatorMacroTransactionType::TYPE_FILE: - case PhabricatorMacroTransactionType::TYPE_AUDIO: - case PhabricatorMacroTransactionType::TYPE_AUDIO_BEHAVIOR: + case PhabricatorMacroTransaction::TYPE_NAME: + case PhabricatorMacroTransaction::TYPE_DISABLED: + case PhabricatorMacroTransaction::TYPE_FILE: + case PhabricatorMacroTransaction::TYPE_AUDIO: + case PhabricatorMacroTransaction::TYPE_AUDIO_BEHAVIOR: return $xaction->getNewValue(); } } @@ -61,19 +61,19 @@ final class PhabricatorMacroEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorMacroTransactionType::TYPE_NAME: + case PhabricatorMacroTransaction::TYPE_NAME: $object->setName($xaction->getNewValue()); break; - case PhabricatorMacroTransactionType::TYPE_DISABLED: + case PhabricatorMacroTransaction::TYPE_DISABLED: $object->setIsDisabled($xaction->getNewValue()); break; - case PhabricatorMacroTransactionType::TYPE_FILE: + case PhabricatorMacroTransaction::TYPE_FILE: $object->setFilePHID($xaction->getNewValue()); break; - case PhabricatorMacroTransactionType::TYPE_AUDIO: + case PhabricatorMacroTransaction::TYPE_AUDIO: $object->setAudioPHID($xaction->getNewValue()); break; - case PhabricatorMacroTransactionType::TYPE_AUDIO_BEHAVIOR: + case PhabricatorMacroTransaction::TYPE_AUDIO_BEHAVIOR: $object->setAudioBehavior($xaction->getNewValue()); break; } @@ -84,8 +84,8 @@ final class PhabricatorMacroEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorMacroTransactionType::TYPE_FILE: - case PhabricatorMacroTransactionType::TYPE_AUDIO: + case PhabricatorMacroTransaction::TYPE_FILE: + case PhabricatorMacroTransaction::TYPE_AUDIO: // When changing a macro's image or audio, attach the underlying files // to the macro (and detach the old files). $old = $xaction->getOldValue(); @@ -123,11 +123,11 @@ final class PhabricatorMacroEditor $type = $u->getTransactionType(); switch ($type) { - case PhabricatorMacroTransactionType::TYPE_NAME: - case PhabricatorMacroTransactionType::TYPE_DISABLED: - case PhabricatorMacroTransactionType::TYPE_FILE: - case PhabricatorMacroTransactionType::TYPE_AUDIO: - case PhabricatorMacroTransactionType::TYPE_AUDIO_BEHAVIOR: + case PhabricatorMacroTransaction::TYPE_NAME: + case PhabricatorMacroTransaction::TYPE_DISABLED: + case PhabricatorMacroTransaction::TYPE_FILE: + case PhabricatorMacroTransaction::TYPE_AUDIO: + case PhabricatorMacroTransaction::TYPE_AUDIO_BEHAVIOR: return $v; } @@ -139,7 +139,7 @@ final class PhabricatorMacroEditor array $xactions) { foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorMacroTransactionType::TYPE_NAME; + case PhabricatorMacroTransaction::TYPE_NAME; return ($xaction->getOldValue() !== null); default: break; diff --git a/src/applications/macro/query/PhabricatorMacroQuery.php b/src/applications/macro/query/PhabricatorMacroQuery.php index d4ffe46107..3ba30502d5 100644 --- a/src/applications/macro/query/PhabricatorMacroQuery.php +++ b/src/applications/macro/query/PhabricatorMacroQuery.php @@ -5,7 +5,7 @@ final class PhabricatorMacroQuery private $ids; private $phids; - private $authors; + private $authorPHIDs; private $names; private $nameLike; private $namePrefix; @@ -51,8 +51,8 @@ final class PhabricatorMacroQuery return $this; } - public function withAuthorPHIDs(array $authors) { - $this->authors = $authors; + public function withAuthorPHIDs(array $author_phids) { + $this->authorPHIDs = $author_phids; return $this; } @@ -96,53 +96,46 @@ final class PhabricatorMacroQuery return $this; } - protected function loadPage() { - $macro_table = new PhabricatorFileImageMacro(); - $conn = $macro_table->establishConnection('r'); - - $rows = queryfx_all( - $conn, - 'SELECT m.* FROM %T m %Q %Q %Q', - $macro_table->getTableName(), - $this->buildWhereClause($conn), - $this->buildOrderClause($conn), - $this->buildLimitClause($conn)); - - return $macro_table->loadAllFromArray($rows); + public function newResultObject() { + return new PhabricatorFileImageMacro(); } - protected function buildWhereClause(AphrontDatabaseConnection $conn) { - $where = array(); + protected function loadPage() { + return $this->loadStandardPage(new PhabricatorFileImageMacro()); + } - if ($this->ids) { + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { $where[] = qsprintf( $conn, 'm.id IN (%Ld)', $this->ids); } - if ($this->phids) { + if ($this->phids !== null) { $where[] = qsprintf( $conn, 'm.phid IN (%Ls)', $this->phids); } - if ($this->authors) { + if ($this->authorPHIDs !== null) { $where[] = qsprintf( $conn, 'm.authorPHID IN (%Ls)', - $this->authors); + $this->authorPHIDs); } - if ($this->nameLike) { + if (strlen($this->nameLike)) { $where[] = qsprintf( $conn, 'm.name LIKE %~', $this->nameLike); } - if ($this->names) { + if ($this->names !== null) { $where[] = qsprintf( $conn, 'm.name IN (%Ls)', @@ -210,9 +203,7 @@ final class PhabricatorMacroQuery } } - $where[] = $this->buildPagingClause($conn); - - return $this->formatWhereClause($where); + return $where; } protected function didFilterPage(array $macros) { diff --git a/src/applications/macro/query/PhabricatorMacroSearchEngine.php b/src/applications/macro/query/PhabricatorMacroSearchEngine.php index 8d632e818f..5c69716bbf 100644 --- a/src/applications/macro/query/PhabricatorMacroSearchEngine.php +++ b/src/applications/macro/query/PhabricatorMacroSearchEngine.php @@ -11,123 +11,83 @@ final class PhabricatorMacroSearchEngine return 'PhabricatorMacroApplication'; } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - $saved->setParameter( - 'authorPHIDs', - $this->readUsersFromRequest($request, 'authors')); - - $saved->setParameter('status', $request->getStr('status')); - $saved->setParameter('names', $request->getStrList('names')); - $saved->setParameter('nameLike', $request->getStr('nameLike')); - $saved->setParameter('createdStart', $request->getStr('createdStart')); - $saved->setParameter('createdEnd', $request->getStr('createdEnd')); - $saved->setParameter('flagColor', $request->getStr('flagColor', '-1')); - - $this->saveQueryOrder($saved, $request); - - return $saved; + public function newQuery() { + return id(new PhabricatorMacroQuery()) + ->needFiles(true); } - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new PhabricatorMacroQuery()) - ->needFiles(true) - ->withIDs($saved->getParameter('ids', array())) - ->withPHIDs($saved->getParameter('phids', array())) - ->withAuthorPHIDs($saved->getParameter('authorPHIDs', array())); + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorSearchSelectField()) + ->setLabel(pht('Status')) + ->setKey('status') + ->setOptions(PhabricatorMacroQuery::getStatusOptions()), + id(new PhabricatorSearchUsersField()) + ->setLabel(pht('Authors')) + ->setKey('authorPHIDs') + ->setAliases(array('author', 'authors')), + id(new PhabricatorSearchTextField()) + ->setLabel(pht('Name Contains')) + ->setKey('nameLike'), + id(new PhabricatorSearchStringListField()) + ->setLabel(pht('Exact Names')) + ->setKey('names'), + id(new PhabricatorSearchSelectField()) + ->setLabel(pht('Marked with Flag')) + ->setKey('flagColor') + ->setDefault('-1') + ->setOptions(PhabricatorMacroQuery::getFlagColorsOptions()), + id(new PhabricatorSearchDateField()) + ->setLabel(pht('Created After')) + ->setKey('createdStart'), + id(new PhabricatorSearchDateField()) + ->setLabel(pht('Created Before')) + ->setKey('createdEnd'), + ); + } - $this->setQueryOrder($query, $saved); + protected function getDefaultFieldOrder() { + return array( + '...', + 'createdStart', + 'createdEnd', + ); + } - $status = $saved->getParameter('status'); - $options = PhabricatorMacroQuery::getStatusOptions(); - if (empty($options[$status])) { - $status = head_key($options); - } - $query->withStatus($status); + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); - $names = $saved->getParameter('names', array()); - if ($names) { - $query->withNames($names); + if ($map['authorPHIDs']) { + $query->withAuthorPHIDs($map['authorPHIDs']); } - $like = $saved->getParameter('nameLike'); - if (strlen($like)) { - $query->withNameLike($like); + if ($map['status']) { + $query->withStatus($map['status']); } - $start = $this->parseDateTime($saved->getParameter('createdStart')); - $end = $this->parseDateTime($saved->getParameter('createdEnd')); - - if ($start) { - $query->withDateCreatedAfter($start); + if ($map['names']) { + $query->withNames($map['names']); } - if ($end) { - $query->withDateCreatedBefore($end); + if (strlen($map['nameLike'])) { + $query->withNameLike($map['nameLike']); } - $color = $saved->getParameter('flagColor'); - if (strlen($color)) { - $query->withFlagColor($color); + if ($map['createdStart']) { + $query->withDateCreatedAfter($map['createdStart']); + } + + if ($map['createdEnd']) { + $query->withDateCreatedBefore($map['createdEnd']); + } + + if ($map['flagColor'] !== null) { + $query->withFlagColor($map['flagColor']); } return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved) { - - $author_phids = $saved->getParameter('authorPHIDs', array()); - $status = $saved->getParameter('status'); - $names = implode(', ', $saved->getParameter('names', array())); - $like = $saved->getParameter('nameLike'); - $color = $saved->getParameter('flagColor', '-1'); - - $form - ->appendChild( - id(new AphrontFormSelectControl()) - ->setName('status') - ->setLabel(pht('Status')) - ->setOptions(PhabricatorMacroQuery::getStatusOptions()) - ->setValue($status)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorPeopleDatasource()) - ->setName('authors') - ->setLabel(pht('Authors')) - ->setValue($author_phids)) - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('nameLike') - ->setLabel(pht('Name Contains')) - ->setValue($like)) - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('names') - ->setLabel(pht('Exact Names')) - ->setValue($names)) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setName('flagColor') - ->setLabel(pht('Marked with Flag')) - ->setOptions(PhabricatorMacroQuery::getFlagColorsOptions()) - ->setValue($color)); - - $this->buildDateRange( - $form, - $saved, - 'createdStart', - pht('Created After'), - 'createdEnd', - pht('Created Before')); - - $this->appendOrderFieldsToForm( - $form, - $saved, - new PhabricatorMacroQuery()); - } - protected function getURI($path) { return '/macro/'.$path; } @@ -165,12 +125,6 @@ final class PhabricatorMacroSearchEngine return parent::buildSavedQueryFromBuiltin($query_key); } - protected function getRequiredHandlePHIDsForResultList( - array $macros, - PhabricatorSavedQuery $query) { - return mpull($macros, 'getAuthorPHID'); - } - protected function renderResultList( array $macros, PhabricatorSavedQuery $query, @@ -178,6 +132,7 @@ final class PhabricatorMacroSearchEngine assert_instances_of($macros, 'PhabricatorFileImageMacro'); $viewer = $this->requireViewer(); + $handles = $viewer->loadHandles(mpull($macros, 'getAuthorPHID')); $xform = PhabricatorFileTransform::getTransformByKey( PhabricatorFileThumbnailTransform::TRANSFORM_PINBOARD); diff --git a/src/applications/macro/storage/PhabricatorMacroTransaction.php b/src/applications/macro/storage/PhabricatorMacroTransaction.php index e5e7c3004d..46eb932476 100644 --- a/src/applications/macro/storage/PhabricatorMacroTransaction.php +++ b/src/applications/macro/storage/PhabricatorMacroTransaction.php @@ -3,6 +3,13 @@ final class PhabricatorMacroTransaction extends PhabricatorApplicationTransaction { + const TYPE_NAME = 'macro:name'; + const TYPE_DISABLED = 'macro:disabled'; + const TYPE_FILE = 'macro:file'; + + const TYPE_AUDIO = 'macro:audio'; + const TYPE_AUDIO_BEHAVIOR = 'macro:audiobehavior'; + public function getApplicationName() { return 'file'; } @@ -26,8 +33,8 @@ final class PhabricatorMacroTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case PhabricatorMacroTransactionType::TYPE_FILE: - case PhabricatorMacroTransactionType::TYPE_AUDIO: + case self::TYPE_FILE: + case self::TYPE_AUDIO: if ($old !== null) { $phids[] = $old; } @@ -43,7 +50,7 @@ final class PhabricatorMacroTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case PhabricatorMacroTransactionType::TYPE_NAME: + case self::TYPE_NAME: return ($old === null); } @@ -57,14 +64,14 @@ final class PhabricatorMacroTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case PhabricatorMacroTransactionType::TYPE_NAME: + case self::TYPE_NAME: return pht( '%s renamed this macro from "%s" to "%s".', $this->renderHandleLink($author_phid), $old, $new); break; - case PhabricatorMacroTransactionType::TYPE_DISABLED: + case self::TYPE_DISABLED: if ($new) { return pht( '%s disabled this macro.', @@ -76,7 +83,7 @@ final class PhabricatorMacroTransaction } break; - case PhabricatorMacroTransactionType::TYPE_AUDIO: + case self::TYPE_AUDIO: if (!$old) { return pht( '%s attached audio: %s.', @@ -90,7 +97,7 @@ final class PhabricatorMacroTransaction $this->renderHandleLink($new)); } - case PhabricatorMacroTransactionType::TYPE_AUDIO_BEHAVIOR: + case self::TYPE_AUDIO_BEHAVIOR: switch ($new) { case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_ONCE: return pht( @@ -106,7 +113,7 @@ final class PhabricatorMacroTransaction $this->renderHandleLink($author_phid)); } - case PhabricatorMacroTransactionType::TYPE_FILE: + case self::TYPE_FILE: if ($old === null) { return pht( '%s created this macro.', @@ -132,14 +139,14 @@ final class PhabricatorMacroTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case PhabricatorMacroTransactionType::TYPE_NAME: + case self::TYPE_NAME: return pht( '%s renamed %s from "%s" to "%s".', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $old, $new); - case PhabricatorMacroTransactionType::TYPE_DISABLED: + case self::TYPE_DISABLED: if ($new) { return pht( '%s disabled %s.', @@ -151,7 +158,7 @@ final class PhabricatorMacroTransaction $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } - case PhabricatorMacroTransactionType::TYPE_FILE: + case self::TYPE_FILE: if ($old === null) { return pht( '%s created %s.', @@ -164,7 +171,7 @@ final class PhabricatorMacroTransaction $this->renderHandleLink($object_phid)); } - case PhabricatorMacroTransactionType::TYPE_AUDIO: + case self::TYPE_AUDIO: if (!$old) { return pht( '%s attached audio to %s: %s.', @@ -180,7 +187,7 @@ final class PhabricatorMacroTransaction $this->renderHandleLink($new)); } - case PhabricatorMacroTransactionType::TYPE_AUDIO_BEHAVIOR: + case self::TYPE_AUDIO_BEHAVIOR: switch ($new) { case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_ONCE: return pht( @@ -209,29 +216,29 @@ final class PhabricatorMacroTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case PhabricatorMacroTransactionType::TYPE_NAME: + case self::TYPE_NAME: if ($old === null) { return pht('Created'); } else { return pht('Renamed'); } - case PhabricatorMacroTransactionType::TYPE_DISABLED: + case self::TYPE_DISABLED: if ($new) { return pht('Disabled'); } else { return pht('Restored'); } - case PhabricatorMacroTransactionType::TYPE_FILE: + case self::TYPE_FILE: if ($old === null) { return pht('Created'); } else { return pht('Edited Image'); } - case PhabricatorMacroTransactionType::TYPE_AUDIO: + case self::TYPE_AUDIO: return pht('Audio'); - case PhabricatorMacroTransactionType::TYPE_AUDIO_BEHAVIOR: + case self::TYPE_AUDIO_BEHAVIOR: return pht('Audio Behavior'); } @@ -241,9 +248,9 @@ final class PhabricatorMacroTransaction public function getActionStrength() { switch ($this->getTransactionType()) { - case PhabricatorMacroTransactionType::TYPE_DISABLED: + case self::TYPE_DISABLED: return 2.0; - case PhabricatorMacroTransactionType::TYPE_FILE: + case self::TYPE_FILE: return 1.5; } return parent::getActionStrength(); @@ -254,21 +261,21 @@ final class PhabricatorMacroTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case PhabricatorMacroTransactionType::TYPE_NAME: + case self::TYPE_NAME: return 'fa-pencil'; - case PhabricatorMacroTransactionType::TYPE_FILE: + case self::TYPE_FILE: if ($old === null) { return 'fa-plus'; } else { return 'fa-pencil'; } - case PhabricatorMacroTransactionType::TYPE_DISABLED: + case self::TYPE_DISABLED: if ($new) { return 'fa-times'; } else { return 'fa-undo'; } - case PhabricatorMacroTransactionType::TYPE_AUDIO: + case self::TYPE_AUDIO: return 'fa-headphones'; } @@ -280,15 +287,15 @@ final class PhabricatorMacroTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case PhabricatorMacroTransactionType::TYPE_NAME: + case self::TYPE_NAME: return PhabricatorTransactions::COLOR_BLUE; - case PhabricatorMacroTransactionType::TYPE_FILE: + case self::TYPE_FILE: if ($old === null) { return PhabricatorTransactions::COLOR_GREEN; } else { return PhabricatorTransactions::COLOR_BLUE; } - case PhabricatorMacroTransactionType::TYPE_DISABLED: + case self::TYPE_DISABLED: if ($new) { return PhabricatorTransactions::COLOR_RED; } else { diff --git a/src/applications/mailinglists/application/PhabricatorMailingListsApplication.php b/src/applications/mailinglists/application/PhabricatorMailingListsApplication.php deleted file mode 100644 index cf62321996..0000000000 --- a/src/applications/mailinglists/application/PhabricatorMailingListsApplication.php +++ /dev/null @@ -1,48 +0,0 @@ - array( - '(?:query/(?P[^/]+)/)?' - => 'PhabricatorMailingListsListController', - 'edit/(?:(?P[1-9]\d*)/)?' - => 'PhabricatorMailingListsEditController', - ), - ); - } - - public function getTitleGlyph() { - return '@'; - } - - protected function getCustomCapabilities() { - return array( - PhabricatorMailingListsManageCapability::CAPABILITY => array( - 'default' => PhabricatorPolicies::POLICY_ADMIN, - ), - ); - } - -} diff --git a/src/applications/mailinglists/capability/PhabricatorMailingListsManageCapability.php b/src/applications/mailinglists/capability/PhabricatorMailingListsManageCapability.php deleted file mode 100644 index 42e69f4d86..0000000000 --- a/src/applications/mailinglists/capability/PhabricatorMailingListsManageCapability.php +++ /dev/null @@ -1,16 +0,0 @@ -getRequest()->getUser(); - - $nav = new AphrontSideNavFilterView(); - $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - - if ($for_app) { - $nav->addFilter('edit', pht('Create List')); - } - - id(new PhabricatorMailingListSearchEngine()) - ->setViewer($user) - ->addNavigationItems($nav->getMenu()); - - $nav->selectFilter(null); - - return $nav; - } - - public function buildApplicationMenu() { - return $this->buildSideNavView(true)->getMenu(); - } - - protected function buildApplicationCrumbs() { - $crumbs = parent::buildApplicationCrumbs(); - - $can_manage = $this->hasApplicationCapability( - PhabricatorMailingListsManageCapability::CAPABILITY); - - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('Create List')) - ->setHref($this->getApplicationURI('edit/')) - ->setIcon('fa-plus-square') - ->setDisabled(!$can_manage) - ->setWorkflow(!$can_manage)); - - return $crumbs; - } - -} diff --git a/src/applications/mailinglists/controller/PhabricatorMailingListsEditController.php b/src/applications/mailinglists/controller/PhabricatorMailingListsEditController.php deleted file mode 100644 index b6eb81a6ff..0000000000 --- a/src/applications/mailinglists/controller/PhabricatorMailingListsEditController.php +++ /dev/null @@ -1,131 +0,0 @@ -getRequest(); - $viewer = $request->getUser(); - - $this->requireApplicationCapability( - PhabricatorMailingListsManageCapability::CAPABILITY); - - $list_id = $request->getURIData('id'); - if ($list_id) { - $page_title = pht('Edit Mailing List'); - $list = id(new PhabricatorMailingListQuery()) - ->setViewer($viewer) - ->withIDs(array($list_id)) - ->executeOne(); - if (!$list) { - return new Aphront404Response(); - } - } else { - $page_title = pht('Create Mailing List'); - $list = new PhabricatorMetaMTAMailingList(); - } - - $e_email = true; - $e_uri = null; - $e_name = true; - $errors = array(); - - $crumbs = $this->buildApplicationCrumbs(); - - if ($request->isFormPost()) { - $list->setName($request->getStr('name')); - $list->setEmail($request->getStr('email')); - $list->setURI($request->getStr('uri')); - - $e_email = null; - $e_name = null; - - if (!strlen($list->getEmail())) { - $e_email = pht('Required'); - $errors[] = pht('Email is required.'); - } - - if (!strlen($list->getName())) { - $e_name = pht('Required'); - $errors[] = pht('Name is required.'); - } else if (preg_match('/[ ,]/', $list->getName())) { - $e_name = pht('Invalid'); - $errors[] = pht('Name must not contain spaces or commas.'); - } - - if ($list->getURI()) { - if (!PhabricatorEnv::isValidRemoteURIForLink($list->getURI())) { - $e_uri = pht('Invalid'); - $errors[] = pht('Mailing list URI must point to a valid web page.'); - } - } - - if (!$errors) { - try { - $list->save(); - return id(new AphrontRedirectResponse()) - ->setURI($this->getApplicationURI()); - } catch (AphrontDuplicateKeyQueryException $ex) { - $e_email = pht('Duplicate'); - $errors[] = pht('Another mailing list already uses that address.'); - } - } - } - - $form = new AphrontFormView(); - $form->setUser($request->getUser()); - if ($list->getID()) { - $form->setAction($this->getApplicationURI('/edit/'.$list->getID().'/')); - } else { - $form->setAction($this->getApplicationURI('/edit/')); - } - - $form - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Email')) - ->setName('email') - ->setValue($list->getEmail()) - ->setCaption(pht('Email will be delivered to this address.')) - ->setError($e_email)) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Name')) - ->setName('name') - ->setError($e_name) - ->setCaption(pht('Human-readable display and autocomplete name.')) - ->setValue($list->getName())) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('URI')) - ->setName('uri') - ->setError($e_uri) - ->setCaption(pht('Optional link to mailing list archives or info.')) - ->setValue($list->getURI())) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Save')) - ->addCancelButton($this->getApplicationURI())); - - if ($list->getID()) { - $crumbs->addTextCrumb(pht('Edit Mailing List')); - } else { - $crumbs->addTextCrumb(pht('Create Mailing List')); - } - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($page_title) - ->setFormErrors($errors) - ->setForm($form); - - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, - ), - array( - 'title' => $page_title, - )); - } - -} diff --git a/src/applications/mailinglists/controller/PhabricatorMailingListsListController.php b/src/applications/mailinglists/controller/PhabricatorMailingListsListController.php deleted file mode 100644 index 4d993e9f8e..0000000000 --- a/src/applications/mailinglists/controller/PhabricatorMailingListsListController.php +++ /dev/null @@ -1,25 +0,0 @@ -queryKey = idx($data, 'queryKey'); - } - - public function processRequest() { - $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($this->queryKey) - ->setSearchEngine(new PhabricatorMailingListSearchEngine()) - ->setNavigation($this->buildSideNavView()); - - return $this->delegateToController($controller); - } - -} diff --git a/src/applications/mailinglists/query/PhabricatorMailingListQuery.php b/src/applications/mailinglists/query/PhabricatorMailingListQuery.php deleted file mode 100644 index 113bb4c80d..0000000000 --- a/src/applications/mailinglists/query/PhabricatorMailingListQuery.php +++ /dev/null @@ -1,86 +0,0 @@ -ids = $ids; - return $this; - } - - public function withPHIDs($phids) { - $this->phids = $phids; - return $this; - } - - public function withEmails(array $emails) { - $this->emails = $emails; - return $this; - } - - public function withNames(array $names) { - $this->names = $names; - return $this; - } - - protected function loadPage() { - $table = new PhabricatorMetaMTAMailingList(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT * FROM %T %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - return $table->loadAllFromArray($data); - } - - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); - - if ($this->ids) { - $where[] = qsprintf( - $conn_r, - 'id IN (%Ld)', - $this->ids); - } - - if ($this->phids) { - $where[] = qsprintf( - $conn_r, - 'phid IN (%Ls)', - $this->phids); - } - - if ($this->names) { - $where[] = qsprintf( - $conn_r, - 'name IN (%Ls)', - $this->names); - } - - if ($this->emails) { - $where[] = qsprintf( - $conn_r, - 'email IN (%Ls)', - $this->emails); - } - - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); - } - - public function getQueryApplicationClass() { - return 'PhabricatorMailingListsApplication'; - } - -} diff --git a/src/applications/mailinglists/query/PhabricatorMailingListSearchEngine.php b/src/applications/mailinglists/query/PhabricatorMailingListSearchEngine.php deleted file mode 100644 index 89f17fbe88..0000000000 --- a/src/applications/mailinglists/query/PhabricatorMailingListSearchEngine.php +++ /dev/null @@ -1,91 +0,0 @@ -appendChild( - id(new AphrontFormMarkupControl()) - ->setValue(pht('No query filters are available for mailing lists.'))); - } - - protected function getURI($path) { - return '/mailinglists/'.$path; - } - - protected function getBuiltinQueryNames() { - return array( - 'all' => pht('All Lists'), - ); - } - - public function buildSavedQueryFromBuiltin($query_key) { - $query = $this->newSavedQuery(); - $query->setQueryKey($query_key); - - switch ($query_key) { - case 'all': - return $query; - } - - return parent::buildSavedQueryFromBuiltin($query_key); - } - - protected function renderResultList( - array $lists, - PhabricatorSavedQuery $query, - array $handles) { - assert_instances_of($lists, 'PhabricatorMetaMTAMailingList'); - - $view = id(new PHUIObjectItemListView()); - - $can_manage = PhabricatorPolicyFilter::hasCapability( - $this->requireViewer(), - $this->getApplication(), - PhabricatorMailingListsManageCapability::CAPABILITY); - - foreach ($lists as $list) { - $item = new PHUIObjectItemView(); - - $item->setHeader($list->getName()); - $item->setHref($list->getURI()); - $item->addAttribute($list->getEmail()); - $item->addAction( - id(new PHUIListItemView()) - ->setIcon('fa-pencil') - ->setHref($this->getApplicationURI('/edit/'.$list->getID().'/')) - ->setDisabled(!$can_manage) - ->setWorkflow(!$can_manage)); - - $view->addItem($item); - } - - return $view; - } - -} diff --git a/src/applications/mailinglists/storage/PhabricatorMetaMTAMailingList.php b/src/applications/mailinglists/storage/PhabricatorMetaMTAMailingList.php index c93d19de2d..68d2499ed6 100644 --- a/src/applications/mailinglists/storage/PhabricatorMetaMTAMailingList.php +++ b/src/applications/mailinglists/storage/PhabricatorMetaMTAMailingList.php @@ -1,18 +1,15 @@ openTransaction(); - $this->delete(); - $this->saveTransaction(); - } - } diff --git a/src/applications/mailinglists/typeahead/PhabricatorMailingListDatasource.php b/src/applications/mailinglists/typeahead/PhabricatorMailingListDatasource.php deleted file mode 100644 index cac292e1e5..0000000000 --- a/src/applications/mailinglists/typeahead/PhabricatorMailingListDatasource.php +++ /dev/null @@ -1,39 +0,0 @@ -getViewer(); - $raw_query = $this->getRawQuery(); - - $query = id(new PhabricatorMailingListQuery()); - $lists = $this->executeQuery($query); - - $results = array(); - foreach ($lists as $list) { - $results[] = id(new PhabricatorTypeaheadResult()) - ->setName($list->getName()) - ->setURI($list->getURI()) - ->setPHID($list->getPHID()); - } - - // TODO: It would be slightly preferable to do this as part of the query, - // this is just simpler for the moment. - - return $this->filterResultsAgainstTokens($results); - } - -} diff --git a/src/applications/maniphest/__tests__/ManiphestTaskTestCase.php b/src/applications/maniphest/__tests__/ManiphestTaskTestCase.php index b7fafa83ff..18f433fe52 100644 --- a/src/applications/maniphest/__tests__/ManiphestTaskTestCase.php +++ b/src/applications/maniphest/__tests__/ManiphestTaskTestCase.php @@ -145,7 +145,7 @@ final class ManiphestTaskTestCase extends PhabricatorTestCase { private function loadTasks(PhabricatorUser $viewer, $auto_base) { $tasks = id(new ManiphestTaskQuery()) ->setViewer($viewer) - ->setOrderBy(ManiphestTaskQuery::ORDER_PRIORITY) + ->setOrder(ManiphestTaskQuery::ORDER_PRIORITY) ->execute(); // NOTE: AUTO_INCREMENT changes survive ROLLBACK, and we can't throw them diff --git a/src/applications/maniphest/conduit/ManiphestQueryConduitAPIMethod.php b/src/applications/maniphest/conduit/ManiphestQueryConduitAPIMethod.php index 8a2321b2d3..ea8aeb95ed 100644 --- a/src/applications/maniphest/conduit/ManiphestQueryConduitAPIMethod.php +++ b/src/applications/maniphest/conduit/ManiphestQueryConduitAPIMethod.php @@ -102,7 +102,7 @@ final class ManiphestQueryConduitAPIMethod extends ManiphestConduitAPIMethod { $order = $request->getValue('order'); if ($order) { - $query->setOrderBy($order); + $query->setOrder($order); } $limit = $request->getValue('limit'); diff --git a/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php b/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php index a52b84dcbb..c669e2f858 100644 --- a/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php +++ b/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php @@ -212,16 +212,16 @@ EOTEXT $status_example = array( 'open' => array( - 'name' => 'Open', + 'name' => pht('Open'), 'special' => 'default', ), 'closed' => array( - 'name' => 'Closed', + 'name' => pht('Closed'), 'special' => 'closed', 'closed' => true, ), 'duplicate' => array( - 'name' => 'Duplicate', + 'name' => pht('Duplicate'), 'special' => 'duplicate', 'closed' => true, ), diff --git a/src/applications/maniphest/constants/__tests__/ManiphestTaskStatusTestCase.php b/src/applications/maniphest/constants/__tests__/ManiphestTaskStatusTestCase.php index 6548e5700f..20f4c438a7 100644 --- a/src/applications/maniphest/constants/__tests__/ManiphestTaskStatusTestCase.php +++ b/src/applications/maniphest/constants/__tests__/ManiphestTaskStatusTestCase.php @@ -34,16 +34,16 @@ final class ManiphestTaskStatusTestCase extends PhabricatorTestCase { $valid = array( 'open' => array( - 'name' => 'Open', + 'name' => pht('Open'), 'special' => 'default', ), 'closed' => array( - 'name' => 'Closed', + 'name' => pht('Closed'), 'special' => 'closed', 'closed' => true, ), 'duplicate' => array( - 'name' => 'Duplicate', + 'name' => pht('Duplicate'), 'special' => 'duplicate', 'closed' => true, ), @@ -52,7 +52,7 @@ final class ManiphestTaskStatusTestCase extends PhabricatorTestCase { // We should raise on a bad key. $bad_key = $valid; - $bad_key['!'] = array('name' => 'Exclaim'); + $bad_key['!'] = array('name' => pht('Exclaim')); $this->assertConfigValid(false, pht('Bad Key'), $bad_key); // We should raise on a value type. @@ -68,7 +68,7 @@ final class ManiphestTaskStatusTestCase extends PhabricatorTestCase { // We should raise on two statuses with the same special. $double_close = $valid; $double_close['finished'] = array( - 'name' => 'Finished', + 'name' => pht('Finished'), 'special' => 'closed', 'closed' => true, ); diff --git a/src/applications/maniphest/controller/ManiphestReportController.php b/src/applications/maniphest/controller/ManiphestReportController.php index 94c00aa1f8..bdf446150e 100644 --- a/src/applications/maniphest/controller/ManiphestReportController.php +++ b/src/applications/maniphest/controller/ManiphestReportController.php @@ -588,7 +588,7 @@ final class ManiphestReportController extends ManiphestController { $cname[] = $label; $cclass[] = 'n'; } - $cname[] = 'Total'; + $cname[] = pht('Total'); $cclass[] = 'n'; $cname[] = javelin_tag( 'span', diff --git a/src/applications/maniphest/editor/ManiphestTransactionEditor.php b/src/applications/maniphest/editor/ManiphestTransactionEditor.php index 3609145c78..39111644cb 100644 --- a/src/applications/maniphest/editor/ManiphestTransactionEditor.php +++ b/src/applications/maniphest/editor/ManiphestTransactionEditor.php @@ -376,9 +376,7 @@ final class ManiphestTransactionEditor protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { - - $xactions = mfilter($xactions, 'shouldHide', true); - return $xactions; + return true; } protected function getMailSubjectPrefix() { @@ -515,13 +513,6 @@ final class ManiphestTransactionEditor $xactions = array(); - $cc_phids = $adapter->getCcPHIDs(); - if ($cc_phids) { - $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS) - ->setNewValue(array('+' => $cc_phids)); - } - $assign_phid = $adapter->getAssignPHID(); if ($assign_phid) { $xactions[] = id(new ManiphestTransaction()) @@ -641,7 +632,7 @@ final class ManiphestTransactionEditor $query = id(new ManiphestTaskQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->setOrderBy(ManiphestTaskQuery::ORDER_PRIORITY) + ->setOrder(ManiphestTaskQuery::ORDER_PRIORITY) ->withPriorities(array($dst->getPriority())) ->setLimit(1); diff --git a/src/applications/maniphest/export/ManiphestExcelFormat.php b/src/applications/maniphest/export/ManiphestExcelFormat.php index d2e28d1b13..f22df69976 100644 --- a/src/applications/maniphest/export/ManiphestExcelFormat.php +++ b/src/applications/maniphest/export/ManiphestExcelFormat.php @@ -18,8 +18,8 @@ abstract class ManiphestExcelFormat { return $objects; } - public abstract function getName(); - public abstract function getFileName(); + abstract public function getName(); + abstract public function getFileName(); public function getOrder() { return 0; @@ -35,7 +35,7 @@ abstract class ManiphestExcelFormat { /** * @phutil-external-symbol class PHPExcel */ - public abstract function buildWorkbook( + abstract public function buildWorkbook( PHPExcel $workbook, array $tasks, array $handles, diff --git a/src/applications/maniphest/query/ManiphestTaskQuery.php b/src/applications/maniphest/query/ManiphestTaskQuery.php index 09472be2ab..956810524d 100644 --- a/src/applications/maniphest/query/ManiphestTaskQuery.php +++ b/src/applications/maniphest/query/ManiphestTaskQuery.php @@ -43,7 +43,6 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { const GROUP_STATUS = 'group-status'; const GROUP_PROJECT = 'group-project'; - private $orderBy = 'order-modified'; const ORDER_PRIORITY = 'order-priority'; const ORDER_CREATED = 'order-created'; const ORDER_MODIFIED = 'order-modified'; @@ -127,11 +126,27 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { public function setGroupBy($group) { $this->groupBy = $group; - return $this; - } - public function setOrderBy($order) { - $this->orderBy = $order; + switch ($this->groupBy) { + case self::GROUP_NONE: + $vector = array(); + break; + case self::GROUP_PRIORITY: + $vector = array('priority'); + break; + case self::GROUP_OWNER: + $vector = array('owner'); + break; + case self::GROUP_STATUS: + $vector = array('status'); + break; + case self::GROUP_PROJECT: + $vector = array('project'); + break; + } + + $this->setGroupVector($vector); + return $this; } @@ -193,73 +208,10 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { return $this; } - protected function newResultObject() { + public function newResultObject() { return new ManiphestTask(); } - protected function willExecute() { - // If we already have an order vector, use it as provided. - // TODO: This is a messy hack to make setOrderVector() stronger than - // setPriority(). - $vector = $this->getOrderVector(); - $keys = mpull(iterator_to_array($vector), 'getOrderKey'); - if (array_values($keys) !== array('id')) { - return; - } - - $parts = array(); - switch ($this->groupBy) { - case self::GROUP_NONE: - break; - case self::GROUP_PRIORITY: - $parts[] = array('priority'); - break; - case self::GROUP_OWNER: - $parts[] = array('owner'); - break; - case self::GROUP_STATUS: - $parts[] = array('status'); - break; - case self::GROUP_PROJECT: - $parts[] = array('project'); - break; - } - - if ($this->applicationSearchOrders) { - $columns = array(); - foreach ($this->applicationSearchOrders as $order) { - $part = 'custom:'.$order['key']; - if ($order['ascending']) { - $part = '-'.$part; - } - $columns[] = $part; - } - $columns[] = 'id'; - $parts[] = $columns; - } else { - switch ($this->orderBy) { - case self::ORDER_PRIORITY: - $parts[] = array('priority', 'subpriority', 'id'); - break; - case self::ORDER_CREATED: - $parts[] = array('id'); - break; - case self::ORDER_MODIFIED: - $parts[] = array('updated', 'id'); - break; - case self::ORDER_TITLE: - $parts[] = array('title', 'id'); - break; - } - } - - $parts = array_mergev($parts); - // We may have a duplicate column if we are both ordering and grouping - // by priority. - $parts = array_unique($parts); - $this->setOrderVector($parts); - } - protected function loadPage() { $task_dao = new ManiphestTask(); $conn = $task_dao->establishConnection('r'); @@ -760,6 +712,41 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { return $id; } + public function getBuiltinOrders() { + $orders = array( + 'priority' => array( + 'vector' => array('priority', 'subpriority', 'id'), + 'name' => pht('Priority'), + 'aliases' => array(self::ORDER_PRIORITY), + ), + 'updated' => array( + 'vector' => array('updated', 'id'), + 'name' => pht('Date Updated'), + 'aliases' => array(self::ORDER_MODIFIED), + ), + 'title' => array( + 'vector' => array('title', 'id'), + 'name' => pht('Title'), + 'aliases' => array(self::ORDER_TITLE), + ), + ) + parent::getBuiltinOrders(); + + // Alias the "newest" builtin to the historical key for it. + $orders['newest']['aliases'][] = self::ORDER_CREATED; + + $orders = array_select_keys( + $orders, + array( + 'priority', + 'updated', + 'newest', + 'oldest', + 'title', + )) + $orders; + + return $orders; + } + public function getOrderableColumns() { return parent::getOrderableColumns() + array( 'priority' => array( diff --git a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php index c587de5c10..8a875202f1 100644 --- a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php +++ b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php @@ -38,130 +38,157 @@ final class ManiphestTaskSearchEngine return 'PhabricatorManiphestApplication'; } - public function getCustomFieldObject() { - return new ManiphestTask(); + public function newQuery() { + return id(new ManiphestTaskQuery()) + ->needProjectPHIDs(true); } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); + public function buildCustomSearchFields() { + return array( + id(new PhabricatorSearchOwnersField()) + ->setLabel(pht('Assigned To')) + ->setKey('assignedPHIDs') + ->setAliases(array('assigned')), + id(new PhabricatorSearchUsersField()) + ->setLabel(pht('Authors')) + ->setKey('authorPHIDs') + ->setAliases(array('author', 'authors')), + id(new PhabricatorSearchDatasourceField()) + ->setLabel(pht('Statuses')) + ->setKey('statuses') + ->setAliases(array('status')) + ->setDatasource(new ManiphestTaskStatusFunctionDatasource()), + id(new PhabricatorSearchDatasourceField()) + ->setLabel(pht('Priorities')) + ->setKey('priorities') + ->setAliases(array('priority')) + ->setDatasource(new ManiphestTaskPriorityDatasource()), + id(new PhabricatorSearchTextField()) + ->setLabel(pht('Contains Words')) + ->setKey('fulltext'), + id(new PhabricatorSearchThreeStateField()) + ->setLabel(pht('Blocking')) + ->setKey('blocking') + ->setOptions( + pht('(Show All)'), + pht('Show Only Tasks Blocking Other Tasks'), + pht('Hide Tasks Blocking Other Tasks')), + id(new PhabricatorSearchThreeStateField()) + ->setLabel(pht('Blocked')) + ->setKey('blocked') + ->setOptions( + pht('(Show All)'), + pht('Show Only Task Blocked By Other Tasks'), + pht('Hide Tasks Blocked By Other Tasks')), + id(new PhabricatorSearchSelectField()) + ->setLabel(pht('Group By')) + ->setKey('group') + ->setOptions($this->getGroupOptions()), + id(new PhabricatorSearchStringListField()) + ->setLabel(pht('Task IDs')) + ->setKey('ids'), + id(new PhabricatorSearchDateField()) + ->setLabel(pht('Created After')) + ->setKey('createdStart'), + id(new PhabricatorSearchDateField()) + ->setLabel(pht('Created Before')) + ->setKey('createdEnd'), + id(new PhabricatorSearchDateField()) + ->setLabel(pht('Updated After')) + ->setKey('modifiedStart'), + id(new PhabricatorSearchDateField()) + ->setLabel(pht('Updated Before')) + ->setKey('modifiedEnd'), + id(new PhabricatorSearchTextField()) + ->setLabel(pht('Page Size')) + ->setKey('limit'), + ); + } - $saved->setParameter( + public function getDefaultFieldOrder() { + return array( 'assignedPHIDs', - $this->readUsersFromRequest($request, 'assigned')); - - $saved->setParameter( + 'projectPHIDs', 'authorPHIDs', - $this->readUsersFromRequest($request, 'authors')); - - $saved->setParameter( 'subscriberPHIDs', - $this->readSubscribersFromRequest($request, 'subscribers')); - - $saved->setParameter( 'statuses', - $this->readListFromRequest($request, 'statuses')); - - $saved->setParameter( 'priorities', - $this->readListFromRequest($request, 'priorities')); - - $saved->setParameter( + 'fulltext', 'blocking', - $this->readBoolFromRequest($request, 'blocking')); - $saved->setParameter( 'blocked', - $this->readBoolFromRequest($request, 'blocked')); - - $saved->setParameter('group', $request->getStr('group')); - $saved->setParameter('order', $request->getStr('order')); - - $ids = $request->getStrList('ids'); - foreach ($ids as $key => $id) { - $id = trim($id, ' Tt'); - if (!$id || !is_numeric($id)) { - unset($ids[$key]); - } else { - $ids[$key] = $id; - } - } - $saved->setParameter('ids', $ids); - - $saved->setParameter('fulltext', $request->getStr('fulltext')); - - $saved->setParameter( - 'projects', - $this->readProjectsFromRequest($request, 'projects')); - - $saved->setParameter('createdStart', $request->getStr('createdStart')); - $saved->setParameter('createdEnd', $request->getStr('createdEnd')); - $saved->setParameter('modifiedStart', $request->getStr('modifiedStart')); - $saved->setParameter('modifiedEnd', $request->getStr('modifiedEnd')); - - $limit = $request->getInt('limit'); - if ($limit > 0) { - $saved->setParameter('limit', $limit); - } - - $this->readCustomFieldsFromRequest($request, $saved); - - return $saved; + 'group', + 'order', + 'ids', + '...', + 'createdStart', + 'createdEnd', + 'modifiedStart', + 'modifiedEnd', + 'limit', + ); } - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { + public function getHiddenFields() { + $keys = array(); + + if ($this->getIsBoardView()) { + $keys[] = 'group'; + $keys[] = 'order'; + $keys[] = 'limit'; + } + + return $keys; + } + + public function buildQueryFromParameters(array $map) { $query = id(new ManiphestTaskQuery()) ->needProjectPHIDs(true); - $viewer = $this->requireViewer(); - - $datasource = id(new PhabricatorPeopleUserFunctionDatasource()) - ->setViewer($viewer); - - $author_phids = $saved->getParameter('authorPHIDs', array()); - $author_phids = $datasource->evaluateTokens($author_phids); - if ($author_phids) { - $query->withAuthors($author_phids); + if ($map['assignedPHIDs']) { + $query->withOwners($map['assignedPHIDs']); } - $datasource = id(new PhabricatorMetaMTAMailableFunctionDatasource()) - ->setViewer($viewer); - $subscriber_phids = $saved->getParameter('subscriberPHIDs', array()); - $subscriber_phids = $datasource->evaluateTokens($subscriber_phids); - if ($subscriber_phids) { - $query->withSubscribers($subscriber_phids); + if ($map['authorPHIDs']) { + $query->withAuthors($map['authorPHIDs']); } - $datasource = id(new PhabricatorPeopleOwnerDatasource()) - ->setViewer($this->requireViewer()); - - $assigned_phids = $this->readAssignedPHIDs($saved); - $assigned_phids = $datasource->evaluateTokens($assigned_phids); - if ($assigned_phids) { - $query->withOwners($assigned_phids); + if ($map['statuses']) { + $query->withStatuses($map['statuses']); } - $datasource = id(new ManiphestTaskStatusFunctionDatasource()) - ->setViewer($this->requireViewer()); - $statuses = $saved->getParameter('statuses', array()); - $statuses = $datasource->evaluateTokens($statuses); - if ($statuses) { - $query->withStatuses($statuses); + if ($map['priorities']) { + $query->withPriorities($map['priorities']); } - $priorities = $saved->getParameter('priorities', array()); - if ($priorities) { - $query->withPriorities($priorities); + if ($map['createdStart']) { + $query->withDateCreatedAfter($map['createdStart']); } + if ($map['createdEnd']) { + $query->withDateCreatedBefore($map['createdEnd']); + } - $query->withBlockingTasks($saved->getParameter('blocking')); - $query->withBlockedTasks($saved->getParameter('blocked')); + if ($map['modifiedStart']) { + $query->withDateModifiedAfter($map['modifiedStart']); + } - $this->applyOrderByToQuery( - $query, - $this->getOrderValues(), - $saved->getParameter('order')); + if ($map['modifiedEnd']) { + $query->withDateModifiedBefore($map['modifiedEnd']); + } - $group = $saved->getParameter('group'); + if ($map['blocking'] !== null) { + $query->withBlockingTasks($map['blocking']); + } + + if ($map['blocked'] !== null) { + $query->withBlockedTasks($map['blocked']); + } + + if (strlen($map['fulltext'])) { + $query->withFullTextSearch($map['fulltext']); + } + + $group = idx($map, 'group'); $group = idx($this->getGroupValues(), $group); if ($group) { $query->setGroupBy($group); @@ -169,183 +196,25 @@ final class ManiphestTaskSearchEngine $query->setGroupBy(head($this->getGroupValues())); } - $ids = $saved->getParameter('ids'); - if ($ids) { - $query->withIDs($ids); + if ($map['ids']) { + $ids = $map['ids']; + foreach ($ids as $key => $id) { + $id = trim($id, ' Tt'); + if (!$id || !is_numeric($id)) { + unset($ids[$key]); + } else { + $ids[$key] = $id; + } + } + + if ($ids) { + $query->withIDs($ids); + } } - $fulltext = $saved->getParameter('fulltext'); - if (strlen($fulltext)) { - $query->withFullTextSearch($fulltext); - } - - $projects = $this->readProjectTokens($saved); - $adjusted = id(clone $saved)->setParameter('projects', $projects); - $this->setQueryProjects($query, $adjusted); - - $start = $this->parseDateTime($saved->getParameter('createdStart')); - $end = $this->parseDateTime($saved->getParameter('createdEnd')); - - if ($start) { - $query->withDateCreatedAfter($start); - } - - if ($end) { - $query->withDateCreatedBefore($end); - } - - $mod_start = $this->parseDateTime($saved->getParameter('modifiedStart')); - $mod_end = $this->parseDateTime($saved->getParameter('modifiedEnd')); - - if ($mod_start) { - $query->withDateModifiedAfter($mod_start); - } - - if ($mod_end) { - $query->withDateModifiedBefore($mod_end); - } - - $this->applyCustomFieldsToQuery($query, $saved); - return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved) { - - $assigned_phids = $this->readAssignedPHIDs($saved); - - $author_phids = $saved->getParameter('authorPHIDs', array()); - $projects = $this->readProjectTokens($saved); - - $subscriber_phids = $saved->getParameter('subscriberPHIDs', array()); - - $statuses = $saved->getParameter('statuses', array()); - $priorities = $saved->getParameter('priorities', array()); - - $blocking_control = id(new AphrontFormSelectControl()) - ->setLabel(pht('Blocking')) - ->setName('blocking') - ->setValue($this->getBoolFromQuery($saved, 'blocking')) - ->setOptions(array( - '' => pht('Show All Tasks'), - 'true' => pht('Show Tasks Blocking Other Tasks'), - 'false' => pht('Show Tasks Not Blocking Other Tasks'), - )); - - $blocked_control = id(new AphrontFormSelectControl()) - ->setLabel(pht('Blocked')) - ->setName('blocked') - ->setValue($this->getBoolFromQuery($saved, 'blocked')) - ->setOptions(array( - '' => pht('Show All Tasks'), - 'true' => pht('Show Tasks Blocked By Other Tasks'), - 'false' => pht('Show Tasks Not Blocked By Other Tasks'), - )); - - $ids = $saved->getParameter('ids', array()); - - $builtin_orders = $this->getOrderOptions(); - $custom_orders = $this->getCustomFieldOrderOptions(); - $all_orders = $builtin_orders + $custom_orders; - - $form - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorPeopleOwnerDatasource()) - ->setName('assigned') - ->setLabel(pht('Assigned To')) - ->setValue($assigned_phids)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorProjectLogicalDatasource()) - ->setName('projects') - ->setLabel(pht('Projects')) - ->setValue($projects)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorPeopleUserFunctionDatasource()) - ->setName('authors') - ->setLabel(pht('Authors')) - ->setValue($author_phids)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorMetaMTAMailableFunctionDatasource()) - ->setName('subscribers') - ->setLabel(pht('Subscribers')) - ->setValue($subscriber_phids)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new ManiphestTaskStatusFunctionDatasource()) - ->setLabel(pht('Statuses')) - ->setName('statuses') - ->setValue($statuses)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new ManiphestTaskPriorityDatasource()) - ->setLabel(pht('Priorities')) - ->setName('priorities') - ->setValue($priorities)) - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('fulltext') - ->setLabel(pht('Contains Words')) - ->setValue($saved->getParameter('fulltext'))) - ->appendChild($blocking_control) - ->appendChild($blocked_control); - - if (!$this->getIsBoardView()) { - $form - ->appendChild( - id(new AphrontFormSelectControl()) - ->setName('group') - ->setLabel(pht('Group By')) - ->setValue($saved->getParameter('group')) - ->setOptions($this->getGroupOptions())) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setName('order') - ->setLabel(pht('Order By')) - ->setValue($saved->getParameter('order')) - ->setOptions($all_orders)); - } - - $form - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('ids') - ->setLabel(pht('Task IDs')) - ->setValue(implode(', ', $ids))); - - $this->appendCustomFieldsToForm($form, $saved); - - $this->buildDateRange( - $form, - $saved, - 'createdStart', - pht('Created After'), - 'createdEnd', - pht('Created Before')); - - $this->buildDateRange( - $form, - $saved, - 'modifiedStart', - pht('Updated After'), - 'modifiedEnd', - pht('Updated Before')); - - if (!$this->getIsBoardView()) { - $form - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('limit') - ->setLabel(pht('Page Size')) - ->setValue($saved->getParameter('limit', 100))); - } - } - protected function getURI($path) { if ($this->baseURI) { return $this->baseURI.$path; @@ -405,24 +274,6 @@ final class ManiphestTaskSearchEngine return parent::buildSavedQueryFromBuiltin($query_key); } - private function getOrderOptions() { - return array( - 'priority' => pht('Priority'), - 'updated' => pht('Date Updated'), - 'created' => pht('Date Created'), - 'title' => pht('Title'), - ); - } - - private function getOrderValues() { - return array( - 'priority' => ManiphestTaskQuery::ORDER_PRIORITY, - 'updated' => ManiphestTaskQuery::ORDER_MODIFIED, - 'created' => ManiphestTaskQuery::ORDER_CREATED, - 'title' => ManiphestTaskQuery::ORDER_TITLE, - ); - } - private function getGroupOptions() { return array( 'priority' => pht('Priority'), @@ -474,48 +325,52 @@ final class ManiphestTaskSearchEngine ->setShowBatchControls($this->showBatchControls); } - private function readAssignedPHIDs(PhabricatorSavedQuery $saved) { - $assigned_phids = $saved->getParameter('assignedPHIDs', array()); + protected function willUseSavedQuery(PhabricatorSavedQuery $saved) { - // This may be present in old saved queries from before parameterized - // typeaheads, and is retained for compatibility. We could remove it by - // migrating old saved queries. + // The 'withUnassigned' parameter may be present in old saved queries from + // before parameterized typeaheads, and is retained for compatibility. We + // could remove it by migrating old saved queries. + $assigned_phids = $saved->getParameter('assignedPHIDs', array()); if ($saved->getParameter('withUnassigned')) { $assigned_phids[] = PhabricatorPeopleNoOwnerDatasource::FUNCTION_TOKEN; } + $saved->setParameter('assignedPHIDs', $assigned_phids); - return $assigned_phids; - } + // The 'projects' and other parameters may be present in old saved queries + // from before parameterized typeaheads. + $project_phids = $saved->getParameter('projectPHIDs', array()); - private function readProjectTokens(PhabricatorSavedQuery $saved) { - $projects = $saved->getParameter('projects', array()); + $old = $saved->getParameter('projects', array()); + foreach ($old as $phid) { + $project_phids[] = $phid; + } $all = $saved->getParameter('allProjectPHIDs', array()); foreach ($all as $phid) { - $projects[] = $phid; + $project_phids[] = $phid; } $any = $saved->getParameter('anyProjectPHIDs', array()); foreach ($any as $phid) { - $projects[] = 'any('.$phid.')'; + $project_phids[] = 'any('.$phid.')'; } $not = $saved->getParameter('excludeProjectPHIDs', array()); foreach ($not as $phid) { - $projects[] = 'not('.$phid.')'; + $project_phids[] = 'not('.$phid.')'; } $users = $saved->getParameter('userProjectPHIDs', array()); foreach ($users as $phid) { - $projects[] = 'projects('.$phid.')'; + $project_phids[] = 'projects('.$phid.')'; } $no = $saved->getParameter('withNoProject'); if ($no) { - $projects[] = 'null()'; + $project_phids[] = 'null()'; } - return $projects; + $saved->setParameter('projectPHIDs', $project_phids); } } diff --git a/src/applications/maniphest/search/ManiphestSearchIndexer.php b/src/applications/maniphest/search/ManiphestSearchIndexer.php index 5dfdee6769..4ac9a51608 100644 --- a/src/applications/maniphest/search/ManiphestSearchIndexer.php +++ b/src/applications/maniphest/search/ManiphestSearchIndexer.php @@ -17,7 +17,7 @@ final class ManiphestSearchIndexer extends PhabricatorSearchDocumentIndexer { $doc->setDocumentModified($task->getDateModified()); $doc->addField( - PhabricatorSearchField::FIELD_BODY, + PhabricatorSearchDocumentFieldType::FIELD_BODY, $task->getDescription()); $doc->addRelationship( diff --git a/src/applications/maniphest/storage/ManiphestTask.php b/src/applications/maniphest/storage/ManiphestTask.php index 3e133fe65e..cdf4b4a575 100644 --- a/src/applications/maniphest/storage/ManiphestTask.php +++ b/src/applications/maniphest/storage/ManiphestTask.php @@ -352,17 +352,7 @@ final class ManiphestTask extends ManiphestDAO PhabricatorDestructionEngine $engine) { $this->openTransaction(); - - // TODO: Once this implements PhabricatorTransactionInterface, this - // will be handled automatically and can be removed. - $xactions = id(new ManiphestTransaction())->loadAllWhere( - 'objectPHID = %s', - $this->getPHID()); - foreach ($xactions as $xaction) { - $engine->destroyObject($xaction); - } - - $this->delete(); + $this->delete(); $this->saveTransaction(); } diff --git a/src/applications/meta/controller/PhabricatorApplicationUninstallController.php b/src/applications/meta/controller/PhabricatorApplicationUninstallController.php index eaedb3a069..b43c728257 100644 --- a/src/applications/meta/controller/PhabricatorApplicationUninstallController.php +++ b/src/applications/meta/controller/PhabricatorApplicationUninstallController.php @@ -53,16 +53,16 @@ final class PhabricatorApplicationUninstallController if ($this->action == 'install') { if ($selected->canUninstall()) { $dialog - ->setTitle('Confirmation') + ->setTitle(pht('Confirmation')) ->appendChild( pht( 'Install %s application?', $selected->getName())) - ->addSubmitButton('Install'); + ->addSubmitButton(pht('Install')); } else { $dialog - ->setTitle('Information') + ->setTitle(pht('Information')) ->appendChild(pht('You cannot install an installed application.')); } } else { diff --git a/src/applications/metamta/PhabricatorMail.php b/src/applications/metamta/PhabricatorMail.php deleted file mode 100644 index c0ef8c559b..0000000000 --- a/src/applications/metamta/PhabricatorMail.php +++ /dev/null @@ -1,16 +0,0 @@ -actor = $actor; - return $this; - } - - public function getActor() { - return $this->actor; - } - -} diff --git a/src/applications/metamta/constants/MetaMTANotificationType.php b/src/applications/metamta/constants/MetaMTANotificationType.php deleted file mode 100644 index 7406af4928..0000000000 --- a/src/applications/metamta/constants/MetaMTANotificationType.php +++ /dev/null @@ -1,19 +0,0 @@ -verifyMessage()) { throw new Exception( - 'Mail signature is not valid. Check your Mailgun API key.'); + pht('Mail signature is not valid. Check your Mailgun API key.')); } $request = $this->getRequest(); diff --git a/src/applications/metamta/query/PhabricatorMetaMTAActorQuery.php b/src/applications/metamta/query/PhabricatorMetaMTAActorQuery.php index 1f593472a6..a1f0aec057 100644 --- a/src/applications/metamta/query/PhabricatorMetaMTAActorQuery.php +++ b/src/applications/metamta/query/PhabricatorMetaMTAActorQuery.php @@ -39,9 +39,6 @@ final class PhabricatorMetaMTAActorQuery extends PhabricatorQuery { case PhabricatorPeopleExternalPHIDType::TYPECONST: $this->loadExternalUserActors($actors, $phids); break; - case PhabricatorMailingListListPHIDType::TYPECONST: - $this->loadMailingListActors($actors, $phids); - break; default: $this->loadUnknownActors($actors, $phids); break; @@ -124,28 +121,6 @@ final class PhabricatorMetaMTAActorQuery extends PhabricatorQuery { } } - private function loadMailingListActors(array $actors, array $phids) { - assert_instances_of($actors, 'PhabricatorMetaMTAActor'); - - $lists = id(new PhabricatorMailingListQuery()) - ->setViewer($this->getViewer()) - ->withPHIDs($phids) - ->execute(); - $lists = mpull($lists, null, 'getPHID'); - - foreach ($phids as $phid) { - $actor = $actors[$phid]; - - $list = idx($lists, $phid); - if (!$list) { - $actor->setUndeliverable(PhabricatorMetaMTAActor::REASON_UNLOADABLE); - continue; - } - - $actor->setName($list->getName()); - $actor->setEmailAddress($list->getEmail()); - } - } private function loadUnknownActors(array $actors, array $phids) { foreach ($phids as $phid) { diff --git a/src/applications/metamta/receiver/__tests__/PhabricatorMailReceiverTestCase.php b/src/applications/metamta/receiver/__tests__/PhabricatorMailReceiverTestCase.php index f5b6ab578a..37eb757616 100644 --- a/src/applications/metamta/receiver/__tests__/PhabricatorMailReceiverTestCase.php +++ b/src/applications/metamta/receiver/__tests__/PhabricatorMailReceiverTestCase.php @@ -18,7 +18,7 @@ final class PhabricatorMailReceiverTestCase extends PhabricatorTestCase { foreach ($same as $address) { $this->assertTrue( PhabricatorMailReceiver::matchAddresses($base, $address), - "Address {$address}"); + pht('Address %s', $address)); } $diff = array( diff --git a/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php b/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php index 697c0622fe..98d6a46fc9 100644 --- a/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php +++ b/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php @@ -47,7 +47,7 @@ abstract class PhabricatorMailReplyHandler { abstract public function validateMailReceiver($mail_receiver); abstract public function getPrivateReplyHandlerEmailAddress( - PhabricatorObjectHandle $handle); + PhabricatorUser $user); public function getReplyHandlerDomain() { return PhabricatorEnv::getEnvConfig('metamta.reply-handler-domain'); @@ -117,151 +117,6 @@ abstract class PhabricatorMailReplyHandler { return null; } - final public function getRecipientsSummary( - array $to_handles, - array $cc_handles) { - assert_instances_of($to_handles, 'PhabricatorObjectHandle'); - assert_instances_of($cc_handles, 'PhabricatorObjectHandle'); - - $body = ''; - - 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; - } - - final public function getRecipientsSummaryHTML( - array $to_handles, - array $cc_handles) { - assert_instances_of($to_handles, 'PhabricatorObjectHandle'); - assert_instances_of($cc_handles, 'PhabricatorObjectHandle'); - - if (PhabricatorEnv::getEnvConfig('metamta.recipients.show-hints')) { - $body = array(); - if ($to_handles) { - $body[] = phutil_tag('strong', array(), 'To: '); - $body[] = phutil_implode_html(', ', mpull($to_handles, 'getName')); - $body[] = phutil_tag('br'); - } - if ($cc_handles) { - $body[] = phutil_tag('strong', array(), 'Cc: '); - $body[] = phutil_implode_html(', ', mpull($cc_handles, 'getName')); - $body[] = phutil_tag('br'); - } - return phutil_tag('div', array(), $body); - } else { - return ''; - } - - } - - final public function multiplexMail( - PhabricatorMetaMTAMail $mail_template, - array $to_handles, - array $cc_handles) { - assert_instances_of($to_handles, 'PhabricatorObjectHandle'); - assert_instances_of($cc_handles, 'PhabricatorObjectHandle'); - - $result = array(); - - // If MetaMTA is configured to always multiplex, skip the single-email - // case. - if (!PhabricatorMetaMTAMail::shouldMultiplexAllMail()) { - // If private replies are not supported, simply send one email to all - // recipients and CCs. This covers cases where we have no reply handler, - // or we have a public reply handler. - if (!$this->supportsPrivateReplies()) { - $mail = clone $mail_template; - $mail->addTos(mpull($to_handles, 'getPHID')); - $mail->addCCs(mpull($cc_handles, 'getPHID')); - - if ($this->supportsPublicReplies()) { - $reply_to = $this->getPublicReplyHandlerEmailAddress(); - $mail->setReplyTo($reply_to); - } - - $result[] = $mail; - - return $result; - } - } - - // TODO: This is pretty messy. We should really be doing all of this - // multiplexing in the task queue, but that requires significant rewriting - // in the general case. ApplicationTransactions can do it fairly easily, - // but other mail sites currently can not, so we need to support this - // junky version until they catch up and we can swap things over. - - $to_handles = $this->expandRecipientHandles($to_handles); - $cc_handles = $this->expandRecipientHandles($cc_handles); - - $tos = mpull($to_handles, null, 'getPHID'); - $ccs = mpull($cc_handles, null, 'getPHID'); - - // Merge all the recipients together. TODO: We could keep the CCs as real - // CCs and send to a "noreply@domain.com" type address, but keep it simple - // for now. - $recipients = $tos + $ccs; - - // When multiplexing mail, explicitly include To/Cc information in the - // message body and headers. - - $mail_template = clone $mail_template; - - $mail_template->addPHIDHeaders('X-Phabricator-To', array_keys($tos)); - $mail_template->addPHIDHeaders('X-Phabricator-Cc', array_keys($ccs)); - - $body = $mail_template->getBody(); - $body .= "\n"; - $body .= $this->getRecipientsSummary($to_handles, $cc_handles); - - $html_body = $mail_template->getHTMLBody(); - if (strlen($html_body)) { - $html_body .= hsprintf('%s', - $this->getRecipientsSummaryHTML($to_handles, $cc_handles)); - } - - foreach ($recipients as $phid => $recipient) { - - $mail = clone $mail_template; - if (isset($to_handles[$phid])) { - $mail->addTos(array($phid)); - } else if (isset($cc_handles[$phid])) { - $mail->addCCs(array($phid)); - } else { - // not good - they should be a to or a cc - continue; - } - - $mail->setBody($body); - $mail->setHTMLBody($html_body); - - $reply_to = null; - if (!$reply_to && $this->supportsPrivateReplies()) { - $reply_to = $this->getPrivateReplyHandlerEmailAddress($recipient); - } - - if (!$reply_to && $this->supportsPublicReplies()) { - $reply_to = $this->getPublicReplyHandlerEmailAddress(); - } - - if ($reply_to) { - $mail->setReplyTo($reply_to); - } - - $result[] = $mail; - } - - return $result; - } - protected function getDefaultPublicReplyHandlerEmailAddress($prefix) { $receiver = $this->getMailReceiver(); @@ -288,31 +143,15 @@ abstract class PhabricatorMailReplyHandler { } protected function getDefaultPrivateReplyHandlerEmailAddress( - PhabricatorObjectHandle $handle, + PhabricatorUser $user, $prefix) { - if ($handle->getType() != PhabricatorPeopleUserPHIDType::TYPECONST) { - // You must be a real user to get a private reply handler address. - return null; - } - - $user = id(new PhabricatorPeopleQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPHIDs(array($handle->getPHID())) - ->executeOne(); - - if (!$user) { - // This may happen if a user was subscribed to something, and was then - // deleted. - return null; - } - $receiver = $this->getMailReceiver(); $receiver_id = $receiver->getID(); $user_id = $user->getID(); $hash = PhabricatorObjectMailReceiver::computeMailHash( $receiver->getMailKey(), - $handle->getPHID()); + $user->getPHID()); $domain = $this->getReplyHandlerDomain(); $address = "{$prefix}{$receiver_id}+{$user_id}+{$hash}@{$domain}"; @@ -368,21 +207,188 @@ abstract class PhabricatorMailReplyHandler { return rtrim($output); } - private function expandRecipientHandles(array $handles) { - if (!$handles) { + + /** + * Produce a list of mail targets for a given to/cc list. + * + * Each target should be sent a separate email, and contains the information + * required to generate it with appropriate permissions and configuration. + * + * @param list List of "To" PHIDs. + * @param list List of "CC" PHIDs. + * @return list List of targets. + */ + final public function getMailTargets(array $raw_to, array $raw_cc) { + list($to, $cc) = $this->expandRecipientPHIDs($raw_to, $raw_cc); + list($to, $cc) = $this->loadRecipientUsers($to, $cc); + list($to, $cc) = $this->filterRecipientUsers($to, $cc); + + if (!$to && !$cc) { return array(); } - $phids = mpull($handles, 'getPHID'); - $results = id(new PhabricatorMetaMTAMemberQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPHIDs($phids) - ->executeExpansion(); + $template = id(new PhabricatorMailTarget()) + ->setRawToPHIDs($raw_to) + ->setRawCCPHIDs($raw_cc); - return id(new PhabricatorHandleQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPHIDs($results) - ->execute(); + // Set the public reply address as the default, if one exists. We + // might replace this with a private address later. + if ($this->supportsPublicReplies()) { + $reply_to = $this->getPublicReplyHandlerEmailAddress(); + if ($reply_to) { + $template->setReplyTo($reply_to); + } + } + + $supports_private_replies = $this->supportsPrivateReplies(); + $mail_all = !PhabricatorEnv::getEnvConfig('metamta.one-mail-per-recipient'); + $targets = array(); + if ($mail_all) { + $target = id(clone $template) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->setToMap($to) + ->setCCMap($cc); + + $targets[] = $target; + } else { + $map = $to + $cc; + + foreach ($map as $phid => $user) { + $target = id(clone $template) + ->setViewer($user) + ->setToMap(array($phid => $user)) + ->setCCMap(array()); + + if ($supports_private_replies) { + $reply_to = $this->getPrivateReplyHandlerEmailAddress($user); + if ($reply_to) { + $target->setReplyTo($reply_to); + } + } + + $targets[] = $target; + } + } + + return $targets; + } + + + /** + * Expand lists of recipient PHIDs. + * + * This takes any compound recipients (like projects) and looks up all their + * members. + * + * @param list List of To PHIDs. + * @param list List of CC PHIDs. + * @return pair, list> Expanded PHID lists. + */ + private function expandRecipientPHIDs(array $to, array $cc) { + $to_result = array(); + $cc_result = array(); + + $all_phids = array_merge($to, $cc); + if ($all_phids) { + $map = id(new PhabricatorMetaMTAMemberQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs($all_phids) + ->execute(); + foreach ($to as $phid) { + foreach ($map[$phid] as $expanded) { + $to_result[$expanded] = $expanded; + } + } + foreach ($cc as $phid) { + foreach ($map[$phid] as $expanded) { + $cc_result[$expanded] = $expanded; + } + } + } + + // Remove recipients from "CC" if they're also present in "To". + $cc_result = array_diff_key($cc_result, $to_result); + + return array(array_values($to_result), array_values($cc_result)); + } + + + /** + * Load @{class:PhabricatorUser} objects for each recipient. + * + * Invalid recipients are dropped from the results. + * + * @param list List of To PHIDs. + * @param list List of CC PHIDs. + * @return pair Maps from PHIDs to users. + */ + private function loadRecipientUsers(array $to, array $cc) { + $to_result = array(); + $cc_result = array(); + + $all_phids = array_merge($to, $cc); + if ($all_phids) { + $users = id(new PhabricatorPeopleQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs($all_phids) + ->execute(); + $users = mpull($users, null, 'getPHID'); + + foreach ($to as $phid) { + if (isset($users[$phid])) { + $to_result[$phid] = $users[$phid]; + } + } + foreach ($cc as $phid) { + if (isset($users[$phid])) { + $cc_result[$phid] = $users[$phid]; + } + } + } + + return array($to_result, $cc_result); + } + + + /** + * Remove recipients who do not have permission to view the mail receiver. + * + * @param map Map of "To" users. + * @param map Map of "CC" users. + * @return pair Filtered user maps. + */ + private function filterRecipientUsers(array $to, array $cc) { + $to_result = array(); + $cc_result = array(); + + $all_users = $to + $cc; + if ($all_users) { + $can_see = array(); + $object = $this->getMailReceiver(); + foreach ($all_users as $phid => $user) { + $visible = PhabricatorPolicyFilter::hasCapability( + $user, + $object, + PhabricatorPolicyCapability::CAN_VIEW); + if ($visible) { + $can_see[$phid] = true; + } + } + + foreach ($to as $phid => $user) { + if (!empty($can_see[$phid])) { + $to_result[$phid] = $all_users[$phid]; + } + } + + foreach ($cc as $phid => $user) { + if (!empty($can_see[$phid])) { + $cc_result[$phid] = $all_users[$phid]; + } + } + } + + return array($to_result, $cc_result); } } diff --git a/src/applications/metamta/replyhandler/PhabricatorMailTarget.php b/src/applications/metamta/replyhandler/PhabricatorMailTarget.php new file mode 100644 index 0000000000..fd2e7a0f7a --- /dev/null +++ b/src/applications/metamta/replyhandler/PhabricatorMailTarget.php @@ -0,0 +1,146 @@ +rawToPHIDs = $to_phids; + return $this; + } + + public function setRawCCPHIDs(array $cc_phids) { + $this->rawCCPHIDs = $cc_phids; + return $this; + } + + public function setCCMap(array $cc_map) { + $this->ccMap = $cc_map; + return $this; + } + + public function getCCMap() { + return $this->ccMap; + } + + public function setToMap(array $to_map) { + $this->toMap = $to_map; + return $this; + } + + public function getToMap() { + return $this->toMap; + } + + public function setReplyTo($reply_to) { + $this->replyTo = $reply_to; + return $this; + } + + public function getReplyTo() { + return $this->replyTo; + } + + public function setViewer($viewer) { + $this->viewer = $viewer; + return $this; + } + + public function getViewer() { + return $this->viewer; + } + + public function sendMail(PhabricatorMetaMTAMail $mail) { + $viewer = $this->getViewer(); + + $mail->addPHIDHeaders('X-Phabricator-To', $this->rawToPHIDs); + $mail->addPHIDHeaders('X-Phabricator-Cc', $this->rawCCPHIDs); + + $to_handles = $viewer->loadHandles($this->rawToPHIDs); + $cc_handles = $viewer->loadHandles($this->rawCCPHIDs); + + $body = $mail->getBody(); + $body .= "\n"; + $body .= $this->getRecipientsSummary($to_handles, $cc_handles); + $mail->setBody($body); + + $html_body = $mail->getHTMLBody(); + if (strlen($html_body)) { + $html_body .= hsprintf( + '%s', + $this->getRecipientsSummaryHTML($to_handles, $cc_handles)); + } + $mail->setHTMLBody($html_body); + + $reply_to = $this->getReplyTo(); + if ($reply_to) { + $mail->setReplyTo($reply_to); + } + + $to = array_keys($this->getToMap()); + if ($to) { + $mail->addTos($to); + } + + $cc = array_keys($this->getCCMap()); + if ($cc) { + $mail->addCCs($cc); + } + + return $mail->save(); + } + + private function getRecipientsSummary( + PhabricatorHandleList $to_handles, + PhabricatorHandleList $cc_handles) { + + if (!PhabricatorEnv::getEnvConfig('metamta.recipients.show-hints')) { + return ''; + } + + $to_handles = iterator_to_array($to_handles); + $cc_handles = iterator_to_array($cc_handles); + + $body = ''; + if ($to_handles) { + $body .= "To: ".implode(', ', mpull($to_handles, 'getName'))."\n"; + } + if ($cc_handles) { + $body .= "Cc: ".implode(', ', mpull($cc_handles, 'getName'))."\n"; + } + + return $body; + } + + private function getRecipientsSummaryHTML( + PhabricatorHandleList $to_handles, + PhabricatorHandleList $cc_handles) { + + if (!PhabricatorEnv::getEnvConfig('metamta.recipients.show-hints')) { + return ''; + } + + $to_handles = iterator_to_array($to_handles); + $cc_handles = iterator_to_array($cc_handles); + + $body = array(); + if ($to_handles) { + $body[] = phutil_tag('strong', array(), 'To: '); + $body[] = phutil_implode_html(', ', mpull($to_handles, 'getName')); + $body[] = phutil_tag('br'); + } + if ($cc_handles) { + $body[] = phutil_tag('strong', array(), 'Cc: '); + $body[] = phutil_implode_html(', ', mpull($cc_handles, 'getName')); + $body[] = phutil_tag('br'); + } + return phutil_tag('div', array(), $body); + } + + +} diff --git a/src/applications/metamta/storage/PhabricatorMetaMTAMail.php b/src/applications/metamta/storage/PhabricatorMetaMTAMail.php index 07129d9e06..af7007cd87 100644 --- a/src/applications/metamta/storage/PhabricatorMetaMTAMail.php +++ b/src/applications/metamta/storage/PhabricatorMetaMTAMail.php @@ -64,12 +64,10 @@ final class PhabricatorMetaMTAMail extends PhabricatorMetaMTADAO { } /** - * Set tags (@{class:MetaMTANotificationType} constants) which identify the - * content of this mail in a general way. These tags are used to allow users - * to opt out of receiving certain types of mail, like updates when a task's - * projects change. + * These tags are used to allow users to opt out of receiving certain types + * of mail, like updates when a task's projects change. * - * @param list List of @{class:MetaMTANotificationType} constants. + * @param list * @return this */ public function setMailTags(array $tags) { @@ -178,6 +176,7 @@ final class PhabricatorMetaMTAMail extends PhabricatorMetaMTADAO { } public function addPHIDHeaders($name, array $phids) { + $phids = array_unique($phids); foreach ($phids as $phid) { $this->addHeader($name, '<'.$phid.'>'); } diff --git a/src/applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php b/src/applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php index 0f17f08d53..4b98bc904e 100644 --- a/src/applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php +++ b/src/applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php @@ -100,13 +100,7 @@ final class PhabricatorMetaMTAReceivedMail extends PhabricatorMetaMTADAO { } $users = id(new PhabricatorUserEmail()) ->loadAllWhere('address IN (%Ls)', $addresses); - $user_phids = mpull($users, 'getUserPHID'); - - $mailing_lists = id(new PhabricatorMetaMTAMailingList()) - ->loadAllWhere('email in (%Ls)', $addresses); - $mailing_list_phids = mpull($mailing_lists, 'getPHID'); - - return array_merge($user_phids, $mailing_list_phids); + return mpull($users, 'getUserPHID'); } public function processReceivedMail() { diff --git a/src/applications/metamta/typeahead/PhabricatorMetaMTAMailableDatasource.php b/src/applications/metamta/typeahead/PhabricatorMetaMTAMailableDatasource.php index 804e4498c0..5fa7492fef 100644 --- a/src/applications/metamta/typeahead/PhabricatorMetaMTAMailableDatasource.php +++ b/src/applications/metamta/typeahead/PhabricatorMetaMTAMailableDatasource.php @@ -19,7 +19,6 @@ final class PhabricatorMetaMTAMailableDatasource return array( new PhabricatorPeopleDatasource(), new PhabricatorProjectDatasource(), - new PhabricatorMailingListDatasource(), ); } diff --git a/src/applications/metamta/typeahead/PhabricatorMetaMTAMailableFunctionDatasource.php b/src/applications/metamta/typeahead/PhabricatorMetaMTAMailableFunctionDatasource.php index be8f75b0ee..68cb0e79b2 100644 --- a/src/applications/metamta/typeahead/PhabricatorMetaMTAMailableFunctionDatasource.php +++ b/src/applications/metamta/typeahead/PhabricatorMetaMTAMailableFunctionDatasource.php @@ -21,7 +21,6 @@ final class PhabricatorMetaMTAMailableFunctionDatasource new PhabricatorPeopleDatasource(), new PhabricatorProjectMembersDatasource(), new PhabricatorProjectDatasource(), - new PhabricatorMailingListDatasource(), ); } diff --git a/src/applications/metamta/view/PhabricatorMetaMTAMailBody.php b/src/applications/metamta/view/PhabricatorMetaMTAMailBody.php index 03f89467f4..d8061c27a7 100644 --- a/src/applications/metamta/view/PhabricatorMetaMTAMailBody.php +++ b/src/applications/metamta/view/PhabricatorMetaMTAMailBody.php @@ -158,26 +158,6 @@ final class PhabricatorMetaMTAMailBody { return $this; } - - /** - * Add a section with a link to email preferences. - * - * @return this - * @task compose - */ - public function addEmailPreferenceSection() { - if (!PhabricatorEnv::getEnvConfig('metamta.email-preferences')) { - return $this; - } - - $href = PhabricatorEnv::getProductionURI( - '/settings/panel/emailpreferences/'); - $this->addLinkSection(pht('EMAIL PREFERENCES'), $href); - - return $this; - } - - /** * Add an attachment. * diff --git a/src/applications/notification/storage/PhabricatorFeedStoryNotification.php b/src/applications/notification/storage/PhabricatorFeedStoryNotification.php index b4b139fd16..8f89c987ec 100644 --- a/src/applications/notification/storage/PhabricatorFeedStoryNotification.php +++ b/src/applications/notification/storage/PhabricatorFeedStoryNotification.php @@ -25,11 +25,14 @@ final class PhabricatorFeedStoryNotification extends PhabricatorFeedDAO { 'userPHID_2' => array( 'columns' => array('userPHID', 'hasViewed', 'primaryObjectPHID'), ), + 'key_object' => array( + 'columns' => array('primaryObjectPHID'), + ), ), ) + parent::getConfiguration(); } - static public function updateObjectNotificationViews( + public static function updateObjectNotificationViews( PhabricatorUser $user, $object_phid) { diff --git a/src/applications/nuance/application/PhabricatorNuanceApplication.php b/src/applications/nuance/application/PhabricatorNuanceApplication.php index 31f06c4b64..be297f4026 100644 --- a/src/applications/nuance/application/PhabricatorNuanceApplication.php +++ b/src/applications/nuance/application/PhabricatorNuanceApplication.php @@ -44,11 +44,14 @@ final class PhabricatorNuanceApplication extends PhabricatorApplication { 'new/' => 'NuanceItemEditController', ), 'source/' => array( + '(?:query/(?P[^/]+)/)?' => 'NuanceSourceListController', 'view/(?P[1-9]\d*)/' => 'NuanceSourceViewController', 'edit/(?P[1-9]\d*)/' => 'NuanceSourceEditController', - 'new/' => 'NuanceSourceEditController', + 'new/(?P[^/]+)/' => 'NuanceSourceEditController', + 'create/' => 'NuanceSourceCreateController', ), 'queue/' => array( + '(?:query/(?P[^/]+)/)?' => 'NuanceQueueListController', 'view/(?P[1-9]\d*)/' => 'NuanceQueueViewController', 'edit/(?P[1-9]\d*)/' => 'NuanceQueueEditController', 'new/' => 'NuanceQueueEditController', @@ -59,6 +62,9 @@ final class PhabricatorNuanceApplication extends PhabricatorApplication { 'new/' => 'NuanceRequestorEditController', ), ), + '/action/' => array( + '(?P[1-9]\d*)/(?P.*)' => 'NuanceSourceActionController', + ), ); } diff --git a/src/applications/nuance/conduit/NuanceCreateItemConduitAPIMethod.php b/src/applications/nuance/conduit/NuanceCreateItemConduitAPIMethod.php index 2bc5e9151a..b05c09fec4 100644 --- a/src/applications/nuance/conduit/NuanceCreateItemConduitAPIMethod.php +++ b/src/applications/nuance/conduit/NuanceCreateItemConduitAPIMethod.php @@ -36,7 +36,7 @@ final class NuanceCreateItemConduitAPIMethod extends NuanceConduitAPIMethod { $user = $request->getUser(); - $item = NuanceItem::initializeNewItem($user); + $item = NuanceItem::initializeNewItem(); $xactions = array(); if ($source_phid) { diff --git a/src/applications/nuance/controller/NuanceQueueEditController.php b/src/applications/nuance/controller/NuanceQueueEditController.php index 02e73c912c..cfb657615d 100644 --- a/src/applications/nuance/controller/NuanceQueueEditController.php +++ b/src/applications/nuance/controller/NuanceQueueEditController.php @@ -2,46 +2,131 @@ final class NuanceQueueEditController extends NuanceController { - private $queueID; + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $queues_uri = $this->getApplicationURI('queue/'); - public function setQueueID($queue_id) { - $this->queueID = $queue_id; - return $this; - } - public function getQueueID() { - return $this->queueID; - } - - public function willProcessRequest(array $data) { - $this->setQueueID(idx($data, 'id')); - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); - - $queue_id = $this->getQueueID(); + $queue_id = $request->getURIData('id'); $is_new = !$queue_id; - if ($is_new) { - $queue = new NuanceQueue(); - + $queue = NuanceQueue::initializeNewQueue(); + $cancel_uri = $queues_uri; } else { $queue = id(new NuanceQueueQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withIDs(array($queue_id)) ->executeOne(); + if (!$queue) { + return new Aphront404Response(); + } + $cancel_uri = $queue->getURI(); } - if (!$queue) { - return new Aphront404Response(); + $v_name = $queue->getName(); + $e_name = true; + $v_edit = $queue->getEditPolicy(); + $v_view = $queue->getViewPolicy(); + + $validation_exception = null; + if ($request->isFormPost()) { + $e_name = null; + + $v_name = $request->getStr('name'); + $v_edit = $request->getStr('editPolicy'); + $v_view = $request->getStr('viewPolicy'); + + $type_name = NuanceQueueTransaction::TYPE_NAME; + $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; + $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; + + $xactions = array(); + + $xactions[] = id(new NuanceQueueTransaction()) + ->setTransactionType($type_name) + ->setNewValue($v_name); + + $xactions[] = id(new NuanceQueueTransaction()) + ->setTransactionType($type_view) + ->setNewValue($v_view); + + $xactions[] = id(new NuanceQueueTransaction()) + ->setTransactionType($type_edit) + ->setNewValue($v_edit); + + $editor = id(new NuanceQueueEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true); + + try { + + $editor->applyTransactions($queue, $xactions); + + $uri = $queue->getURI(); + return id(new AphrontRedirectResponse())->setURI($uri); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $validation_exception = $ex; + + $e_name = $ex->getShortMessage($type_name); + } } $crumbs = $this->buildApplicationCrumbs(); - $title = 'TODO'; + $crumbs->addTextCrumb(pht('Queues'), $queues_uri); + + if ($is_new) { + $title = pht('Create Queue'); + $crumbs->addTextCrumb(pht('Create')); + } else { + $title = pht('Edit %s', $queue->getName()); + $crumbs->addTextCrumb($queue->getName(), $queue->getURI()); + $crumbs->addTextCrumb(pht('Edit')); + } + + $policies = id(new PhabricatorPolicyQuery()) + ->setViewer($viewer) + ->setObject($queue) + ->execute(); + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel(pht('Name')) + ->setName('name') + ->setError($e_name) + ->setValue($v_name)) + ->appendChild( + id(new AphrontFormPolicyControl()) + ->setUser($viewer) + ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) + ->setPolicyObject($queue) + ->setPolicies($policies) + ->setValue($v_view) + ->setName('viewPolicy')) + ->appendChild( + id(new AphrontFormPolicyControl()) + ->setUser($viewer) + ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) + ->setPolicyObject($queue) + ->setPolicies($policies) + ->setValue($v_edit) + ->setName('editPolicy')) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->addCancelButton($cancel_uri) + ->setValue(pht('Save'))); + + $box = id(new PHUIObjectBoxView()) + ->setHeaderText($title) + ->setValidationException($validation_exception) + ->appendChild($form); return $this->buildApplicationPage( - $crumbs, + array( + $crumbs, + $box, + ), array( 'title' => $title, )); diff --git a/src/applications/nuance/controller/NuanceQueueListController.php b/src/applications/nuance/controller/NuanceQueueListController.php new file mode 100644 index 0000000000..e139386bdf --- /dev/null +++ b/src/applications/nuance/controller/NuanceQueueListController.php @@ -0,0 +1,48 @@ +getRequest(); + $controller = id(new PhabricatorApplicationSearchController($request)) + ->setQueryKey($request->getURIData('queryKey')) + ->setSearchEngine(new NuanceQueueSearchEngine()) + ->setNavigation($this->buildSideNavView()); + + return $this->delegateToController($controller); + } + + public function buildSideNavView($for_app = false) { + $user = $this->getRequest()->getUser(); + + $nav = new AphrontSideNavFilterView(); + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); + + id(new NuanceQueueSearchEngine()) + ->setViewer($user) + ->addNavigationItems($nav->getMenu()); + + $nav->selectFilter(null); + + return $nav; + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + // TODO: Maybe use SourceManage capability? + $can_create = true; + + $crumbs->addAction( + id(new PHUIListItemView()) + ->setName(pht('Create Queue')) + ->setHref($this->getApplicationURI('queue/new/')) + ->setIcon('fa-plus-square') + ->setDisabled(!$can_create) + ->setWorkflow(!$can_create)); + + return $crumbs; + } + +} diff --git a/src/applications/nuance/controller/NuanceQueueViewController.php b/src/applications/nuance/controller/NuanceQueueViewController.php index 261b528193..d619cfb946 100644 --- a/src/applications/nuance/controller/NuanceQueueViewController.php +++ b/src/applications/nuance/controller/NuanceQueueViewController.php @@ -2,41 +2,92 @@ final class NuanceQueueViewController extends NuanceController { - private $queueID; + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); - public function setQueueID($queue_id) { - $this->queueID = $queue_id; - return $this; - } - public function getQueueID() { - return $this->queueID; - } - - public function willProcessRequest(array $data) { - $this->setQueueID($data['id']); - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); - - $queue_id = $this->getQueueID(); $queue = id(new NuanceQueueQuery()) - ->setViewer($user) - ->withIDs(array($queue_id)) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) ->executeOne(); - if (!$queue) { return new Aphront404Response(); } + $title = $queue->getName(); + $crumbs = $this->buildApplicationCrumbs(); - $title = pht('TODO'); + $crumbs->addTextCrumb(pht('Queues'), $this->getApplicationURI('queue/')); + $crumbs->addTextCrumb($queue->getName()); + + $header = $this->buildHeaderView($queue); + $actions = $this->buildActionView($queue); + $properties = $this->buildPropertyView($queue, $actions); + + $box = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->addPropertyList($properties); + + $timeline = $this->buildTransactionTimeline( + $queue, + new NuanceQueueTransactionQuery()); + $timeline->setShouldTerminate(true); return $this->buildApplicationPage( - $crumbs, + array( + $crumbs, + $box, + $timeline, + ), array( 'title' => $title, )); } + + private function buildHeaderView(NuanceQueue $queue) { + $viewer = $this->getViewer(); + + $header = id(new PHUIHeaderView()) + ->setUser($viewer) + ->setHeader($queue->getName()) + ->setPolicyObject($queue); + + return $header; + } + + private function buildActionView(NuanceQueue $queue) { + $viewer = $this->getViewer(); + $id = $queue->getID(); + + $actions = id(new PhabricatorActionListView()) + ->setObjectURI($queue->getURI()) + ->setUser($viewer); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $queue, + PhabricatorPolicyCapability::CAN_EDIT); + + $actions->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Queue')) + ->setIcon('fa-pencil') + ->setHref($this->getApplicationURI("queue/edit/{$id}/")) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); + + return $actions; + } + + private function buildPropertyView( + NuanceQueue $queue, + PhabricatorActionListView $actions) { + $viewer = $this->getViewer(); + + $properties = id(new PHUIPropertyListView()) + ->setUser($viewer) + ->setObject($queue) + ->setActionList($actions); + + return $properties; + } } diff --git a/src/applications/nuance/controller/NuanceSourceActionController.php b/src/applications/nuance/controller/NuanceSourceActionController.php new file mode 100644 index 0000000000..6e9fabe455 --- /dev/null +++ b/src/applications/nuance/controller/NuanceSourceActionController.php @@ -0,0 +1,38 @@ +getViewer(); + + $source = id(new NuanceSourceQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) + ->executeOne(); + if (!$source) { + return new Aphront404Response(); + } + + $def = NuanceSourceDefinition::getDefinitionForSource($source); + $def->setActor($viewer); + + $response = $def->handleActionRequest($request); + if ($response instanceof AphrontResponse) { + return $response; + } + + $title = $source->getName(); + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb($title); + + return $this->buildApplicationPage( + array( + $crumbs, + $response, + ), + array( + 'title' => $title, + )); + } + +} diff --git a/src/applications/nuance/controller/NuanceSourceCreateController.php b/src/applications/nuance/controller/NuanceSourceCreateController.php new file mode 100644 index 0000000000..22dd41024c --- /dev/null +++ b/src/applications/nuance/controller/NuanceSourceCreateController.php @@ -0,0 +1,57 @@ +requireApplicationCapability( + NuanceSourceManageCapability::CAPABILITY); + + $viewer = $this->getViewer(); + $map = NuanceSourceDefinition::getAllDefinitions(); + $cancel_uri = $this->getApplicationURI('source/'); + + if ($request->isFormPost()) { + $type = $request->getStr('type'); + if (isset($map[$type])) { + $uri = $this->getApplicationURI('source/new/'.$type.'/'); + return id(new AphrontRedirectResponse())->setURI($uri); + } + } + + $source_types = id(new AphrontFormRadioButtonControl()) + ->setName('type') + ->setLabel(pht('Source Type')); + + foreach ($map as $type => $definition) { + $source_types->addButton( + $type, + $definition->getName(), + $definition->getSourceDescription()); + } + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendChild($source_types) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue(pht('Continue')) + ->addCancelButton($cancel_uri)); + + $box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Choose Source Type')) + ->appendChild($form); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Sources'), $cancel_uri); + $crumbs->addTextCrumb(pht('New')); + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + ), + array( + 'title' => pht('Choose Source Type'), + )); + } +} diff --git a/src/applications/nuance/controller/NuanceSourceEditController.php b/src/applications/nuance/controller/NuanceSourceEditController.php index 7b4f20ba67..79bc8c94dc 100644 --- a/src/applications/nuance/controller/NuanceSourceEditController.php +++ b/src/applications/nuance/controller/NuanceSourceEditController.php @@ -2,35 +2,32 @@ final class NuanceSourceEditController extends NuanceController { - private $sourceID; - - public function setSourceID($source_id) { - $this->sourceID = $source_id; - return $this; - } - public function getSourceID() { - return $this->sourceID; - } - - public function willProcessRequest(array $data) { - $this->setSourceID(idx($data, 'id')); - } - - public function processRequest() { + public function handleRequest(AphrontRequest $request) { $can_edit = $this->requireApplicationCapability( NuanceSourceManageCapability::CAPABILITY); - $request = $this->getRequest(); - $user = $request->getUser(); + $viewer = $this->getViewer(); - $source_id = $this->getSourceID(); + $sources_uri = $this->getApplicationURI('source/'); + + $source_id = $request->getURIData('id'); $is_new = !$source_id; if ($is_new) { - $source = NuanceSource::initializeNewSource($user); + $source = NuanceSource::initializeNewSource($viewer); + + $type = $request->getURIData('type'); + $map = NuanceSourceDefinition::getAllDefinitions(); + + if (empty($map[$type])) { + return new Aphront404Response(); + } + + $source->setType($type); + $cancel_uri = $sources_uri; } else { $source = id(new NuanceSourceQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withIDs(array($source_id)) ->requireCapabilities( array( @@ -38,14 +35,14 @@ final class NuanceSourceEditController extends NuanceController { PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); - } - - if (!$source) { - return new Aphront404Response(); + if (!$source) { + return new Aphront404Response(); + } + $cancel_uri = $source->getURI(); } $definition = NuanceSourceDefinition::getDefinitionForSource($source); - $definition->setActor($user); + $definition->setActor($viewer); $response = $definition->buildEditLayout($request); if ($response instanceof AphrontResponse) { @@ -54,6 +51,15 @@ final class NuanceSourceEditController extends NuanceController { $layout = $response; $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Sources'), $sources_uri); + + if ($is_new) { + $crumbs->addTextCrumb(pht('New')); + } else { + $crumbs->addTextCrumb($source->getName(), $cancel_uri); + $crumbs->addTextCrumb(pht('Edit')); + } + return $this->buildApplicationPage( array( $crumbs, diff --git a/src/applications/nuance/controller/NuanceSourceListController.php b/src/applications/nuance/controller/NuanceSourceListController.php new file mode 100644 index 0000000000..1d906dc3d3 --- /dev/null +++ b/src/applications/nuance/controller/NuanceSourceListController.php @@ -0,0 +1,48 @@ +getRequest(); + $controller = id(new PhabricatorApplicationSearchController($request)) + ->setQueryKey($request->getURIData('queryKey')) + ->setSearchEngine(new NuanceSourceSearchEngine()) + ->setNavigation($this->buildSideNavView()); + + return $this->delegateToController($controller); + } + + public function buildSideNavView($for_app = false) { + $user = $this->getRequest()->getUser(); + + $nav = new AphrontSideNavFilterView(); + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); + + id(new NuanceSourceSearchEngine()) + ->setViewer($user) + ->addNavigationItems($nav->getMenu()); + + $nav->selectFilter(null); + + return $nav; + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $can_create = $this->hasApplicationCapability( + NuanceSourceManageCapability::CAPABILITY); + + $crumbs->addAction( + id(new PHUIListItemView()) + ->setName(pht('Create Source')) + ->setHref($this->getApplicationURI('source/create/')) + ->setIcon('fa-plus-square') + ->setDisabled(!$can_create) + ->setWorkflow(!$can_create)); + + return $crumbs; + } + +} diff --git a/src/applications/nuance/controller/NuanceSourceViewController.php b/src/applications/nuance/controller/NuanceSourceViewController.php index 60487582d1..edff9fb0a1 100644 --- a/src/applications/nuance/controller/NuanceSourceViewController.php +++ b/src/applications/nuance/controller/NuanceSourceViewController.php @@ -2,30 +2,13 @@ final class NuanceSourceViewController extends NuanceController { - private $sourceID; + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); - public function setSourceID($source_id) { - $this->sourceID = $source_id; - return $this; - } - public function getSourceID() { - return $this->sourceID; - } - - public function willProcessRequest(array $data) { - $this->setSourceID($data['id']); - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); - - $source_id = $this->getSourceID(); $source = id(new NuanceSourceQuery()) ->setViewer($viewer) - ->withIDs(array($source_id)) + ->withIDs(array($request->getURIData('id'))) ->executeOne(); - if (!$source) { return new Aphront404Response(); } @@ -37,10 +20,6 @@ final class NuanceSourceViewController extends NuanceController { new NuanceSourceTransactionQuery()); $timeline->setShouldTerminate(true); - $title = pht('%s', $source->getName()); - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb($title); - $header = $this->buildHeaderView($source); $actions = $this->buildActionView($source); $properties = $this->buildPropertyView($source, $actions); @@ -49,6 +28,12 @@ final class NuanceSourceViewController extends NuanceController { ->setHeader($header) ->addPropertyList($properties); + $title = $source->getName(); + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Sources'), $this->getApplicationURI('source/')); + + $crumbs->addTextCrumb($title); + return $this->buildApplicationPage( array( $crumbs, @@ -58,12 +43,10 @@ final class NuanceSourceViewController extends NuanceController { array( 'title' => $title, )); - } - - private function buildHeaderView(NuanceSource $source) { - $viewer = $this->getRequest()->getUser(); + private function buildHeaderView(NuanceSource $source) { + $viewer = $this->getViewer(); $header = id(new PHUIHeaderView()) ->setUser($viewer) @@ -74,7 +57,7 @@ final class NuanceSourceViewController extends NuanceController { } private function buildActionView(NuanceSource $source) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $id = $source->getID(); $actions = id(new PhabricatorActionListView()) diff --git a/src/applications/nuance/editor/NuanceItemEditor.php b/src/applications/nuance/editor/NuanceItemEditor.php index f47318de9b..49382f5350 100644 --- a/src/applications/nuance/editor/NuanceItemEditor.php +++ b/src/applications/nuance/editor/NuanceItemEditor.php @@ -17,6 +17,7 @@ final class NuanceItemEditor $types[] = NuanceItemTransaction::TYPE_OWNER; $types[] = NuanceItemTransaction::TYPE_SOURCE; $types[] = NuanceItemTransaction::TYPE_REQUESTOR; + $types[] = NuanceItemTransaction::TYPE_PROPERTY; $types[] = PhabricatorTransactions::TYPE_EDGE; $types[] = PhabricatorTransactions::TYPE_COMMENT; @@ -37,6 +38,10 @@ final class NuanceItemEditor return $object->getSourcePHID(); case NuanceItemTransaction::TYPE_OWNER: return $object->getOwnerPHID(); + case NuanceItemTransaction::TYPE_PROPERTY: + $key = $xaction->getMetadataValue( + NuanceItemTransaction::PROPERTY_KEY); + return $object->getNuanceProperty($key); } return parent::getCustomTransactionOldValue($object, $xaction); @@ -50,6 +55,7 @@ final class NuanceItemEditor case NuanceItemTransaction::TYPE_REQUESTOR: case NuanceItemTransaction::TYPE_SOURCE: case NuanceItemTransaction::TYPE_OWNER: + case NuanceItemTransaction::TYPE_PROPERTY: return $xaction->getNewValue(); } @@ -70,6 +76,11 @@ final class NuanceItemEditor case NuanceItemTransaction::TYPE_OWNER: $object->setOwnerPHID($xaction->getNewValue()); break; + case NuanceItemTransaction::TYPE_PROPERTY: + $key = $xaction->getMetadataValue( + NuanceItemTransaction::PROPERTY_KEY); + $object->setNuanceProperty($key, $xaction->getNewValue()); + break; } } @@ -81,6 +92,7 @@ final class NuanceItemEditor case NuanceItemTransaction::TYPE_REQUESTOR: case NuanceItemTransaction::TYPE_SOURCE: case NuanceItemTransaction::TYPE_OWNER: + case NuanceItemTransaction::TYPE_PROPERTY: return; } diff --git a/src/applications/nuance/editor/NuanceQueueEditor.php b/src/applications/nuance/editor/NuanceQueueEditor.php index 69d18f6e4a..589ab5da5c 100644 --- a/src/applications/nuance/editor/NuanceQueueEditor.php +++ b/src/applications/nuance/editor/NuanceQueueEditor.php @@ -14,12 +14,88 @@ final class NuanceQueueEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = PhabricatorTransactions::TYPE_EDGE; - $types[] = PhabricatorTransactions::TYPE_COMMENT; + $types[] = NuanceQueueTransaction::TYPE_NAME; + $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; return $types; } + protected function getCustomTransactionOldValue( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case NuanceQueueTransaction::TYPE_NAME: + return $object->getName(); + } + + return parent::getCustomTransactionOldValue($object, $xaction); + } + + protected function getCustomTransactionNewValue( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case NuanceQueueTransaction::TYPE_NAME: + return $xaction->getNewValue(); + } + + return parent::getCustomTransactionNewValue($object, $xaction); + } + + protected function applyCustomInternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case NuanceQueueTransaction::TYPE_NAME: + $object->setName($xaction->getNewValue()); + break; + } + } + + protected function applyCustomExternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case NuanceQueueTransaction::TYPE_NAME: + return; + } + + return parent::applyCustomExternalTransaction($object, $xaction); + } + protected function validateTransaction( + PhabricatorLiskDAO $object, + $type, + array $xactions) { + + $errors = parent::validateTransaction($object, $type, $xactions); + + switch ($type) { + case NuanceQueueTransaction::TYPE_NAME: + $missing = $this->validateIsEmptyTextField( + $object->getName(), + $xactions); + + if ($missing) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Required'), + pht('A queue must have a name.'), + nonempty(last($xactions), null)); + + $error->setIsMissingFieldError(true); + $errors[] = $error; + } + break; + } + + return $errors; + } + + } diff --git a/src/applications/nuance/editor/NuanceRequestorEditor.php b/src/applications/nuance/editor/NuanceRequestorEditor.php index 25061d9a5f..4ca4c875ff 100644 --- a/src/applications/nuance/editor/NuanceRequestorEditor.php +++ b/src/applications/nuance/editor/NuanceRequestorEditor.php @@ -14,12 +14,62 @@ final class NuanceRequestorEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = PhabricatorTransactions::TYPE_EDGE; - $types[] = PhabricatorTransactions::TYPE_COMMENT; + $types[] = NuanceRequestorTransaction::TYPE_PROPERTY; + $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; return $types; } + protected function getCustomTransactionOldValue( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case NuanceRequestorTransaction::TYPE_PROPERTY: + $key = $xaction->getMetadataValue( + NuanceRequestorTransaction::PROPERTY_KEY); + return $object->getNuanceProperty($key); + } + + return parent::getCustomTransactionOldValue($object, $xaction); + } + + protected function getCustomTransactionNewValue( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case NuanceRequestorTransaction::TYPE_PROPERTY: + return $xaction->getNewValue(); + } + + return parent::getCustomTransactionNewValue($object, $xaction); + } + + protected function applyCustomInternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case NuanceRequestorTransaction::TYPE_PROPERTY: + $key = $xaction->getMetadataValue( + NuanceRequestorTransaction::PROPERTY_KEY); + $object->setNuanceProperty($key, $xaction->getNewValue()); + break; + } + } + + protected function applyCustomExternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case NuanceRequestorTransaction::TYPE_PROPERTY: + return; + } + + return parent::applyCustomExternalTransaction($object, $xaction); + } } diff --git a/src/applications/nuance/query/NuanceItemQuery.php b/src/applications/nuance/query/NuanceItemQuery.php index 4b87f38a77..cc3f79c915 100644 --- a/src/applications/nuance/query/NuanceItemQuery.php +++ b/src/applications/nuance/query/NuanceItemQuery.php @@ -5,7 +5,7 @@ final class NuanceItemQuery private $ids; private $phids; - private $sourceIDs; + private $sourcePHIDs; public function withIDs(array $ids) { $this->ids = $ids; @@ -17,54 +17,52 @@ final class NuanceItemQuery return $this; } - public function withSourceIDs($source_ids) { - $this->sourceIDs = $source_ids; + public function withSourcePHIDs($source_phids) { + $this->sourcePHIDs = $source_phids; return $this; } - protected function loadPage() { $table = new NuanceItem(); - $conn_r = $table->establishConnection('r'); + $conn = $table->establishConnection('r'); $data = queryfx_all( - $conn_r, - 'SELECT FROM %T %Q %Q %Q', + $conn, + '%Q FROM %T %Q %Q %Q', + $this->buildSelectClause($conn), $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); + $this->buildWhereClause($conn), + $this->buildOrderClause($conn), + $this->buildLimitClause($conn)); return $table->loadAllFromArray($data); } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); - $where[] = $this->buildPagingClause($conn_r); - - if ($this->sourceID) { + if ($this->sourcePHIDs !== null) { $where[] = qsprintf( - $conn_r, - 'sourceID IN (%Ld)', - $this->sourceIDs); + $conn, + 'sourcePHID IN (%Ls)', + $this->sourcePHIDs); } - if ($this->ids) { + if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } - if ($this->phids) { + if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } - return $this->formatWhereClause($where); + return $where; } } diff --git a/src/applications/nuance/query/NuanceQueueQuery.php b/src/applications/nuance/query/NuanceQueueQuery.php index 822a60c620..d50e393667 100644 --- a/src/applications/nuance/query/NuanceQueueQuery.php +++ b/src/applications/nuance/query/NuanceQueueQuery.php @@ -18,39 +18,38 @@ final class NuanceQueueQuery protected function loadPage() { $table = new NuanceQueue(); - $conn_r = $table->establishConnection('r'); + $conn = $table->establishConnection('r'); $data = queryfx_all( - $conn_r, - 'SELECT FROM %T %Q %Q %Q', + $conn, + '%Q FROM %T %Q %Q %Q', + $this->buildSelectClause($conn), $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); + $this->buildWhereClause($conn), + $this->buildOrderClause($conn), + $this->buildLimitClause($conn)); return $table->loadAllFromArray($data); } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); - $where[] = $this->buildPagingClause($conn_r); - - if ($this->ids) { + if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } - if ($this->phids) { + if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } - return $this->formatWhereClause($where); + return $where; } } diff --git a/src/applications/nuance/query/NuanceQueueSearchEngine.php b/src/applications/nuance/query/NuanceQueueSearchEngine.php new file mode 100644 index 0000000000..139068ed50 --- /dev/null +++ b/src/applications/nuance/query/NuanceQueueSearchEngine.php @@ -0,0 +1,75 @@ + pht('All Queues'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function renderResultList( + array $queues, + PhabricatorSavedQuery $query, + array $handles) { + assert_instances_of($queues, 'NuanceQueue'); + + $viewer = $this->requireViewer(); + + $list = new PHUIObjectItemListView(); + $list->setUser($viewer); + foreach ($queues as $queue) { + $item = id(new PHUIObjectItemView()) + ->setObjectName(pht('Queue %d', $queue->getID())) + ->setHeader($queue->getName()) + ->setHref($queue->getURI()); + $list->addItem($item); + } + + return $list; + } + +} diff --git a/src/applications/nuance/query/NuanceSourceQuery.php b/src/applications/nuance/query/NuanceSourceQuery.php index 77421656e7..ee4b964ee3 100644 --- a/src/applications/nuance/query/NuanceSourceQuery.php +++ b/src/applications/nuance/query/NuanceSourceQuery.php @@ -5,7 +5,6 @@ final class NuanceSourceQuery private $ids; private $phids; - private $creatorPHIDs; private $types; public function withIDs(array $ids) { @@ -18,66 +17,52 @@ final class NuanceSourceQuery return $this; } - public function withCreatorPHIDs(array $phids) { - $this->CreatorPHIDs = $phids; - return $this; - } - public function withTypes($types) { $this->types = $types; return $this; } - protected function loadPage() { $table = new NuanceSource(); - $conn_r = $table->establishConnection('r'); + $conn = $table->establishConnection('r'); $data = queryfx_all( - $conn_r, - 'SELECT * FROM %T %Q %Q %Q', + $conn, + '%Q FROM %T %Q %Q %Q', + $this->buildSelectClause($conn), $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); + $this->buildWhereClause($conn), + $this->buildOrderClause($conn), + $this->buildLimitClause($conn)); return $table->loadAllFromArray($data); } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); - $where[] = $this->buildPagingClause($conn_r); - - if ($this->creatorPHIDs) { + if ($this->types !== null) { $where[] = qsprintf( - $conn_r, - 'creatorPHID IN (%Ls)', - $this->creatorPHIDs); - } - - if ($this->types) { - $where[] = qsprintf( - $conn_r, - 'type IN (%Ld)', + $conn, + 'type IN (%Ls)', $this->types); } - if ($this->ids) { + if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } - if ($this->phids) { + if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } - return $this->formatWhereClause($where); + return $where; } } diff --git a/src/applications/nuance/query/NuanceSourceSearchEngine.php b/src/applications/nuance/query/NuanceSourceSearchEngine.php new file mode 100644 index 0000000000..8ea3231eea --- /dev/null +++ b/src/applications/nuance/query/NuanceSourceSearchEngine.php @@ -0,0 +1,78 @@ + pht('All Sources'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function renderResultList( + array $sources, + PhabricatorSavedQuery $query, + array $handles) { + assert_instances_of($sources, 'NuanceSource'); + + $viewer = $this->requireViewer(); + + $list = new PHUIObjectItemListView(); + $list->setUser($viewer); + foreach ($sources as $source) { + $item = id(new PHUIObjectItemView()) + ->setObjectName(pht('Source %d', $source->getID())) + ->setHeader($source->getName()) + ->setHref($source->getURI()); + + $item->addIcon('none', $source->getType()); + + $list->addItem($item); + } + + return $list; + } + +} diff --git a/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php b/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php index 1d49b17c55..2b596cc198 100644 --- a/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php +++ b/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php @@ -7,6 +7,10 @@ final class NuancePhabricatorFormSourceDefinition return pht('Phabricator Form'); } + public function getSourceDescription() { + return pht('Create a web form that submits into a Nuance queue.'); + } + public function getSourceTypeConstant() { return 'phabricator-form'; } @@ -38,4 +42,51 @@ final class NuancePhabricatorFormSourceDefinition public function renderListView() {} + + public function handleActionRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + + // TODO: As above, this would eventually be driven by custom logic. + + if ($request->isFormPost()) { + $properties = array( + 'complaint' => (string)$request->getStr('text'), + ); + + $content_source = PhabricatorContentSource::newFromRequest($request); + + $requestor = NuanceRequestor::newFromPhabricatorUser( + $viewer, + $content_source); + + $item = $this->newItemFromProperties( + $requestor, + $properties, + $content_source); + + $uri = $item->getURI(); + return id(new AphrontRedirectResponse())->setURI($uri); + } + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendRemarkupInstructions( + pht('IMPORTANT: This is a very rough prototype.')) + ->appendRemarkupInstructions( + pht('Got a complaint? Complain here! We love complaints.')) + ->appendChild( + id(new AphrontFormTextAreaControl()) + ->setName('complaint') + ->setLabel(pht('Complaint'))) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue(pht('Submit Complaint'))); + + $box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Complaint Form')) + ->appendChild($form); + + return $box; + } + } diff --git a/src/applications/nuance/source/NuanceSourceDefinition.php b/src/applications/nuance/source/NuanceSourceDefinition.php index b2092dfdff..b96b161e3a 100644 --- a/src/applications/nuance/source/NuanceSourceDefinition.php +++ b/src/applications/nuance/source/NuanceSourceDefinition.php @@ -1,5 +1,8 @@ actor = $actor; return $this; } + public function getActor() { return $this->actor; } + public function requireActor() { $actor = $this->getActor(); if (!$actor) { @@ -25,9 +30,11 @@ abstract class NuanceSourceDefinition extends Phobject { $this->sourceObject = $source; return $this; } + public function getSourceObject() { return $this->sourceObject; } + public function requireSourceObject() { $source = $this->getSourceObject(); if (!$source) { @@ -36,19 +43,6 @@ abstract class NuanceSourceDefinition extends Phobject { return $source; } - public static function getSelectOptions() { - $definitions = self::getAllDefinitions(); - - $options = array(); - foreach ($definitions as $definition) { - $key = $definition->getSourceTypeConstant(); - $name = $definition->getName(); - $options[$key] = $name; - } - - return $options; - } - /** * Gives a @{class:NuanceSourceDefinition} object for a given * @{class:NuanceSource}. Note you still need to @{method:setActor} @@ -67,6 +61,8 @@ abstract class NuanceSourceDefinition extends Phobject { static $definitions; if ($definitions === null) { + $definitions = array(); + $objects = id(new PhutilSymbolLoader()) ->setAncestorClass(__CLASS__) ->loadObjects(); @@ -82,9 +78,10 @@ abstract class NuanceSourceDefinition extends Phobject { $conflict, $name)); } + $definitions[$key] = $definition; } - $definitions = $objects; } + return $definitions; } @@ -93,6 +90,12 @@ abstract class NuanceSourceDefinition extends Phobject { */ abstract public function getName(); + + /** + * Human readable description of this source, a sentence or two long. + */ + abstract public function getSourceDescription(); + /** * This should be a any VARCHAR(32). * @@ -193,13 +196,7 @@ abstract class NuanceSourceDefinition extends Phobject { ->setLabel(pht('Name')) ->setName('name') ->setError($e_name) - ->setValue($source->getName())) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Type')) - ->setName('type') - ->setOptions(self::getSelectOptions()) - ->setValue($source->getType())); + ->setValue($source->getName())); $form = $this->augmentEditForm($form, $ex); @@ -260,4 +257,54 @@ abstract class NuanceSourceDefinition extends Phobject { abstract public function renderView(); abstract public function renderListView(); + + + protected function newItemFromProperties( + NuanceRequestor $requestor, + array $properties, + PhabricatorContentSource $content_source) { + + // TODO: Should we have a tighter actor/viewer model? Requestors will + // often have no real user associated with them... + $actor = PhabricatorUser::getOmnipotentUser(); + + $source = $this->requireSourceObject(); + + $item = NuanceItem::initializeNewItem(); + + $xactions = array(); + + $xactions[] = id(new NuanceItemTransaction()) + ->setTransactionType(NuanceItemTransaction::TYPE_SOURCE) + ->setNewValue($source->getPHID()); + + $xactions[] = id(new NuanceItemTransaction()) + ->setTransactionType(NuanceItemTransaction::TYPE_REQUESTOR) + ->setNewValue($requestor->getPHID()); + + foreach ($properties as $key => $property) { + $xactions[] = id(new NuanceItemTransaction()) + ->setTransactionType(NuanceItemTransaction::TYPE_PROPERTY) + ->setMetadataValue(NuanceItemTransaction::PROPERTY_KEY, $key) + ->setNewValue($property); + } + + $editor = id(new NuanceItemEditor()) + ->setActor($actor) + ->setActingAsPHID($requestor->getActingAsPHID()) + ->setContentSource($content_source); + + $editor->applyTransactions($item, $xactions); + + return $item; + } + + +/* -( Handling Action Requests )------------------------------------------- */ + + + public function handleActionRequest(AphrontRequest $request) { + return new Aphront404Response(); + } + } diff --git a/src/applications/nuance/storage/NuanceItem.php b/src/applications/nuance/storage/NuanceItem.php index 50d7ee72ef..3e9f7617f9 100644 --- a/src/applications/nuance/storage/NuanceItem.php +++ b/src/applications/nuance/storage/NuanceItem.php @@ -13,11 +13,11 @@ final class NuanceItem protected $requestorPHID; protected $sourcePHID; protected $sourceLabel; - protected $data; + protected $data = array(); protected $mailKey; protected $dateNuanced; - public static function initializeNewItem(PhabricatorUser $user) { + public static function initializeNewItem() { return id(new NuanceItem()) ->setDateNuanced(time()) ->setStatus(self::STATUS_OPEN); @@ -94,6 +94,15 @@ final class NuanceItem $this->source = $source; } + public function getNuanceProperty($key, $default = null) { + return idx($this->data, $key, $default); + } + + public function setNuanceProperty($key, $value) { + $this->data[$key] = $value; + return $this; + } + public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, diff --git a/src/applications/nuance/storage/NuanceItemTransaction.php b/src/applications/nuance/storage/NuanceItemTransaction.php index 08eef5c681..e6fac7cde7 100644 --- a/src/applications/nuance/storage/NuanceItemTransaction.php +++ b/src/applications/nuance/storage/NuanceItemTransaction.php @@ -3,9 +3,12 @@ final class NuanceItemTransaction extends NuanceTransaction { - const TYPE_OWNER = 'item-owner'; - const TYPE_REQUESTOR = 'item-requestor'; - const TYPE_SOURCE = 'item-source'; + const PROPERTY_KEY = 'property.key'; + + const TYPE_OWNER = 'nuance.item.owner'; + const TYPE_REQUESTOR = 'nuance.item.requestor'; + const TYPE_SOURCE = 'nuance.item.source'; + const TYPE_PROPERTY = 'nuance.item.property'; public function getApplicationTransactionType() { return NuanceItemPHIDType::TYPECONST; diff --git a/src/applications/nuance/storage/NuanceQueue.php b/src/applications/nuance/storage/NuanceQueue.php index e9499e0f3d..093b437e24 100644 --- a/src/applications/nuance/storage/NuanceQueue.php +++ b/src/applications/nuance/storage/NuanceQueue.php @@ -2,7 +2,9 @@ final class NuanceQueue extends NuanceDAO - implements PhabricatorPolicyInterface { + implements + PhabricatorPolicyInterface, + PhabricatorApplicationTransactionInterface { protected $name; protected $mailKey; @@ -24,6 +26,10 @@ final class NuanceQueue NuanceQueuePHIDType::TYPECONST); } + public static function initializeNewQueue() { + return new NuanceQueue(); + } + public function save() { if (!$this->getMailKey()) { $this->setMailKey(Filesystem::readRandomCharacters(20)); @@ -35,6 +41,10 @@ final class NuanceQueue return '/nuance/queue/view/'.$this->getID().'/'; } + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, @@ -59,4 +69,26 @@ final class NuanceQueue return null; } + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new NuanceQueueEditor(); + } + + public function getApplicationTransactionObject() { + return $this; + } + + public function getApplicationTransactionTemplate() { + return new NuanceQueueTransaction(); + } + + public function willRenderTimeline( + PhabricatorApplicationTransactionView $timeline, + AphrontRequest $request) { + return $timeline; + } + } diff --git a/src/applications/nuance/storage/NuanceQueueTransaction.php b/src/applications/nuance/storage/NuanceQueueTransaction.php index 0e8ffcfdee..46ace23cd1 100644 --- a/src/applications/nuance/storage/NuanceQueueTransaction.php +++ b/src/applications/nuance/storage/NuanceQueueTransaction.php @@ -2,6 +2,8 @@ final class NuanceQueueTransaction extends NuanceTransaction { + const TYPE_NAME = 'nuance.queue.name'; + public function getApplicationTransactionType() { return NuanceQueuePHIDType::TYPECONST; } diff --git a/src/applications/nuance/storage/NuanceRequestor.php b/src/applications/nuance/storage/NuanceRequestor.php index 3305da3988..d4341d0b24 100644 --- a/src/applications/nuance/storage/NuanceRequestor.php +++ b/src/applications/nuance/storage/NuanceRequestor.php @@ -1,9 +1,12 @@ getMailKey()) { - $this->setMailKey(Filesystem::readRandomCharacters(20)); - } - return parent::save(); + public static function initializeNewRequestor() { + return new NuanceRequestor(); } public function getURI() { @@ -34,4 +34,104 @@ final class NuanceRequestor return idx($this->getData(), 'phabricatorUserPHID'); } + public function getActingAsPHID() { + $user_phid = $this->getPhabricatorUserPHID(); + if ($user_phid) { + return $user_phid; + } + + return id(new PhabricatorNuanceApplication())->getPHID(); + } + + public static function newFromPhabricatorUser( + PhabricatorUser $viewer, + PhabricatorContentSource $content_source) { + + // TODO: This is real sketchy and creates a new requestor every time. It + // shouldn't do that. + + $requestor = self::initializeNewRequestor(); + + $xactions = array(); + + $properties = array( + 'phabricatorUserPHID' => $viewer->getPHID(), + ); + + foreach ($properties as $key => $value) { + $xactions[] = id(new NuanceRequestorTransaction()) + ->setTransactionType(NuanceRequestorTransaction::TYPE_PROPERTY) + ->setMetadataValue(NuanceRequestorTransaction::PROPERTY_KEY, $key) + ->setNewValue($value); + } + + $editor = id(new NuanceRequestorEditor()) + ->setActor($viewer) + ->setContentSource($content_source); + + $editor->applyTransactions($requestor, $xactions); + + return $requestor; + } + + public function getNuanceProperty($key, $default = null) { + return idx($this->data, $key, $default); + } + + public function setNuanceProperty($key, $value) { + $this->data[$key] = $value; + return $this; + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function getPolicy($capability) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return PhabricatorPolicies::POLICY_USER; + case PhabricatorPolicyCapability::CAN_EDIT: + return PhabricatorPolicies::POLICY_USER; + } + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new NuanceRequestorEditor(); + } + + public function getApplicationTransactionObject() { + return $this; + } + + public function getApplicationTransactionTemplate() { + return new NuanceRequestorTransaction(); + } + + public function willRenderTimeline( + PhabricatorApplicationTransactionView $timeline, + AphrontRequest $request) { + + return $timeline; + } + } diff --git a/src/applications/nuance/storage/NuanceRequestorTransaction.php b/src/applications/nuance/storage/NuanceRequestorTransaction.php index d1035cd766..ae2e0a119f 100644 --- a/src/applications/nuance/storage/NuanceRequestorTransaction.php +++ b/src/applications/nuance/storage/NuanceRequestorTransaction.php @@ -3,6 +3,10 @@ final class NuanceRequestorTransaction extends NuanceTransaction { + const PROPERTY_KEY = 'property.key'; + + const TYPE_PROPERTY = 'nuance.requestor.property'; + public function getApplicationTransactionType() { return NuanceRequestorPHIDType::TYPECONST; } diff --git a/src/applications/nuance/storage/NuanceSource.php b/src/applications/nuance/storage/NuanceSource.php index 423f70fd76..01f0d6e620 100644 --- a/src/applications/nuance/storage/NuanceSource.php +++ b/src/applications/nuance/storage/NuanceSource.php @@ -57,13 +57,9 @@ final class NuanceSource extends NuanceDAO $edit_policy = $app->getPolicy( NuanceSourceDefaultEditCapability::CAPABILITY); - $definitions = NuanceSourceDefinition::getAllDefinitions(); - $lucky_definition = head($definitions); - return id(new NuanceSource()) ->setViewPolicy($view_policy) - ->setEditPolicy($edit_policy) - ->setType($lucky_definition->getSourceTypeConstant()); + ->setEditPolicy($edit_policy); } @@ -90,6 +86,9 @@ final class NuanceSource extends NuanceDAO } +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, diff --git a/src/applications/oauthserver/PhabricatorOAuthServerScope.php b/src/applications/oauthserver/PhabricatorOAuthServerScope.php index dcba6b6234..146293dfde 100644 --- a/src/applications/oauthserver/PhabricatorOAuthServerScope.php +++ b/src/applications/oauthserver/PhabricatorOAuthServerScope.php @@ -11,18 +11,18 @@ final class PhabricatorOAuthServerScope { * used to simplify code for data that is not currently accessible * via OAuth. */ - static public function getScopesDict() { + public static function getScopesDict() { return array( self::SCOPE_OFFLINE_ACCESS => 1, self::SCOPE_WHOAMI => 1, ); } - static public function getDefaultScope() { + public static function getDefaultScope() { return self::SCOPE_WHOAMI; } - static public function getCheckboxControl( + public static function getCheckboxControl( array $current_scopes) { $have_options = false; @@ -56,7 +56,7 @@ final class PhabricatorOAuthServerScope { return null; } - static private function getCheckboxLabel($scope) { + private static function getCheckboxLabel($scope) { $label = null; switch ($scope) { case self::SCOPE_OFFLINE_ACCESS: @@ -70,7 +70,7 @@ final class PhabricatorOAuthServerScope { return $label; } - static public function getScopesFromRequest(AphrontRequest $request) { + public static function getScopesFromRequest(AphrontRequest $request) { $scopes = self::getScopesDict(); $requested_scopes = array(); foreach ($scopes as $scope => $bit) { @@ -86,7 +86,7 @@ final class PhabricatorOAuthServerScope { * A scopes list is considered valid if each scope is a known scope * and each scope is seen only once. Otherwise, the list is invalid. */ - static public function validateScopesList($scope_list) { + public static function validateScopesList($scope_list) { $scopes = explode(' ', $scope_list); $known_scopes = self::getScopesDict(); $seen_scopes = array(); @@ -107,7 +107,7 @@ final class PhabricatorOAuthServerScope { * A scopes dictionary is considered valid if each key is a known scope. * Otherwise, the dictionary is invalid. */ - static public function validateScopesDict($scope_dict) { + public static function validateScopesDict($scope_dict) { $known_scopes = self::getScopesDict(); $unknown_scopes = array_diff_key($scope_dict, $known_scopes); @@ -119,7 +119,7 @@ final class PhabricatorOAuthServerScope { * should be validated by @{method:validateScopesList} before * transformation. */ - static public function scopesListToDict($scope_list) { + public static function scopesListToDict($scope_list) { $scopes = explode(' ', $scope_list); return array_fill_keys($scopes, 1); } diff --git a/src/applications/owners/mail/OwnersPackageReplyHandler.php b/src/applications/owners/mail/OwnersPackageReplyHandler.php index 0e28ff12b1..84c1a0d049 100644 --- a/src/applications/owners/mail/OwnersPackageReplyHandler.php +++ b/src/applications/owners/mail/OwnersPackageReplyHandler.php @@ -11,7 +11,7 @@ final class OwnersPackageReplyHandler extends PhabricatorMailReplyHandler { } public function getPrivateReplyHandlerEmailAddress( - PhabricatorObjectHandle $handle) { + PhabricatorUser $user) { return null; } diff --git a/src/applications/passphrase/search/PassphraseSearchIndexer.php b/src/applications/passphrase/search/PassphraseSearchIndexer.php index 44ec9d207a..2f8da05a77 100644 --- a/src/applications/passphrase/search/PassphraseSearchIndexer.php +++ b/src/applications/passphrase/search/PassphraseSearchIndexer.php @@ -17,7 +17,7 @@ final class PassphraseSearchIndexer extends PhabricatorSearchDocumentIndexer { $doc->setDocumentModified($credential->getDateModified()); $doc->addField( - PhabricatorSearchField::FIELD_BODY, + PhabricatorSearchDocumentFieldType::FIELD_BODY, $credential->getDescription()); $doc->addRelationship( diff --git a/src/applications/paste/controller/PhabricatorPasteEditController.php b/src/applications/paste/controller/PhabricatorPasteEditController.php index 32cc465737..7aa28673da 100644 --- a/src/applications/paste/controller/PhabricatorPasteEditController.php +++ b/src/applications/paste/controller/PhabricatorPasteEditController.php @@ -57,13 +57,12 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController { } } - $text = null; - $e_text = true; - $errors = array(); + $v_space = $paste->getSpacePHID(); if ($is_create && $parent) { $v_title = pht('Fork of %s', $parent->getFullName()); $v_language = $parent->getLanguage(); $v_text = $parent->getRawContent(); + $v_space = $parent->getSpacePHID(); } else { $v_title = $paste->getTitle(); $v_language = $paste->getLanguage(); @@ -81,68 +80,64 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController { $v_projects = array_reverse($v_projects); } + $validation_exception = null; if ($request->isFormPost()) { $xactions = array(); $v_text = $request->getStr('text'); - if (!strlen($v_text)) { - $e_text = pht('Required'); - $errors[] = pht('The paste may not be blank.'); - } else { - $e_text = null; - } - $v_title = $request->getStr('title'); $v_language = $request->getStr('language'); $v_view_policy = $request->getStr('can_view'); $v_edit_policy = $request->getStr('can_edit'); $v_projects = $request->getArr('projects'); + $v_space = $request->getStr('spacePHID'); // NOTE: The author is the only editor and can always view the paste, // so it's impossible for them to choose an invalid policy. - if (!$errors) { - if ($is_create || ($v_text !== $paste->getRawContent())) { - $file = PhabricatorPasteEditor::initializeFileForPaste( - $user, - $v_title, - $v_text); - - $xactions[] = id(new PhabricatorPasteTransaction()) - ->setTransactionType(PhabricatorPasteTransaction::TYPE_CONTENT) - ->setNewValue($file->getPHID()); - } + if ($is_create || ($v_text !== $paste->getRawContent())) { + $file = PhabricatorPasteEditor::initializeFileForPaste( + $user, + $v_title, + $v_text); $xactions[] = id(new PhabricatorPasteTransaction()) - ->setTransactionType(PhabricatorPasteTransaction::TYPE_TITLE) - ->setNewValue($v_title); - $xactions[] = id(new PhabricatorPasteTransaction()) - ->setTransactionType(PhabricatorPasteTransaction::TYPE_LANGUAGE) - ->setNewValue($v_language); - $xactions[] = id(new PhabricatorPasteTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) - ->setNewValue($v_view_policy); - $xactions[] = id(new PhabricatorPasteTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) - ->setNewValue($v_edit_policy); + ->setTransactionType(PhabricatorPasteTransaction::TYPE_CONTENT) + ->setNewValue($file->getPHID()); + } - $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; - $xactions[] = id(new PhabricatorPasteTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue('edge:type', $proj_edge_type) - ->setNewValue(array('=' => array_fuse($v_projects))); + $xactions[] = id(new PhabricatorPasteTransaction()) + ->setTransactionType(PhabricatorPasteTransaction::TYPE_TITLE) + ->setNewValue($v_title); + $xactions[] = id(new PhabricatorPasteTransaction()) + ->setTransactionType(PhabricatorPasteTransaction::TYPE_LANGUAGE) + ->setNewValue($v_language); + $xactions[] = id(new PhabricatorPasteTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) + ->setNewValue($v_view_policy); + $xactions[] = id(new PhabricatorPasteTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) + ->setNewValue($v_edit_policy); + $xactions[] = id(new PhabricatorPasteTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_SPACE) + ->setNewValue($v_space); - $editor = id(new PhabricatorPasteEditor()) - ->setActor($user) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true); + $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; + $xactions[] = id(new PhabricatorPasteTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue('edge:type', $proj_edge_type) + ->setNewValue(array('=' => array_fuse($v_projects))); + + $editor = id(new PhabricatorPasteEditor()) + ->setActor($user) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true); + + try { $xactions = $editor->applyTransactions($paste, $xactions); return id(new AphrontRedirectResponse())->setURI($paste->getURI()); - } else { - // make sure we update policy so its correctly populated to what - // the user chose - $paste->setViewPolicy($v_view_policy); - $paste->setEditPolicy($v_edit_policy); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $validation_exception = $ex; } } @@ -172,12 +167,19 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController { ->setObject($paste) ->execute(); + $form->appendControl( + id(new PhabricatorSpacesControl()) + ->setObject($paste) + ->setValue($v_space) + ->setName('spacePHID')); + $form->appendChild( id(new AphrontFormPolicyControl()) ->setUser($user) ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) ->setPolicyObject($paste) ->setPolicies($policies) + ->setValue($v_view_policy) ->setName('can_view')); $form->appendChild( @@ -186,6 +188,7 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController { ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) ->setPolicyObject($paste) ->setPolicies($policies) + ->setValue($v_edit_policy) ->setName('can_edit')); $form->appendControl( @@ -199,7 +202,6 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController { ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel(pht('Text')) - ->setError($e_text) ->setValue($v_text) ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) ->setCustomClass('PhabricatorMonospaced') @@ -222,9 +224,12 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController { $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) - ->setFormErrors($errors) ->setForm($form); + if ($validation_exception) { + $form_box->setValidationException($validation_exception); + } + $crumbs = $this->buildApplicationCrumbs($this->buildSideNavView()); if (!$is_create) { $crumbs->addTextCrumb('P'.$paste->getID(), '/P'.$paste->getID()); diff --git a/src/applications/paste/editor/PhabricatorPasteEditor.php b/src/applications/paste/editor/PhabricatorPasteEditor.php index 9c966bf724..732db28bd7 100644 --- a/src/applications/paste/editor/PhabricatorPasteEditor.php +++ b/src/applications/paste/editor/PhabricatorPasteEditor.php @@ -3,8 +3,6 @@ final class PhabricatorPasteEditor extends PhabricatorApplicationTransactionEditor { - private $pasteFile; - public function getEditorApplicationClass() { return 'PhabricatorPasteApplication'; } @@ -134,7 +132,7 @@ final class PhabricatorPasteEditor protected function getMailTo(PhabricatorLiskDAO $object) { return array( $object->getAuthorPHID(), - $this->requireActor()->getPHID(), + $this->getActingAsPHID(), ); } diff --git a/src/applications/paste/query/PhabricatorPasteQuery.php b/src/applications/paste/query/PhabricatorPasteQuery.php index d5a5daf9e3..5c31b65931 100644 --- a/src/applications/paste/query/PhabricatorPasteQuery.php +++ b/src/applications/paste/query/PhabricatorPasteQuery.php @@ -67,21 +67,12 @@ final class PhabricatorPasteQuery return $this; } + public function newResultObject() { + return new PhabricatorPaste(); + } + protected function loadPage() { - $table = new PhabricatorPaste(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT paste.* FROM %T paste %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - $pastes = $table->loadAllFromArray($data); - - return $pastes; + return $this->loadStandardPage(new PhabricatorPaste()); } protected function didFilterPage(array $pastes) { @@ -96,61 +87,59 @@ final class PhabricatorPasteQuery return $pastes; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); - $where[] = $this->buildPagingClause($conn_r); - - if ($this->ids) { + if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } - if ($this->phids) { + if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } - if ($this->authorPHIDs) { + if ($this->authorPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'authorPHID IN (%Ls)', $this->authorPHIDs); } - if ($this->parentPHIDs) { + if ($this->parentPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'parentPHID IN (%Ls)', $this->parentPHIDs); } - if ($this->languages) { + if ($this->languages !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'language IN (%Ls)', $this->languages); } - if ($this->dateCreatedAfter) { + if ($this->dateCreatedAfter !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'dateCreated >= %d', $this->dateCreatedAfter); } - if ($this->dateCreatedBefore) { + if ($this->dateCreatedBefore !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'dateCreated <= %d', $this->dateCreatedBefore); } - return $this->formatWhereClause($where); + return $where; } private function getContentCacheKey(PhabricatorPaste $paste) { diff --git a/src/applications/paste/query/PhabricatorPasteSearchEngine.php b/src/applications/paste/query/PhabricatorPasteSearchEngine.php index 45dad0799b..b6dcfce104 100644 --- a/src/applications/paste/query/PhabricatorPasteSearchEngine.php +++ b/src/applications/paste/query/PhabricatorPasteSearchEngine.php @@ -11,87 +11,57 @@ final class PhabricatorPasteSearchEngine return 'PhabricatorPasteApplication'; } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - $saved->setParameter( - 'authorPHIDs', - $this->readUsersFromRequest($request, 'authors')); - - $languages = $request->getStrList('languages'); - if ($request->getBool('noLanguage')) { - $languages[] = null; - } - $saved->setParameter('languages', $languages); - - $saved->setParameter('createdStart', $request->getStr('createdStart')); - $saved->setParameter('createdEnd', $request->getStr('createdEnd')); - - return $saved; + public function newQuery() { + return id(new PhabricatorPasteQuery()) + ->needContent(true); } - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new PhabricatorPasteQuery()) - ->needContent(true) - ->withAuthorPHIDs($saved->getParameter('authorPHIDs', array())) - ->withLanguages($saved->getParameter('languages', array())); + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); - $start = $this->parseDateTime($saved->getParameter('createdStart')); - $end = $this->parseDateTime($saved->getParameter('createdEnd')); - - if ($start) { - $query->withDateCreatedAfter($start); + if ($map['authorPHIDs']) { + $query->withAuthorPHIDs($map['authorPHIDs']); } - if ($end) { - $query->withDateCreatedBefore($end); + if ($map['languages']) { + $query->withLanguages($map['languages']); + } + + if ($map['createdStart']) { + $query->withDateCreatedAfter($map['createdStart']); + } + + if ($map['createdEnd']) { + $query->withDateCreatedBefore($map['createdEnd']); } return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved_query) { - $author_phids = $saved_query->getParameter('authorPHIDs', array()); + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorSearchUsersField()) + ->setAliases(array('authors')) + ->setKey('authorPHIDs') + ->setLabel(pht('Authors')), + id(new PhabricatorSearchStringListField()) + ->setKey('languages') + ->setLabel(pht('Languages')), + id(new PhabricatorSearchDateField()) + ->setKey('createdStart') + ->setLabel(pht('Created After')), + id(new PhabricatorSearchDateField()) + ->setKey('createdEnd') + ->setLabel(pht('Created Before')), + ); + } - $languages = $saved_query->getParameter('languages', array()); - $no_language = false; - foreach ($languages as $key => $language) { - if ($language === null) { - $no_language = true; - unset($languages[$key]); - continue; - } - } - - $form - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorPeopleDatasource()) - ->setName('authors') - ->setLabel(pht('Authors')) - ->setValue($author_phids)) - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('languages') - ->setLabel(pht('Languages')) - ->setValue(implode(', ', $languages))) - ->appendChild( - id(new AphrontFormCheckboxControl()) - ->addCheckbox( - 'noLanguage', - 1, - pht('Find Pastes with no specified language.'), - $no_language)); - - $this->buildDateRange( - $form, - $saved_query, + protected function getDefaultFieldOrder() { + return array( + '...', 'createdStart', - pht('Created After'), 'createdEnd', - pht('Created Before')); - + ); } protected function getURI($path) { diff --git a/src/applications/paste/storage/PhabricatorPaste.php b/src/applications/paste/storage/PhabricatorPaste.php index 3a0ae754e7..da129b8cf4 100644 --- a/src/applications/paste/storage/PhabricatorPaste.php +++ b/src/applications/paste/storage/PhabricatorPaste.php @@ -9,7 +9,8 @@ final class PhabricatorPaste extends PhabricatorPasteDAO PhabricatorPolicyInterface, PhabricatorProjectInterface, PhabricatorDestructibleInterface, - PhabricatorApplicationTransactionInterface { + PhabricatorApplicationTransactionInterface, + PhabricatorSpacesInterface { protected $title; protected $authorPHID; @@ -19,6 +20,7 @@ final class PhabricatorPaste extends PhabricatorPasteDAO protected $viewPolicy; protected $editPolicy; protected $mailKey; + protected $spacePHID; private $content = self::ATTACHABLE; private $rawContent = self::ATTACHABLE; @@ -40,7 +42,11 @@ final class PhabricatorPaste extends PhabricatorPasteDAO } public function getURI() { - return '/P'.$this->getID(); + return '/'.$this->getMonogram(); + } + + public function getMonogram() { + return 'P'.$this->getID(); } protected function getConfiguration() { @@ -206,4 +212,12 @@ final class PhabricatorPaste extends PhabricatorPasteDAO return $timeline; } + +/* -( PhabricatorSpacesInterface )----------------------------------------- */ + + + public function getSpacePHID() { + return $this->spacePHID; + } + } diff --git a/src/applications/people/conduit/UserConduitAPIMethod.php b/src/applications/people/conduit/UserConduitAPIMethod.php index 187e28b9b3..427e7f71e5 100644 --- a/src/applications/people/conduit/UserConduitAPIMethod.php +++ b/src/applications/people/conduit/UserConduitAPIMethod.php @@ -18,6 +18,9 @@ abstract class UserConduitAPIMethod extends ConduitAPIMethod { if ($user->getIsSystemAgent()) { $roles[] = 'agent'; } + if ($user->getIsMailingList()) { + $roles[] = 'list'; + } if ($user->getIsAdmin()) { $roles[] = 'admin'; } diff --git a/src/applications/people/controller/PhabricatorPeopleCreateController.php b/src/applications/people/controller/PhabricatorPeopleCreateController.php index 7bceddc1a2..15a34d0063 100644 --- a/src/applications/people/controller/PhabricatorPeopleCreateController.php +++ b/src/applications/people/controller/PhabricatorPeopleCreateController.php @@ -4,8 +4,6 @@ final class PhabricatorPeopleCreateController extends PhabricatorPeopleController { public function handleRequest(AphrontRequest $request) { - $this->requireApplicationCapability( - PeopleCreateUsersCapability::CAPABILITY); $admin = $request->getUser(); id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession( @@ -17,7 +15,7 @@ final class PhabricatorPeopleCreateController if ($request->isFormPost()) { $v_type = $request->getStr('type'); - if ($v_type == 'standard' || $v_type == 'bot') { + if ($v_type == 'standard' || $v_type == 'bot' || $v_type == 'list') { return id(new AphrontRedirectResponse())->setURI( $this->getApplicationURI('new/'.$v_type.'/')); } @@ -41,6 +39,41 @@ final class PhabricatorPeopleCreateController $bot_admin = pht( 'Administrators have greater access to edit these accounts.'); + $types = array(); + + $can_create = $this->hasApplicationCapability( + PeopleCreateUsersCapability::CAPABILITY); + if ($can_create) { + $types[] = array( + 'type' => 'standard', + 'name' => pht('Create Standard User'), + 'help' => pht('Create a standard user account.'), + ); + } + + $types[] = array( + 'type' => 'bot', + 'name' => pht('Create Bot User'), + 'help' => pht('Create a new user for use with automated scripts.'), + ); + + $types[] = array( + 'type' => 'list', + 'name' => pht('Create Mailing List User'), + 'help' => pht( + 'Create a mailing list user to represent an existing, external '. + 'mailing list like a Google Group or a Mailman list.'), + ); + + $buttons = id(new AphrontFormRadioButtonControl()) + ->setLabel(pht('Account Type')) + ->setName('type') + ->setValue($v_type); + + foreach ($types as $type) { + $buttons->addButton($type['type'], $type['name'], $type['help']); + } + $form = id(new AphrontFormView()) ->setUser($admin) ->appendRemarkupInstructions( @@ -49,19 +82,7 @@ final class PhabricatorPeopleCreateController 'explanation of user account types, see [[ %s | User Guide: '. 'Account Roles ]].', PhabricatorEnv::getDoclink('User Guide: Account Roles'))) - ->appendChild( - id(new AphrontFormRadioButtonControl()) - ->setLabel(pht('Account Type')) - ->setName('type') - ->setValue($v_type) - ->addButton( - 'standard', - pht('Create Standard User'), - hsprintf('%s

%s', $standard_caption, $standard_admin)) - ->addButton( - 'bot', - pht('Create Bot/Script User'), - hsprintf('%s

%s', $bot_caption, $bot_admin))) + ->appendChild($buttons) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($this->getApplicationURI()) diff --git a/src/applications/people/controller/PhabricatorPeopleListController.php b/src/applications/people/controller/PhabricatorPeopleListController.php index aa95480dbe..d4ba9aa217 100644 --- a/src/applications/people/controller/PhabricatorPeopleListController.php +++ b/src/applications/people/controller/PhabricatorPeopleListController.php @@ -33,20 +33,12 @@ final class PhabricatorPeopleListController $crumbs = parent::buildApplicationCrumbs(); $viewer = $this->getRequest()->getUser(); - $can_create = $this->hasApplicationCapability( - PeopleCreateUsersCapability::CAPABILITY); - if ($can_create) { + if ($viewer->getIsAdmin()) { $crumbs->addAction( id(new PHUIListItemView()) ->setName(pht('Create New User')) ->setHref($this->getApplicationURI('create/')) ->setIcon('fa-plus-square')); - } else if ($viewer->getIsAdmin()) { - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('Create New Bot')) - ->setHref($this->getApplicationURI('new/bot/')) - ->setIcon('fa-plus-square')); } return $crumbs; diff --git a/src/applications/people/controller/PhabricatorPeopleNewController.php b/src/applications/people/controller/PhabricatorPeopleNewController.php index 93f9c42faa..2590129c09 100644 --- a/src/applications/people/controller/PhabricatorPeopleNewController.php +++ b/src/applications/people/controller/PhabricatorPeopleNewController.php @@ -7,15 +7,19 @@ final class PhabricatorPeopleNewController $type = $request->getURIData('type'); $admin = $request->getUser(); + $is_bot = false; + $is_list = false; switch ($type) { case 'standard': $this->requireApplicationCapability( PeopleCreateUsersCapability::CAPABILITY); - $is_bot = false; break; case 'bot': $is_bot = true; break; + case 'list': + $is_list = true; + break; default: return new Aphront404Response(); } @@ -77,8 +81,8 @@ final class PhabricatorPeopleNewController // Automatically approve the user, since an admin is creating them. $user->setIsApproved(1); - // If the user is a bot, approve their email too. - if ($is_bot) { + // If the user is a bot or list, approve their email too. + if ($is_bot || $is_list) { $email->setIsVerified(1); } @@ -92,7 +96,13 @@ final class PhabricatorPeopleNewController ->makeSystemAgentUser($user, true); } - if ($welcome_checked && !$is_bot) { + if ($is_list) { + id(new PhabricatorUserEditor()) + ->setActor($admin) + ->makeMailingListUser($user, true); + } + + if ($welcome_checked && !$is_bot && !$is_list) { $user->sendWelcomeEmail($admin); } @@ -123,7 +133,10 @@ final class PhabricatorPeopleNewController if ($is_bot) { $form->appendRemarkupInstructions( - pht('You are creating a new **bot/script** user account.')); + pht('You are creating a new **bot** user account.')); + } else if ($is_list) { + $form->appendRemarkupInstructions( + pht('You are creating a new **mailing list** user account.')); } else { $form->appendRemarkupInstructions( pht('You are creating a new **standard** user account.')); @@ -150,7 +163,7 @@ final class PhabricatorPeopleNewController ->setCaption(PhabricatorUserEmail::describeAllowedAddresses()) ->setError($e_email)); - if (!$is_bot) { + if (!$is_bot && !$is_list) { $form->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( @@ -171,7 +184,7 @@ final class PhabricatorPeopleNewController ->appendChild(id(new AphrontFormDividerControl())) ->appendRemarkupInstructions( pht( - '**Why do bot/script accounts need an email address?**'. + '**Why do bot accounts need an email address?**'. "\n\n". 'Although bots do not normally receive email from Phabricator, '. 'they can interact with other systems which require an email '. diff --git a/src/applications/people/customfield/PhabricatorUserRolesField.php b/src/applications/people/customfield/PhabricatorUserRolesField.php index f4c667fd56..f68ea6c3c5 100644 --- a/src/applications/people/customfield/PhabricatorUserRolesField.php +++ b/src/applications/people/customfield/PhabricatorUserRolesField.php @@ -37,6 +37,9 @@ final class PhabricatorUserRolesField if ($user->getIsSystemAgent()) { $roles[] = pht('Bot'); } + if ($user->getIsMailingList()) { + $roles[] = pht('Mailing List'); + } if ($roles) { return implode(', ', $roles); diff --git a/src/applications/people/editor/PhabricatorUserEditor.php b/src/applications/people/editor/PhabricatorUserEditor.php index cc34696f2d..d7885ef101 100644 --- a/src/applications/people/editor/PhabricatorUserEditor.php +++ b/src/applications/people/editor/PhabricatorUserEditor.php @@ -278,6 +278,43 @@ final class PhabricatorUserEditor extends PhabricatorEditor { return $this; } + /** + * @task role + */ + public function makeMailingListUser(PhabricatorUser $user, $mailing_list) { + $actor = $this->requireActor(); + + if (!$user->getID()) { + throw new Exception(pht('User has not been created yet!')); + } + + $user->openTransaction(); + $user->beginWriteLocking(); + + $user->reload(); + if ($user->getIsMailingList() == $mailing_list) { + $user->endWriteLocking(); + $user->killTransaction(); + return $this; + } + + $log = PhabricatorUserLog::initializeNewLog( + $actor, + $user->getPHID(), + PhabricatorUserLog::ACTION_MAILING_LIST); + $log->setOldValue($user->getIsMailingList()); + $log->setNewValue($mailing_list); + + $user->setIsMailingList((int)$mailing_list); + $user->save(); + + $log->save(); + + $user->endWriteLocking(); + $user->saveTransaction(); + + return $this; + } /** * @task role diff --git a/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php b/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php index ba271e6d92..f09c75087e 100644 --- a/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php +++ b/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php @@ -44,6 +44,10 @@ final class PhabricatorPeopleUserPHIDType extends PhabricatorPHIDType { $handle->setFullName($user->getFullName()); $handle->setImageURI($user->getProfileImageURI()); + if ($user->getIsMailingList()) { + $handle->setIcon('fa-envelope-o'); + } + $availability = null; if (!$user->isUserActivated()) { $availability = PhabricatorObjectHandle::AVAILABILITY_DISABLED; diff --git a/src/applications/people/query/PhabricatorPeopleQuery.php b/src/applications/people/query/PhabricatorPeopleQuery.php index 1b4c7c5450..b8a0851252 100644 --- a/src/applications/people/query/PhabricatorPeopleQuery.php +++ b/src/applications/people/query/PhabricatorPeopleQuery.php @@ -12,6 +12,7 @@ final class PhabricatorPeopleQuery private $dateCreatedBefore; private $isAdmin; private $isSystemAgent; + private $isMailingList; private $isDisabled; private $isApproved; private $nameLike; @@ -67,6 +68,11 @@ final class PhabricatorPeopleQuery return $this; } + public function withIsMailingList($mailing_list) { + $this->isMailingList = $mailing_list; + return $this; + } + public function withIsDisabled($disabled) { $this->isDisabled = $disabled; return $this; @@ -107,19 +113,13 @@ final class PhabricatorPeopleQuery return $this; } - protected function loadPage() { - $table = new PhabricatorUser(); - $conn_r = $table->establishConnection('r'); + public function newResultObject() { + return new PhabricatorUser(); + } - $data = queryfx_all( - $conn_r, - 'SELECT * FROM %T user %Q %Q %Q %Q %Q', - $table->getTableName(), - $this->buildJoinsClause($conn_r), - $this->buildWhereClause($conn_r), - $this->buildGroupClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); + protected function loadPage() { + $table = new PhabricatorUser(); + $data = $this->loadStandardPageRows($table); if ($this->needPrimaryEmail) { $table->putInSet(new LiskDAOSet()); @@ -219,23 +219,21 @@ final class PhabricatorPeopleQuery return $users; } - protected function buildGroupClause(AphrontDatabaseConnection $conn) { + protected function shouldGroupQueryResultRows() { if ($this->nameTokens) { - return qsprintf( - $conn, - 'GROUP BY user.id'); - } else { - return $this->buildApplicationSearchGroupClause($conn); + return true; } + + return parent::shouldGroupQueryResultRows(); } - private function buildJoinsClause($conn_r) { - $joins = array(); + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $joins = parent::buildJoinClauseParts($conn); if ($this->emails) { $email_table = new PhabricatorUserEmail(); $joins[] = qsprintf( - $conn_r, + $conn, 'JOIN %T email ON email.userPHID = user.PHID', $email_table->getTableName()); } @@ -244,7 +242,7 @@ final class PhabricatorPeopleQuery foreach ($this->nameTokens as $key => $token) { $token_table = 'token_'.$key; $joins[] = qsprintf( - $conn_r, + $conn, 'JOIN %T %T ON %T.userID = user.id AND %T.token LIKE %>', PhabricatorUser::NAMETOKEN_TABLE, $token_table, @@ -254,101 +252,105 @@ final class PhabricatorPeopleQuery } } - $joins[] = $this->buildApplicationSearchJoinClause($conn_r); - - $joins = implode(' ', $joins); return $joins; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); if ($this->usernames !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'user.userName IN (%Ls)', $this->usernames); } if ($this->emails !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'email.address IN (%Ls)', $this->emails); } if ($this->realnames !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'user.realName IN (%Ls)', $this->realnames); } if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'user.phid IN (%Ls)', $this->phids); } if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'user.id IN (%Ld)', $this->ids); } if ($this->dateCreatedAfter) { $where[] = qsprintf( - $conn_r, + $conn, 'user.dateCreated >= %d', $this->dateCreatedAfter); } if ($this->dateCreatedBefore) { $where[] = qsprintf( - $conn_r, + $conn, 'user.dateCreated <= %d', $this->dateCreatedBefore); } - if ($this->isAdmin) { + if ($this->isAdmin !== null) { $where[] = qsprintf( - $conn_r, - 'user.isAdmin = 1'); + $conn, + 'user.isAdmin = %d', + (int)$this->isAdmin); } if ($this->isDisabled !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'user.isDisabled = %d', (int)$this->isDisabled); } if ($this->isApproved !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'user.isApproved = %d', (int)$this->isApproved); } - if ($this->isSystemAgent) { + if ($this->isSystemAgent !== null) { $where[] = qsprintf( - $conn_r, - 'user.isSystemAgent = 1'); + $conn, + 'user.isSystemAgent = %d', + (int)$this->isSystemAgent); + } + + if ($this->isMailingList !== null) { + $where[] = qsprintf( + $conn, + 'user.isMailingList = %d', + (int)$this->isMailingList); } if (strlen($this->nameLike)) { $where[] = qsprintf( - $conn_r, + $conn, 'user.username LIKE %~ OR user.realname LIKE %~', $this->nameLike, $this->nameLike); } - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); + return $where; } protected function getPrimaryTableAlias() { diff --git a/src/applications/people/query/PhabricatorPeopleSearchEngine.php b/src/applications/people/query/PhabricatorPeopleSearchEngine.php index 4785cd985d..5038e4db5e 100644 --- a/src/applications/people/query/PhabricatorPeopleSearchEngine.php +++ b/src/applications/people/query/PhabricatorPeopleSearchEngine.php @@ -11,31 +11,75 @@ final class PhabricatorPeopleSearchEngine return 'PhabricatorPeopleApplication'; } - public function getCustomFieldObject() { - return new PhabricatorUser(); - } - - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - - $saved->setParameter('usernames', $request->getStrList('usernames')); - $saved->setParameter('nameLike', $request->getStr('nameLike')); - $saved->setParameter('isAdmin', $request->getStr('isAdmin')); - $saved->setParameter('isDisabled', $request->getStr('isDisabled')); - $saved->setParameter('isSystemAgent', $request->getStr('isSystemAgent')); - $saved->setParameter('needsApproval', $request->getStr('needsApproval')); - $saved->setParameter('createdStart', $request->getStr('createdStart')); - $saved->setParameter('createdEnd', $request->getStr('createdEnd')); - - $this->readCustomFieldsFromRequest($request, $saved); - - return $saved; - } - - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new PhabricatorPeopleQuery()) + public function newQuery() { + return id(new PhabricatorPeopleQuery()) ->needPrimaryEmail(true) ->needProfileImage(true); + } + + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorSearchStringListField()) + ->setLabel(pht('Usernames')) + ->setKey('usernames') + ->setAliases(array('username')), + id(new PhabricatorSearchTextField()) + ->setLabel(pht('Name Contains')) + ->setKey('nameLike'), + id(new PhabricatorSearchThreeStateField()) + ->setLabel(pht('Administrators')) + ->setKey('isAdmin') + ->setOptions( + pht('(Show All)'), + pht('Show Only Administrators'), + pht('Hide Administrators')), + id(new PhabricatorSearchThreeStateField()) + ->setLabel(pht('Disabled')) + ->setKey('isDisabled') + ->setOptions( + pht('(Show All)'), + pht('Show Only Disabled Users'), + pht('Hide Disabled Users')), + id(new PhabricatorSearchThreeStateField()) + ->setLabel(pht('Bots')) + ->setKey('isSystemAgent') + ->setOptions( + pht('(Show All)'), + pht('Show Only Bots'), + pht('Hide Bots')), + id(new PhabricatorSearchThreeStateField()) + ->setLabel(pht('Mailing Lists')) + ->setKey('isMailingList') + ->setOptions( + pht('(Show All)'), + pht('Show Only Mailing Lists'), + pht('Hide Mailing Lists')), + id(new PhabricatorSearchThreeStateField()) + ->setLabel(pht('Needs Approval')) + ->setKey('needsApproval') + ->setOptions( + pht('(Show All)'), + pht('Show Only Unapproved Users'), + pht('Hide Unappproved Users')), + id(new PhabricatorSearchDateField()) + ->setKey('createdStart') + ->setLabel(pht('Joined After')), + id(new PhabricatorSearchDateField()) + ->setKey('createdEnd') + ->setLabel(pht('Joined Before')), + ); + } + + protected function getDefaultFieldOrder() { + return array( + '...', + 'createdStart', + 'createdEnd', + ); + } + + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); $viewer = $this->requireViewer(); @@ -51,120 +95,52 @@ final class PhabricatorPeopleSearchEngine $query->withPHIDs(array($viewer->getPHID())); } - $usernames = $saved->getParameter('usernames', array()); - if ($usernames) { - $query->withUsernames($usernames); + if ($map['usernames']) { + $query->withUsernames($map['usernames']); } - $like = $saved->getParameter('nameLike'); - if ($like) { - $query->withNameLike($like); + if ($map['nameLike']) { + $query->withNameLike($map['nameLike']); } - $is_admin = $saved->getParameter('isAdmin'); - $is_disabled = $saved->getParameter('isDisabled'); - $is_system_agent = $saved->getParameter('isSystemAgent'); - $needs_approval = $saved->getParameter('needsApproval'); - $no_disabled = $saved->getParameter('noDisabled'); - - if ($is_admin) { - $query->withIsAdmin(true); + if ($map['isAdmin'] !== null) { + $query->withIsAdmin($map['isAdmin']); } - if ($is_disabled) { - $query->withIsDisabled(true); - } else if ($no_disabled) { - $query->withIsDisabled(false); + if ($map['isDisabled'] !== null) { + $query->withIsDisabled($map['isDisabled']); } - if ($is_system_agent) { - $query->withIsSystemAgent(true); + if ($map['isMailingList'] !== null) { + $query->withIsMailingList($map['isMailingList']); } - if ($needs_approval) { - $query->withIsApproved(false); + if ($map['isSystemAgent'] !== null) { + $query->withIsSystemAgent($map['isSystemAgent']); } - $start = $this->parseDateTime($saved->getParameter('createdStart')); - $end = $this->parseDateTime($saved->getParameter('createdEnd')); - - if ($start) { - $query->withDateCreatedAfter($start); + if ($map['needsApproval'] !== null) { + $query->withIsApproved(!$map['needsApproval']); } - if ($end) { - $query->withDateCreatedBefore($end); + if ($map['createdStart']) { + $query->withDateCreatedAfter($map['createdStart']); } - $this->applyCustomFieldsToQuery($query, $saved); + if ($map['createdEnd']) { + $query->withDateCreatedBefore($map['createdEnd']); + } return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved) { - - $usernames = $saved->getParameter('usernames', array()); - $like = $saved->getParameter('nameLike'); - - $is_admin = $saved->getParameter('isAdmin'); - $is_disabled = $saved->getParameter('isDisabled'); - $is_system_agent = $saved->getParameter('isSystemAgent'); - $needs_approval = $saved->getParameter('needsApproval'); - - $form - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('usernames') - ->setLabel(pht('Usernames')) - ->setValue(implode(', ', $usernames))) - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('nameLike') - ->setLabel(pht('Name Contains')) - ->setValue($like)) - ->appendChild( - id(new AphrontFormCheckboxControl()) - ->setLabel('Role') - ->addCheckbox( - 'isAdmin', - 1, - pht('Show only administrators.'), - $is_admin) - ->addCheckbox( - 'isDisabled', - 1, - pht('Show only disabled users.'), - $is_disabled) - ->addCheckbox( - 'isSystemAgent', - 1, - pht('Show only bots.'), - $is_system_agent) - ->addCheckbox( - 'needsApproval', - 1, - pht('Show only users who need approval.'), - $needs_approval)); - - $this->appendCustomFieldsToForm($form, $saved); - - $this->buildDateRange( - $form, - $saved, - 'createdStart', - pht('Joined After'), - 'createdEnd', - pht('Joined Before')); - } - protected function getURI($path) { return '/people/'.$path; } protected function getBuiltinQueryNames() { $names = array( + 'active' => pht('Active'), 'all' => pht('All'), ); @@ -183,10 +159,13 @@ final class PhabricatorPeopleSearchEngine switch ($query_key) { case 'all': return $query; + case 'active': + return $query + ->setParameter('isDisabled', false); case 'approval': return $query ->setParameter('needsApproval', true) - ->setParameter('noDisabled', true); + ->setParameter('isDisabled', false); } return parent::buildSavedQueryFromBuiltin($query_key); @@ -240,7 +219,11 @@ final class PhabricatorPeopleSearchEngine } if ($user->getIsSystemAgent()) { - $item->addIcon('fa-desktop', pht('Bot/Script')); + $item->addIcon('fa-desktop', pht('Bot')); + } + + if ($user->getIsMailingList()) { + $item->addIcon('fa-envelope-o', pht('Mailing List')); } if ($viewer->getIsAdmin()) { diff --git a/src/applications/people/query/PhabricatorPeopleTransactionQuery.php b/src/applications/people/query/PhabricatorPeopleTransactionQuery.php new file mode 100644 index 0000000000..898bc9ee05 --- /dev/null +++ b/src/applications/people/query/PhabricatorPeopleTransactionQuery.php @@ -0,0 +1,10 @@ +isDisabled; case 'isSystemAgent': return (bool)$this->isSystemAgent; + case 'isMailingList': + return (bool)$this->isMailingList; case 'isEmailVerified': return (bool)$this->isEmailVerified; case 'isApproved': @@ -112,6 +117,46 @@ final class PhabricatorUser return true; } + public function canEstablishWebSessions() { + if (!$this->isUserActivated()) { + return false; + } + + if ($this->getIsMailingList()) { + return false; + } + + if ($this->getIsSystemAgent()) { + return false; + } + + return true; + } + + public function canEstablishAPISessions() { + if (!$this->isUserActivated()) { + return false; + } + + if ($this->getIsMailingList()) { + return false; + } + + return true; + } + + public function canEstablishSSHSessions() { + if (!$this->isUserActivated()) { + return false; + } + + if ($this->getIsMailingList()) { + return false; + } + + return true; + } + /** * Returns `true` if this is a standard user who is logged in. Returns `false` * for logged out, anonymous, or external users. @@ -140,6 +185,7 @@ final class PhabricatorUser 'consoleTab' => 'text64', 'conduitCertificate' => 'text255', 'isSystemAgent' => 'bool', + 'isMailingList' => 'bool', 'isDisabled' => 'bool', 'isAdmin' => 'bool', 'timezoneIdentifier' => 'text255', @@ -298,21 +344,26 @@ final class PhabricatorUser self::CSRF_TOKEN_LENGTH); } - /** - * @phutil-external-symbol class PhabricatorStartup - */ public function getCSRFToken() { - $salt = PhabricatorStartup::getGlobal('csrf.salt'); - if (!$salt) { - $salt = Filesystem::readRandomCharacters(self::CSRF_SALT_LENGTH); - PhabricatorStartup::setGlobal('csrf.salt', $salt); + if ($this->isOmnipotent()) { + // We may end up here when called from the daemons. The omnipotent user + // has no meaningful CSRF token, so just return `null`. + return null; } + if ($this->csrfSalt === null) { + $this->csrfSalt = Filesystem::readRandomCharacters( + self::CSRF_SALT_LENGTH); + } + + $salt = $this->csrfSalt; + // Generate a token hash to mitigate BREACH attacks against SSL. See // discussion in T3684. $token = $this->getRawCSRFToken(); $hash = PhabricatorHash::digest($token, $salt); - return 'B@'.$salt.substr($hash, 0, self::CSRF_TOKEN_LENGTH); + return self::CSRF_BREACH_PREFIX.$salt.substr( + $hash, 0, self::CSRF_TOKEN_LENGTH); } public function validateCSRFToken($token) { @@ -707,6 +758,20 @@ final class PhabricatorUser $email->getUserPHID()); } + public function getDefaultSpacePHID() { + // TODO: We might let the user switch which space they're "in" later on; + // for now just use the global space if one exists. + + $spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($this); + foreach ($spaces as $space) { + if ($space->getIsDefaultNamespace()) { + return $space->getPHID(); + } + } + + return null; + } + /** * Grant a user a source of authority, to let them bypass policy checks they @@ -1032,7 +1097,7 @@ final class PhabricatorUser case PhabricatorPolicyCapability::CAN_VIEW: return PhabricatorPolicies::POLICY_PUBLIC; case PhabricatorPolicyCapability::CAN_EDIT: - if ($this->getIsSystemAgent()) { + if ($this->getIsSystemAgent() || $this->getIsMailingList()) { return PhabricatorPolicies::POLICY_ADMIN; } else { return PhabricatorPolicies::POLICY_NOONE; @@ -1155,4 +1220,26 @@ final class PhabricatorUser return 'id_rsa_phabricator'; } + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new PhabricatorUserProfileEditor(); + } + + public function getApplicationTransactionObject() { + return $this; + } + + public function getApplicationTransactionTemplate() { + return new PhabricatorUserTransaction(); + } + + public function willRenderTimeline( + PhabricatorApplicationTransactionView $timeline, + AphrontRequest $request) { + return $timeline; + } + } diff --git a/src/applications/people/storage/PhabricatorUserLog.php b/src/applications/people/storage/PhabricatorUserLog.php index 1f14a27e4c..5939778f25 100644 --- a/src/applications/people/storage/PhabricatorUserLog.php +++ b/src/applications/people/storage/PhabricatorUserLog.php @@ -16,6 +16,7 @@ final class PhabricatorUserLog extends PhabricatorUserDAO const ACTION_ADMIN = 'admin'; const ACTION_SYSTEM_AGENT = 'system-agent'; + const ACTION_MAILING_LIST = 'mailing-list'; const ACTION_DISABLE = 'disable'; const ACTION_APPROVE = 'approve'; const ACTION_DELETE = 'delete'; @@ -62,6 +63,7 @@ final class PhabricatorUserLog extends PhabricatorUserDAO self::ACTION_EDIT => pht('Edit Account'), self::ACTION_ADMIN => pht('Add/Remove Administrator'), self::ACTION_SYSTEM_AGENT => pht('Add/Remove System Agent'), + self::ACTION_MAILING_LIST => pht('Add/Remove Mailing List'), self::ACTION_DISABLE => pht('Enable/Disable'), self::ACTION_APPROVE => pht('Approve Registration'), self::ACTION_DELETE => pht('Delete User'), diff --git a/src/applications/people/typeahead/PhabricatorPeopleDatasource.php b/src/applications/people/typeahead/PhabricatorPeopleDatasource.php index ab1f11cede..cdd68988a9 100644 --- a/src/applications/people/typeahead/PhabricatorPeopleDatasource.php +++ b/src/applications/people/typeahead/PhabricatorPeopleDatasource.php @@ -54,7 +54,9 @@ final class PhabricatorPeopleDatasource if ($user->getIsDisabled()) { $closed = pht('Disabled'); } else if ($user->getIsSystemAgent()) { - $closed = pht('Bot/Script'); + $closed = pht('Bot'); + } else if ($user->getIsMailingList()) { + $closed = pht('Mailing List'); } $result = id(new PhabricatorTypeaheadResult()) @@ -65,10 +67,14 @@ final class PhabricatorPeopleDatasource ->setPriorityType('user') ->setClosed($closed); + if ($user->getIsMailingList()) { + $result->setIcon('fa-envelope-o'); + } + if ($this->enrichResults) { - $display_type = 'User'; + $display_type = pht('User'); if ($user->getIsAdmin()) { - $display_type = 'Administrator'; + $display_type = pht('Administrator'); } $result->setDisplayType($display_type); $result->setImageURI($handles[$user->getPHID()]->getImageURI()); diff --git a/src/applications/phame/application/PhabricatorPhameApplication.php b/src/applications/phame/application/PhabricatorPhameApplication.php index 4d8cebffa0..75c68852d0 100644 --- a/src/applications/phame/application/PhabricatorPhameApplication.php +++ b/src/applications/phame/application/PhabricatorPhameApplication.php @@ -15,7 +15,7 @@ final class PhabricatorPhameApplication extends PhabricatorApplication { } public function getShortDescription() { - return 'Blog'; + return pht('Blog'); } public function getTitleGlyph() { diff --git a/src/applications/phame/storage/PhameBlog.php b/src/applications/phame/storage/PhameBlog.php index 349d40d773..ea212c4709 100644 --- a/src/applications/phame/storage/PhameBlog.php +++ b/src/applications/phame/storage/PhameBlog.php @@ -1,7 +1,10 @@ setConfigData($config); } - static public function getSkinOptionsForSelect() { + public static function getSkinOptionsForSelect() { $classes = id(new PhutilSymbolLoader()) ->setAncestorClass('PhameBlogSkin') ->setType('class') @@ -302,4 +305,26 @@ final class PhameBlog extends PhameDAO return (bool)$this->getPHID(); } + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new PhameBlogEditor(); + } + + public function getApplicationTransactionObject() { + return $this; + } + + public function getApplicationTransactionTemplate() { + return new PhameBlogTransaction(); + } + + public function willRenderTimeline( + PhabricatorApplicationTransactionView $timeline, + AphrontRequest $request) { + return $timeline; + } + } diff --git a/src/applications/phame/storage/PhamePost.php b/src/applications/phame/storage/PhamePost.php index e44e9615d3..1ea48a618a 100644 --- a/src/applications/phame/storage/PhamePost.php +++ b/src/applications/phame/storage/PhamePost.php @@ -167,13 +167,13 @@ final class PhamePost extends PhameDAO if ($current == 'facebook' || PhabricatorFacebookAuthProvider::getFacebookApplicationID()) { - $options['facebook'] = 'Facebook'; + $options['facebook'] = pht('Facebook'); } if ($current == 'disqus' || PhabricatorEnv::getEnvConfig('disqus.shortname')) { - $options['disqus'] = 'Disqus'; + $options['disqus'] = pht('Disqus'); } - $options['none'] = 'None'; + $options['none'] = pht('None'); return $options; } diff --git a/src/applications/phid/handle/const/PhabricatorObjectHandleConstants.php b/src/applications/phid/handle/const/PhabricatorObjectHandleConstants.php deleted file mode 100644 index 411fb88c48..0000000000 --- a/src/applications/phid/handle/const/PhabricatorObjectHandleConstants.php +++ /dev/null @@ -1,3 +0,0 @@ -isInstalled()) { - if ($names) { - // We still haven't been able to resolve everything; try mailing lists - // by name as a last resort. - $lists = id(new PhabricatorMailingListQuery()) - ->setViewer($this->getViewer()) - ->withNames($names) - ->execute(); - - $lists = mpull($lists, null, 'getName'); - foreach ($names as $key => $name) { - if (isset($lists[$name])) { - $results[$name] = $lists[$name]; - unset($names[$key]); - } - } - } - } - return $results; } diff --git a/src/applications/pholio/constants/PholioConstants.php b/src/applications/pholio/constants/PholioConstants.php deleted file mode 100644 index d74fec2a80..0000000000 --- a/src/applications/pholio/constants/PholioConstants.php +++ /dev/null @@ -1,3 +0,0 @@ -setTransactionType(PholioTransactionType::TYPE_INLINE) + ->setTransactionType(PholioTransaction::TYPE_INLINE) ->attachComment($inline_comment); } diff --git a/src/applications/pholio/controller/PholioMockEditController.php b/src/applications/pholio/controller/PholioMockEditController.php index 17abba6e60..3ed2cc0779 100644 --- a/src/applications/pholio/controller/PholioMockEditController.php +++ b/src/applications/pholio/controller/PholioMockEditController.php @@ -69,9 +69,9 @@ final class PholioMockEditController extends PholioController { if ($request->isFormPost()) { $xactions = array(); - $type_name = PholioTransactionType::TYPE_NAME; - $type_desc = PholioTransactionType::TYPE_DESCRIPTION; - $type_status = PholioTransactionType::TYPE_STATUS; + $type_name = PholioTransaction::TYPE_NAME; + $type_desc = PholioTransaction::TYPE_DESCRIPTION; + $type_status = PholioTransaction::TYPE_STATUS; $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; $type_cc = PhabricatorTransactions::TYPE_SUBSCRIBERS; @@ -93,7 +93,7 @@ final class PholioMockEditController extends PholioController { $mock_xactions[$type_cc] = array('=' => $v_cc); if (!strlen($request->getStr('name'))) { - $e_name = 'Required'; + $e_name = pht('Required'); $errors[] = pht('You must give the mock a name.'); } @@ -160,7 +160,7 @@ final class PholioMockEditController extends PholioController { ->setSequence($sequence); $xactions[] = id(new PholioTransaction()) ->setTransactionType( - PholioTransactionType::TYPE_IMAGE_REPLACE) + PholioTransaction::TYPE_IMAGE_REPLACE) ->setNewValue($replace_image); $posted_mock_images[] = $replace_image; } else if (!$existing_image) { // this is an add @@ -171,23 +171,23 @@ final class PholioMockEditController extends PholioController { ->setDescription($description) ->setSequence($sequence); $xactions[] = id(new PholioTransaction()) - ->setTransactionType(PholioTransactionType::TYPE_IMAGE_FILE) + ->setTransactionType(PholioTransaction::TYPE_IMAGE_FILE) ->setNewValue( array('+' => array($add_image))); $posted_mock_images[] = $add_image; } else { $xactions[] = id(new PholioTransaction()) - ->setTransactionType(PholioTransactionType::TYPE_IMAGE_NAME) + ->setTransactionType(PholioTransaction::TYPE_IMAGE_NAME) ->setNewValue( array($existing_image->getPHID() => $title)); $xactions[] = id(new PholioTransaction()) ->setTransactionType( - PholioTransactionType::TYPE_IMAGE_DESCRIPTION) + PholioTransaction::TYPE_IMAGE_DESCRIPTION) ->setNewValue( array($existing_image->getPHID() => $description)); $xactions[] = id(new PholioTransaction()) ->setTransactionType( - PholioTransactionType::TYPE_IMAGE_SEQUENCE) + PholioTransaction::TYPE_IMAGE_SEQUENCE) ->setNewValue( array($existing_image->getPHID() => $sequence)); @@ -198,7 +198,7 @@ final class PholioMockEditController extends PholioController { if (!isset($files[$file_phid]) && !isset($replaces[$file_phid])) { // this is an outright delete $xactions[] = id(new PholioTransaction()) - ->setTransactionType(PholioTransactionType::TYPE_IMAGE_FILE) + ->setTransactionType(PholioTransaction::TYPE_IMAGE_FILE) ->setNewValue( array('-' => array($mock_image))); } @@ -339,7 +339,7 @@ final class PholioMockEditController extends PholioController { ->setDatasource(new PhabricatorProjectDatasource())) ->appendControl( id(new AphrontFormTokenizerControl()) - ->setLabel(pht('CC')) + ->setLabel(pht('Subscribers')) ->setName('cc') ->setValue($v_cc) ->setUser($user) diff --git a/src/applications/pholio/editor/PholioMockEditor.php b/src/applications/pholio/editor/PholioMockEditor.php index 87d8d51167..14d3a0af16 100644 --- a/src/applications/pholio/editor/PholioMockEditor.php +++ b/src/applications/pholio/editor/PholioMockEditor.php @@ -29,16 +29,16 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; - $types[] = PholioTransactionType::TYPE_NAME; - $types[] = PholioTransactionType::TYPE_DESCRIPTION; - $types[] = PholioTransactionType::TYPE_STATUS; - $types[] = PholioTransactionType::TYPE_INLINE; + $types[] = PholioTransaction::TYPE_NAME; + $types[] = PholioTransaction::TYPE_DESCRIPTION; + $types[] = PholioTransaction::TYPE_STATUS; + $types[] = PholioTransaction::TYPE_INLINE; - $types[] = PholioTransactionType::TYPE_IMAGE_FILE; - $types[] = PholioTransactionType::TYPE_IMAGE_NAME; - $types[] = PholioTransactionType::TYPE_IMAGE_DESCRIPTION; - $types[] = PholioTransactionType::TYPE_IMAGE_REPLACE; - $types[] = PholioTransactionType::TYPE_IMAGE_SEQUENCE; + $types[] = PholioTransaction::TYPE_IMAGE_FILE; + $types[] = PholioTransaction::TYPE_IMAGE_NAME; + $types[] = PholioTransaction::TYPE_IMAGE_DESCRIPTION; + $types[] = PholioTransaction::TYPE_IMAGE_REPLACE; + $types[] = PholioTransaction::TYPE_IMAGE_SEQUENCE; return $types; } @@ -48,16 +48,16 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PholioTransactionType::TYPE_NAME: + case PholioTransaction::TYPE_NAME: return $object->getName(); - case PholioTransactionType::TYPE_DESCRIPTION: + case PholioTransaction::TYPE_DESCRIPTION: return $object->getDescription(); - case PholioTransactionType::TYPE_STATUS: + case PholioTransaction::TYPE_STATUS: return $object->getStatus(); - case PholioTransactionType::TYPE_IMAGE_FILE: + case PholioTransaction::TYPE_IMAGE_FILE: $images = $object->getImages(); return mpull($images, 'getPHID'); - case PholioTransactionType::TYPE_IMAGE_NAME: + case PholioTransaction::TYPE_IMAGE_NAME: $name = null; $phid = null; $image = $this->getImageForXaction($object, $xaction); @@ -66,7 +66,7 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { $phid = $image->getPHID(); } return array($phid => $name); - case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: + case PholioTransaction::TYPE_IMAGE_DESCRIPTION: $description = null; $phid = null; $image = $this->getImageForXaction($object, $xaction); @@ -75,10 +75,10 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { $phid = $image->getPHID(); } return array($phid => $description); - case PholioTransactionType::TYPE_IMAGE_REPLACE: + case PholioTransaction::TYPE_IMAGE_REPLACE: $raw = $xaction->getNewValue(); return $raw->getReplacesImagePHID(); - case PholioTransactionType::TYPE_IMAGE_SEQUENCE: + case PholioTransaction::TYPE_IMAGE_SEQUENCE: $sequence = null; $phid = null; $image = $this->getImageForXaction($object, $xaction); @@ -95,17 +95,17 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PholioTransactionType::TYPE_NAME: - case PholioTransactionType::TYPE_DESCRIPTION: - case PholioTransactionType::TYPE_STATUS: - case PholioTransactionType::TYPE_IMAGE_NAME: - case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: - case PholioTransactionType::TYPE_IMAGE_SEQUENCE: + case PholioTransaction::TYPE_NAME: + case PholioTransaction::TYPE_DESCRIPTION: + case PholioTransaction::TYPE_STATUS: + case PholioTransaction::TYPE_IMAGE_NAME: + case PholioTransaction::TYPE_IMAGE_DESCRIPTION: + case PholioTransaction::TYPE_IMAGE_SEQUENCE: return $xaction->getNewValue(); - case PholioTransactionType::TYPE_IMAGE_REPLACE: + case PholioTransaction::TYPE_IMAGE_REPLACE: $raw = $xaction->getNewValue(); return $raw->getPHID(); - case PholioTransactionType::TYPE_IMAGE_FILE: + case PholioTransaction::TYPE_IMAGE_FILE: $raw_new_value = $xaction->getNewValue(); $new_value = array(); foreach ($raw_new_value as $key => $images) { @@ -121,14 +121,14 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PholioTransactionType::TYPE_IMAGE_FILE: + case PholioTransaction::TYPE_IMAGE_FILE: $new = $xaction->getNewValue(); $phids = array(); foreach ($new as $key => $images) { $phids[] = mpull($images, 'getFilePHID'); } return array_mergev($phids); - case PholioTransactionType::TYPE_IMAGE_REPLACE: + case PholioTransaction::TYPE_IMAGE_REPLACE: return array($xaction->getNewValue()->getFilePHID()); } @@ -141,7 +141,7 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PholioTransactionType::TYPE_INLINE: + case PholioTransaction::TYPE_INLINE: return true; } @@ -154,8 +154,8 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PholioTransactionType::TYPE_IMAGE_FILE: - case PholioTransactionType::TYPE_IMAGE_REPLACE: + case PholioTransaction::TYPE_IMAGE_FILE: + case PholioTransaction::TYPE_IMAGE_REPLACE: return true; break; } @@ -170,7 +170,7 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { $new_images = array(); foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PholioTransactionType::TYPE_IMAGE_FILE: + case PholioTransaction::TYPE_IMAGE_FILE: $new_value = $xaction->getNewValue(); foreach ($new_value as $key => $txn_images) { if ($key != '+') { @@ -182,7 +182,7 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { } } break; - case PholioTransactionType::TYPE_IMAGE_REPLACE: + case PholioTransaction::TYPE_IMAGE_REPLACE: $image = $xaction->getNewValue(); $image->save(); $new_images[] = $image; @@ -197,16 +197,16 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PholioTransactionType::TYPE_NAME: + case PholioTransaction::TYPE_NAME: $object->setName($xaction->getNewValue()); if ($object->getOriginalName() === null) { $object->setOriginalName($xaction->getNewValue()); } break; - case PholioTransactionType::TYPE_DESCRIPTION: + case PholioTransaction::TYPE_DESCRIPTION: $object->setDescription($xaction->getNewValue()); break; - case PholioTransactionType::TYPE_STATUS: + case PholioTransaction::TYPE_STATUS: $object->setStatus($xaction->getNewValue()); break; } @@ -231,7 +231,7 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PholioTransactionType::TYPE_IMAGE_FILE: + case PholioTransaction::TYPE_IMAGE_FILE: $old_map = array_fuse($xaction->getOldValue()); $new_map = array_fuse($xaction->getNewValue()); @@ -246,7 +246,7 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { } $object->attachImages($images); break; - case PholioTransactionType::TYPE_IMAGE_REPLACE: + case PholioTransaction::TYPE_IMAGE_REPLACE: $old = $xaction->getOldValue(); $images = $object->getImages(); foreach ($images as $seq => $image) { @@ -258,19 +258,19 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { } $object->attachImages($images); break; - case PholioTransactionType::TYPE_IMAGE_NAME: + case PholioTransaction::TYPE_IMAGE_NAME: $image = $this->getImageForXaction($object, $xaction); $value = (string)head($xaction->getNewValue()); $image->setName($value); $image->save(); break; - case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: + case PholioTransaction::TYPE_IMAGE_DESCRIPTION: $image = $this->getImageForXaction($object, $xaction); $value = (string)head($xaction->getNewValue()); $image->setDescription($value); $image->save(); break; - case PholioTransactionType::TYPE_IMAGE_SEQUENCE: + case PholioTransaction::TYPE_IMAGE_SEQUENCE: $image = $this->getImageForXaction($object, $xaction); $value = (int)head($xaction->getNewValue()); $image->setSequence($value); @@ -298,22 +298,22 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { $type = $u->getTransactionType(); switch ($type) { - case PholioTransactionType::TYPE_NAME: - case PholioTransactionType::TYPE_DESCRIPTION: - case PholioTransactionType::TYPE_STATUS: + case PholioTransaction::TYPE_NAME: + case PholioTransaction::TYPE_DESCRIPTION: + case PholioTransaction::TYPE_STATUS: return $v; - case PholioTransactionType::TYPE_IMAGE_REPLACE: + case PholioTransaction::TYPE_IMAGE_REPLACE: $u_img = $u->getNewValue(); $v_img = $v->getNewValue(); if ($u_img->getReplacesImagePHID() == $v_img->getReplacesImagePHID()) { return $v; } break; - case PholioTransactionType::TYPE_IMAGE_FILE: + case PholioTransaction::TYPE_IMAGE_FILE: return $this->mergePHIDOrEdgeTransactions($u, $v); - case PholioTransactionType::TYPE_IMAGE_NAME: - case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: - case PholioTransactionType::TYPE_IMAGE_SEQUENCE: + case PholioTransaction::TYPE_IMAGE_NAME: + case PholioTransaction::TYPE_IMAGE_DESCRIPTION: + case PholioTransaction::TYPE_IMAGE_SEQUENCE: $raw_new_value_u = $u->getNewValue(); $raw_new_value_v = $v->getNewValue(); $phid_u = key($raw_new_value_u); @@ -370,7 +370,7 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { } $comment = $xaction->getComment(); switch ($xaction->getTransactionType()) { - case PholioTransactionType::TYPE_INLINE: + case PholioTransaction::TYPE_INLINE: if ($comment && strlen($comment->getContent())) { $inline_comments[] = $comment; } @@ -418,13 +418,13 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { public function getMailTagsMap() { return array( - MetaMTANotificationType::TYPE_PHOLIO_STATUS => + PholioTransaction::MAILTAG_STATUS => pht("A mock's status changes."), - MetaMTANotificationType::TYPE_PHOLIO_COMMENT => + PholioTransaction::MAILTAG_COMMENT => pht('Someone comments on a mock.'), - MetaMTANotificationType::TYPE_PHOLIO_UPDATED => + PholioTransaction::MAILTAG_UPDATED => pht('Mock images or descriptions change.'), - MetaMTANotificationType::TYPE_PHOLIO_OTHER => + PholioTransaction::MAILTAG_OTHER => pht('Other mock activity not listed above occurs.'), ); } @@ -453,24 +453,6 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { ->setMock($object); } - protected function didApplyHeraldRules( - PhabricatorLiskDAO $object, - HeraldAdapter $adapter, - HeraldTranscript $transcript) { - - $xactions = array(); - - $cc_phids = $adapter->getCcPHIDs(); - if ($cc_phids) { - $value = array_fuse($cc_phids); - $xactions[] = id(new PholioTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS) - ->setNewValue(array('+' => $value)); - } - - return $xactions; - } - protected function sortTransactions(array $xactions) { $head = array(); $tail = array(); @@ -478,7 +460,7 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { // Move inline comments to the end, so the comments precede them. foreach ($xactions as $xaction) { $type = $xaction->getTransactionType(); - if ($type == PholioTransactionType::TYPE_INLINE) { + if ($type == PholioTransaction::TYPE_INLINE) { $tail[] = $xaction; } else { $head[] = $xaction; @@ -493,7 +475,7 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PholioTransactionType::TYPE_INLINE: + case PholioTransaction::TYPE_INLINE: return true; } diff --git a/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php b/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php index 3386219d66..2601284408 100644 --- a/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php +++ b/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php @@ -17,9 +17,9 @@ final class PhabricatorPholioMockTestDataGenerator // Accumulate Transactions $changes = array(); - $changes[PholioTransactionType::TYPE_NAME] = + $changes[PholioTransaction::TYPE_NAME] = $this->generateTitle(); - $changes[PholioTransactionType::TYPE_DESCRIPTION] = + $changes[PholioTransaction::TYPE_DESCRIPTION] = $this->generateDescription(); $changes[PhabricatorTransactions::TYPE_VIEW_POLICY] = PhabricatorPolicies::POLICY_PUBLIC; diff --git a/src/applications/pholio/query/PholioMockQuery.php b/src/applications/pholio/query/PholioMockQuery.php index ff6eea3028..5f1711def6 100644 --- a/src/applications/pholio/query/PholioMockQuery.php +++ b/src/applications/pholio/query/PholioMockQuery.php @@ -53,23 +53,12 @@ final class PholioMockQuery return $this; } + public function newResultObject() { + return new PholioMock(); + } + protected function loadPage() { - $table = new PholioMock(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - '%Q FROM %T mock %Q %Q %Q %Q %Q %Q', - $this->buildSelectClause($conn_r), - $table->getTableName(), - $this->buildJoinClause($conn_r), - $this->buildWhereClause($conn_r), - $this->buildGroupClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildHavingClause($conn_r), - $this->buildLimitClause($conn_r)); - - $mocks = $table->loadAllFromArray($data); + $mocks = $this->loadStandardPage(new PholioMock()); if ($mocks && $this->needImages) { self::loadImages($this->getViewer(), $mocks, $this->needInlineComments); @@ -86,40 +75,38 @@ final class PholioMockQuery return $mocks; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); - - $where[] = $this->buildWhereClauseParts($conn_r); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'mock.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'mock.phid IN (%Ls)', $this->phids); } if ($this->authorPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'mock.authorPHID in (%Ls)', $this->authorPHIDs); } if ($this->statuses !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'mock.status IN (%Ls)', $this->statuses); } - return $this->formatWhereClause($where); + return $where; } public static function loadImages( diff --git a/src/applications/pholio/query/PholioMockSearchEngine.php b/src/applications/pholio/query/PholioMockSearchEngine.php index 057a370649..26b22034a7 100644 --- a/src/applications/pholio/query/PholioMockSearchEngine.php +++ b/src/applications/pholio/query/PholioMockSearchEngine.php @@ -10,84 +10,40 @@ final class PholioMockSearchEngine extends PhabricatorApplicationSearchEngine { return 'PhabricatorPholioApplication'; } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - - $saved->setParameter( - 'authorPHIDs', - $this->readUsersFromRequest($request, 'authors')); - - $saved->setParameter( - 'projects', - $this->readProjectsFromRequest($request, 'projects')); - - $saved->setParameter( - 'statuses', - $request->getStrList('status')); - - return $saved; - } - - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new PholioMockQuery()) + public function newQuery() { + return id(new PholioMockQuery()) ->needCoverFiles(true) ->needImages(true) ->needTokenCounts(true); - - $datasource = id(new PhabricatorPeopleUserFunctionDatasource()) - ->setViewer($this->requireViewer()); - - $author_phids = $saved->getParameter('authorPHIDs', array()); - $author_phids = $datasource->evaluateTokens($author_phids); - if ($author_phids) { - $query->withAuthorPHIDs($author_phids); - } - - $statuses = $saved->getParameter('statuses', array()); - if ($statuses) { - $query->withStatuses($statuses); - } - - $this->setQueryProjects($query, $saved); - - return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved_query) { - - $author_phids = $saved_query->getParameter('authorPHIDs', array()); - $projects = $saved_query->getParameter('projects', array()); - - $statuses = array( - '' => pht('Any Status'), - 'closed' => pht('Closed'), - 'open' => pht('Open'), + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorSearchUsersField()) + ->setKey('authorPHIDs') + ->setAliases(array('authors')) + ->setLabel(pht('Authors')), + id(new PhabricatorSearchCheckboxesField()) + ->setKey('statuses') + ->setLabel(pht('Status')) + ->setOptions( + id(new PholioMock()) + ->getStatuses()), ); + } - $status = $saved_query->getParameter('statuses', array()); - $status = head($status); + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); - $form - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorPeopleUserFunctionDatasource()) - ->setName('authors') - ->setLabel(pht('Authors')) - ->setValue($author_phids)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorProjectLogicalDatasource()) - ->setName('projects') - ->setLabel(pht('Projects')) - ->setValue($projects)) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Status')) - ->setName('status') - ->setOptions($statuses) - ->setValue($status)); + if ($map['authorPHIDs']) { + $query->withAuthorPHIDs($map['authorPHIDs']); + } + + if ($map['statuses']) { + $query->withStatuses($map['statuses']); + } + + return $query; } protected function getURI($path) { diff --git a/src/applications/pholio/search/PholioSearchIndexer.php b/src/applications/pholio/search/PholioSearchIndexer.php index d0c0c47a7d..6c1458b799 100644 --- a/src/applications/pholio/search/PholioSearchIndexer.php +++ b/src/applications/pholio/search/PholioSearchIndexer.php @@ -15,7 +15,7 @@ final class PholioSearchIndexer extends PhabricatorSearchDocumentIndexer { ->setDocumentModified($mock->getDateModified()); $doc->addField( - PhabricatorSearchField::FIELD_BODY, + PhabricatorSearchDocumentFieldType::FIELD_BODY, $mock->getDescription()); $doc->addRelationship( diff --git a/src/applications/pholio/storage/PholioMock.php b/src/applications/pholio/storage/PholioMock.php index ed1a983d2b..d229aa8020 100644 --- a/src/applications/pholio/storage/PholioMock.php +++ b/src/applications/pholio/storage/PholioMock.php @@ -13,6 +13,9 @@ final class PholioMock extends PholioDAO const MARKUP_FIELD_DESCRIPTION = 'markup:description'; + const STATUS_OPEN = 'open'; + const STATUS_CLOSED = 'closed'; + protected $authorPHID; protected $viewPolicy; protected $editPolicy; @@ -41,7 +44,7 @@ final class PholioMock extends PholioDAO return id(new PholioMock()) ->setAuthorPHID($actor->getPHID()) ->attachImages(array()) - ->setStatus('open') + ->setStatus(self::STATUS_OPEN) ->setViewPolicy($view_policy) ->setEditPolicy($edit_policy); } @@ -159,8 +162,8 @@ final class PholioMock extends PholioDAO public function getStatuses() { $options = array(); - $options['open'] = pht('Open'); - $options['closed'] = pht('Closed'); + $options[self::STATUS_OPEN] = pht('Open'); + $options[self::STATUS_CLOSED] = pht('Closed'); return $options; } diff --git a/src/applications/pholio/storage/PholioTransaction.php b/src/applications/pholio/storage/PholioTransaction.php index 86ee15f1de..c4a0cf90f8 100644 --- a/src/applications/pholio/storage/PholioTransaction.php +++ b/src/applications/pholio/storage/PholioTransaction.php @@ -2,6 +2,26 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { + // Edits to the high level mock + const TYPE_NAME = 'name'; + const TYPE_DESCRIPTION = 'description'; + const TYPE_STATUS = 'status'; + + // Edits to images within the mock + const TYPE_IMAGE_FILE = 'image-file'; + const TYPE_IMAGE_NAME= 'image-name'; + const TYPE_IMAGE_DESCRIPTION = 'image-description'; + const TYPE_IMAGE_REPLACE = 'image-replace'; + const TYPE_IMAGE_SEQUENCE = 'image-sequence'; + + // Your witty commentary at the mock : image : x,y level + const TYPE_INLINE = 'inline'; + + const MAILTAG_STATUS = 'pholio-status'; + const MAILTAG_COMMENT = 'pholio-comment'; + const MAILTAG_UPDATED = 'pholio-updated'; + const MAILTAG_OTHER = 'pholio-other'; + public function getApplicationName() { return 'pholio'; } @@ -26,16 +46,16 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { $old = $this->getOldValue(); switch ($this->getTransactionType()) { - case PholioTransactionType::TYPE_IMAGE_FILE: + case self::TYPE_IMAGE_FILE: $phids = array_merge($phids, $new, $old); break; - case PholioTransactionType::TYPE_IMAGE_REPLACE: + case self::TYPE_IMAGE_REPLACE: $phids[] = $new; $phids[] = $old; break; - case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: - case PholioTransactionType::TYPE_IMAGE_NAME: - case PholioTransactionType::TYPE_IMAGE_SEQUENCE: + case self::TYPE_IMAGE_DESCRIPTION: + case self::TYPE_IMAGE_NAME: + case self::TYPE_IMAGE_SEQUENCE: $phids[] = key($new); break; } @@ -47,13 +67,13 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { $old = $this->getOldValue(); switch ($this->getTransactionType()) { - case PholioTransactionType::TYPE_DESCRIPTION: + case self::TYPE_DESCRIPTION: return ($old === null); - case PholioTransactionType::TYPE_IMAGE_NAME: - case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: + case self::TYPE_IMAGE_NAME: + case self::TYPE_IMAGE_DESCRIPTION: return ($old === array(null => null)); // this is boring / silly to surface; changing sequence is NBD - case PholioTransactionType::TYPE_IMAGE_SEQUENCE: + case self::TYPE_IMAGE_SEQUENCE: return true; } @@ -62,17 +82,17 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { public function getIcon() { switch ($this->getTransactionType()) { - case PholioTransactionType::TYPE_INLINE: + case self::TYPE_INLINE: return 'fa-comment'; - case PholioTransactionType::TYPE_NAME: - case PholioTransactionType::TYPE_DESCRIPTION: - case PholioTransactionType::TYPE_STATUS: - case PholioTransactionType::TYPE_IMAGE_NAME: - case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: - case PholioTransactionType::TYPE_IMAGE_SEQUENCE: + case self::TYPE_NAME: + case self::TYPE_DESCRIPTION: + case self::TYPE_STATUS: + case self::TYPE_IMAGE_NAME: + case self::TYPE_IMAGE_DESCRIPTION: + case self::TYPE_IMAGE_SEQUENCE: return 'fa-pencil'; - case PholioTransactionType::TYPE_IMAGE_FILE: - case PholioTransactionType::TYPE_IMAGE_REPLACE: + case self::TYPE_IMAGE_FILE: + case self::TYPE_IMAGE_REPLACE: return 'fa-picture-o'; } @@ -82,24 +102,24 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { public function getMailTags() { $tags = array(); switch ($this->getTransactionType()) { - case PholioTransactionType::TYPE_INLINE: + case self::TYPE_INLINE: case PhabricatorTransactions::TYPE_COMMENT: - $tags[] = MetaMTANotificationType::TYPE_PHOLIO_COMMENT; + $tags[] = self::MAILTAG_COMMENT; break; - case PholioTransactionType::TYPE_STATUS: - $tags[] = MetaMTANotificationType::TYPE_PHOLIO_STATUS; + case self::TYPE_STATUS: + $tags[] = self::MAILTAG_STATUS; break; - case PholioTransactionType::TYPE_NAME: - case PholioTransactionType::TYPE_DESCRIPTION: - case PholioTransactionType::TYPE_IMAGE_NAME: - case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: - case PholioTransactionType::TYPE_IMAGE_SEQUENCE: - case PholioTransactionType::TYPE_IMAGE_FILE: - case PholioTransactionType::TYPE_IMAGE_REPLACE: - $tags[] = MetaMTANotificationType::TYPE_PHOLIO_UPDATED; + case self::TYPE_NAME: + case self::TYPE_DESCRIPTION: + case self::TYPE_IMAGE_NAME: + case self::TYPE_IMAGE_DESCRIPTION: + case self::TYPE_IMAGE_SEQUENCE: + case self::TYPE_IMAGE_FILE: + case self::TYPE_IMAGE_REPLACE: + $tags[] = self::MAILTAG_UPDATED; break; default: - $tags[] = MetaMTANotificationType::TYPE_PHOLIO_OTHER; + $tags[] = self::MAILTAG_OTHER; break; } return $tags; @@ -113,7 +133,7 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { $type = $this->getTransactionType(); switch ($type) { - case PholioTransactionType::TYPE_NAME: + case self::TYPE_NAME: if ($old === null) { return pht( '%s created "%s".', @@ -127,17 +147,17 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { $new); } break; - case PholioTransactionType::TYPE_DESCRIPTION: + case self::TYPE_DESCRIPTION: return pht( "%s updated the mock's description.", $this->renderHandleLink($author_phid)); break; - case PholioTransactionType::TYPE_STATUS: + case self::TYPE_STATUS: return pht( "%s updated the mock's status.", $this->renderHandleLink($author_phid)); break; - case PholioTransactionType::TYPE_INLINE: + case self::TYPE_INLINE: $count = 1; foreach ($this->getTransactionGroup() as $xaction) { if ($xaction->getTransactionType() == $type) { @@ -150,14 +170,14 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { $this->renderHandleLink($author_phid), $count); break; - case PholioTransactionType::TYPE_IMAGE_REPLACE: + case self::TYPE_IMAGE_REPLACE: return pht( '%s replaced %s with %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($old), $this->renderHandleLink($new)); break; - case PholioTransactionType::TYPE_IMAGE_FILE: + case self::TYPE_IMAGE_FILE: $add = array_diff($new, $old); $rem = array_diff($old, $new); @@ -184,7 +204,7 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { } break; - case PholioTransactionType::TYPE_IMAGE_NAME: + case self::TYPE_IMAGE_NAME: return pht( '%s renamed an image (%s) from "%s" to "%s".', $this->renderHandleLink($author_phid), @@ -192,13 +212,13 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { reset($old), reset($new)); break; - case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: + case self::TYPE_IMAGE_DESCRIPTION: return pht( '%s updated an image\'s (%s) description.', $this->renderHandleLink($author_phid), $this->renderHandleLink(key($new))); break; - case PholioTransactionType::TYPE_IMAGE_SEQUENCE: + case self::TYPE_IMAGE_SEQUENCE: return pht( '%s updated an image\'s (%s) sequence.', $this->renderHandleLink($author_phid), @@ -218,7 +238,7 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { $type = $this->getTransactionType(); switch ($type) { - case PholioTransactionType::TYPE_NAME: + case self::TYPE_NAME: if ($old === null) { return pht( '%s created %s.', @@ -233,44 +253,44 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { $new); } break; - case PholioTransactionType::TYPE_DESCRIPTION: + case self::TYPE_DESCRIPTION: return pht( '%s updated the description for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); break; - case PholioTransactionType::TYPE_STATUS: + case self::TYPE_STATUS: return pht( '%s updated the status for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); break; - case PholioTransactionType::TYPE_INLINE: + case self::TYPE_INLINE: return pht( '%s added an inline comment to %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); break; - case PholioTransactionType::TYPE_IMAGE_REPLACE: - case PholioTransactionType::TYPE_IMAGE_FILE: + case self::TYPE_IMAGE_REPLACE: + case self::TYPE_IMAGE_FILE: return pht( '%s updated images of %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); break; - case PholioTransactionType::TYPE_IMAGE_NAME: + case self::TYPE_IMAGE_NAME: return pht( '%s updated the image names of %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); break; - case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: + case self::TYPE_IMAGE_DESCRIPTION: return pht( '%s updated image descriptions of %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); break; - case PholioTransactionType::TYPE_IMAGE_SEQUENCE: + case self::TYPE_IMAGE_SEQUENCE: return pht( '%s updated image sequence of %s.', $this->renderHandleLink($author_phid), @@ -284,13 +304,13 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { public function getBodyForFeed(PhabricatorFeedStory $story) { $text = null; switch ($this->getTransactionType()) { - case PholioTransactionType::TYPE_NAME: + case self::TYPE_NAME: if ($this->getOldValue() === null) { $mock = $story->getPrimaryObject(); $text = $mock->getDescription(); } break; - case PholioTransactionType::TYPE_INLINE: + case self::TYPE_INLINE: $text = $this->getComment()->getContent(); break; } @@ -307,8 +327,8 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { public function hasChangeDetails() { switch ($this->getTransactionType()) { - case PholioTransactionType::TYPE_DESCRIPTION: - case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: + case self::TYPE_DESCRIPTION: + case self::TYPE_IMAGE_DESCRIPTION: return true; } return parent::hasChangeDetails(); @@ -318,7 +338,7 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { $old = $this->getOldValue(); $new = $this->getNewValue(); if ($this->getTransactionType() == - PholioTransactionType::TYPE_IMAGE_DESCRIPTION) { + self::TYPE_IMAGE_DESCRIPTION) { $old = reset($old); $new = reset($new); } @@ -334,19 +354,19 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case PholioTransactionType::TYPE_NAME: + case self::TYPE_NAME: if ($old === null) { return PhabricatorTransactions::COLOR_GREEN; } - case PholioTransactionType::TYPE_DESCRIPTION: - case PholioTransactionType::TYPE_STATUS: - case PholioTransactionType::TYPE_IMAGE_NAME: - case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: - case PholioTransactionType::TYPE_IMAGE_SEQUENCE: + case self::TYPE_DESCRIPTION: + case self::TYPE_STATUS: + case self::TYPE_IMAGE_NAME: + case self::TYPE_IMAGE_DESCRIPTION: + case self::TYPE_IMAGE_SEQUENCE: return PhabricatorTransactions::COLOR_BLUE; - case PholioTransactionType::TYPE_IMAGE_REPLACE: + case self::TYPE_IMAGE_REPLACE: return PhabricatorTransactions::COLOR_YELLOW; - case PholioTransactionType::TYPE_IMAGE_FILE: + case self::TYPE_IMAGE_FILE: $add = array_diff($new, $old); $rem = array_diff($old, $new); if ($add && $rem) { @@ -363,11 +383,11 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { public function getNoEffectDescription() { switch ($this->getTransactionType()) { - case PholioTransactionType::TYPE_IMAGE_NAME: + case self::TYPE_IMAGE_NAME: return pht('The image title was not updated.'); - case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: + case self::TYPE_IMAGE_DESCRIPTION: return pht('The image description was not updated.'); - case PholioTransactionType::TYPE_IMAGE_SEQUENCE: + case self::TYPE_IMAGE_SEQUENCE: return pht('The image sequence was not updated.'); } diff --git a/src/applications/pholio/view/PholioTransactionView.php b/src/applications/pholio/view/PholioTransactionView.php index cf2f44a3aa..7fad6bff9a 100644 --- a/src/applications/pholio/view/PholioTransactionView.php +++ b/src/applications/pholio/view/PholioTransactionView.php @@ -30,14 +30,14 @@ final class PholioTransactionView switch ($u->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: - case PholioTransactionType::TYPE_INLINE: + case PholioTransaction::TYPE_INLINE: break; default: return false; } switch ($v->getTransactionType()) { - case PholioTransactionType::TYPE_INLINE: + case PholioTransaction::TYPE_INLINE: return true; } @@ -50,7 +50,7 @@ final class PholioTransactionView $out = array(); $group = $xaction->getTransactionGroup(); - if ($xaction->getTransactionType() == PholioTransactionType::TYPE_INLINE) { + if ($xaction->getTransactionType() == PholioTransaction::TYPE_INLINE) { array_unshift($group, $xaction); } else { $out[] = parent::renderTransactionContent($xaction); @@ -63,7 +63,7 @@ final class PholioTransactionView $inlines = array(); foreach ($group as $xaction) { switch ($xaction->getTransactionType()) { - case PholioTransactionType::TYPE_INLINE: + case PholioTransaction::TYPE_INLINE: $inlines[] = $xaction; break; default: diff --git a/src/applications/phortune/controller/PhortuneProductListController.php b/src/applications/phortune/controller/PhortuneProductListController.php index 8e219630d4..8a1181b33e 100644 --- a/src/applications/phortune/controller/PhortuneProductListController.php +++ b/src/applications/phortune/controller/PhortuneProductListController.php @@ -17,7 +17,9 @@ final class PhortuneProductListController extends PhabricatorController { $title = pht('Product List'); $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb('Products', $this->getApplicationURI('product/')); + $crumbs->addTextCrumb( + pht('Products'), + $this->getApplicationURI('product/')); $crumbs->addAction( id(new PHUIListItemView()) ->setName(pht('Create Product')) diff --git a/src/applications/phortune/controller/PhortuneProviderActionController.php b/src/applications/phortune/controller/PhortuneProviderActionController.php index dcb171911f..b856b67d97 100644 --- a/src/applications/phortune/controller/PhortuneProviderActionController.php +++ b/src/applications/phortune/controller/PhortuneProviderActionController.php @@ -44,12 +44,10 @@ final class PhortuneProviderActionController return $response; } - $title = 'Phortune'; - return $this->buildApplicationPage( $response, array( - 'title' => $title, + 'title' => pht('Phortune'), )); } diff --git a/src/applications/phortune/editor/PhortuneCartEditor.php b/src/applications/phortune/editor/PhortuneCartEditor.php index 0597c82d87..02f9b729de 100644 --- a/src/applications/phortune/editor/PhortuneCartEditor.php +++ b/src/applications/phortune/editor/PhortuneCartEditor.php @@ -189,7 +189,7 @@ final class PhortuneCartEditor protected function getMailTo(PhabricatorLiskDAO $object) { $phids = array(); - // Relaod the cart to pull merchant and account information, in case we + // Reload the cart to pull merchant and account information, in case we // just created the object. $cart = id(new PhortuneCartQuery()) ->setViewer($this->requireActor()) @@ -220,4 +220,24 @@ final class PhortuneCartEditor ->setMailReceiver($object); } + protected function willPublish(PhabricatorLiskDAO $object, array $xactions) { + // We need the purchases in order to build mail. + return id(new PhortuneCartQuery()) + ->setViewer($this->getActor()) + ->withIDs(array($object->getID())) + ->needPurchases(true) + ->executeOne(); + } + + protected function getCustomWorkerState() { + return array( + 'invoiceIssues' => $this->invoiceIssues, + ); + } + + protected function loadCustomWorkerState(array $state) { + $this->invoiceIssues = idx($state, 'invoiceIssues'); + return $this; + } + } diff --git a/src/applications/phpast/controller/PhabricatorXHPASTViewRunController.php b/src/applications/phpast/controller/PhabricatorXHPASTViewRunController.php index b235f5b842..f5a79a4b28 100644 --- a/src/applications/phpast/controller/PhabricatorXHPASTViewRunController.php +++ b/src/applications/phpast/controller/PhabricatorXHPASTViewRunController.php @@ -41,7 +41,7 @@ final class PhabricatorXHPASTViewRunController ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)) ->appendChild( id(new AphrontFormSubmitControl()) - ->setValue('Parse')); + ->setValue(pht('Parse'))); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Generate XHP AST')) diff --git a/src/applications/phragment/controller/PhragmentBrowseController.php b/src/applications/phragment/controller/PhragmentBrowseController.php index d41ff4442a..7b0a16cc52 100644 --- a/src/applications/phragment/controller/PhragmentBrowseController.php +++ b/src/applications/phragment/controller/PhragmentBrowseController.php @@ -78,7 +78,7 @@ final class PhragmentBrowseController extends PhragmentController { $item->addAttribute(pht('Deleted')); } } else { - $item->addAttribute('Directory'); + $item->addAttribute(pht('Directory')); } $list->addItem($item); } diff --git a/src/applications/phragment/controller/PhragmentHistoryController.php b/src/applications/phragment/controller/PhragmentHistoryController.php index cb84fd819f..72e0000b80 100644 --- a/src/applications/phragment/controller/PhragmentHistoryController.php +++ b/src/applications/phragment/controller/PhragmentHistoryController.php @@ -67,7 +67,7 @@ final class PhragmentHistoryController extends PhragmentController { if ($version->getFilePHID() === null) { $item->setDisabled(true); - $item->addAttribute('Deletion'); + $item->addAttribute(pht('Deletion')); } if (!$first && $can_edit) { diff --git a/src/applications/phragment/controller/PhragmentSnapshotCreateController.php b/src/applications/phragment/controller/PhragmentSnapshotCreateController.php index 2cdea8528a..e135819280 100644 --- a/src/applications/phragment/controller/PhragmentSnapshotCreateController.php +++ b/src/applications/phragment/controller/PhragmentSnapshotCreateController.php @@ -89,8 +89,8 @@ final class PhragmentSnapshotCreateController extends PhragmentController { 'tr', array(), array( - phutil_tag('th', array(), 'Fragment'), - phutil_tag('th', array(), 'Version'), + phutil_tag('th', array(), pht('Fragment')), + phutil_tag('th', array(), pht('Version')), )); $rows[] = phutil_tag( 'tr', diff --git a/src/applications/phragment/controller/PhragmentZIPController.php b/src/applications/phragment/controller/PhragmentZIPController.php index 574090eede..0de9f4d344 100644 --- a/src/applications/phragment/controller/PhragmentZIPController.php +++ b/src/applications/phragment/controller/PhragmentZIPController.php @@ -68,7 +68,7 @@ final class PhragmentZIPController extends PhragmentController { } if (!$zip->open((string)$temp, ZipArchive::CREATE)) { - throw new Exception('Unable to create ZIP archive!'); + throw new Exception(pht('Unable to create ZIP archive!')); } $mappings = $this->getFragmentMappings($fragment, $fragment->getPath()); diff --git a/src/applications/phriction/constants/PhrictionActionConstants.php b/src/applications/phriction/constants/PhrictionActionConstants.php deleted file mode 100644 index f39691181e..0000000000 --- a/src/applications/phriction/constants/PhrictionActionConstants.php +++ /dev/null @@ -1,23 +0,0 @@ - pht('created'), - self::ACTION_EDIT => pht('edited'), - self::ACTION_DELETE => pht('deleted'), - self::ACTION_MOVE_AWAY => pht('moved'), - self::ACTION_MOVE_HERE => pht('moved'), - ); - - return idx($map, $action, pht("brazenly %s'd", $action)); - } - -} diff --git a/src/applications/phriction/editor/PhrictionTransactionEditor.php b/src/applications/phriction/editor/PhrictionTransactionEditor.php index 9d8aedcc3a..b276aaf34a 100644 --- a/src/applications/phriction/editor/PhrictionTransactionEditor.php +++ b/src/applications/phriction/editor/PhrictionTransactionEditor.php @@ -13,6 +13,7 @@ final class PhrictionTransactionEditor private $skipAncestorCheck; private $contentVersion; private $processContentVersionError = true; + private $contentDiffURI; public function setDescription($description) { $this->description = $description; @@ -348,6 +349,20 @@ final class PhrictionTransactionEditor ->applyTransactions($this->moveAwayDocument, $move_away_xactions); } + // Compute the content diff URI for the publishing phase. + foreach ($xactions as $xaction) { + switch ($xaction->getTransactionType()) { + case PhrictionTransaction::TYPE_CONTENT: + $uri = id(new PhutilURI('/phriction/diff/'.$object->getID().'/')) + ->alter('l', $this->getOldContent()->getVersion()) + ->alter('r', $this->getNewContent()->getVersion()); + $this->contentDiffURI = (string)$uri; + break 2; + default: + break; + } + } + return $xactions; } @@ -403,24 +418,10 @@ final class PhrictionTransactionEditor $body->addTextSection( pht('DOCUMENT CONTENT'), $object->getContent()->getContent()); - } else { - - foreach ($xactions as $xaction) { - switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_CONTENT: - $diff_uri = id(new PhutilURI( - '/phriction/diff/'.$object->getID().'/')) - ->alter('l', $this->getOldContent()->getVersion()) - ->alter('r', $this->getNewContent()->getVersion()); - $body->addLinkSection( - pht('DOCUMENT DIFF'), - PhabricatorEnv::getProductionURI($diff_uri)); - break 2; - default: - break; - } - } - + } else if ($this->contentDiffURI) { + $body->addLinkSection( + pht('DOCUMENT DIFF'), + PhabricatorEnv::getProductionURI($this->contentDiffURI)); } $body->addLinkSection( @@ -775,24 +776,6 @@ final class PhrictionTransactionEditor ->setDocument($object); } - protected function didApplyHeraldRules( - PhabricatorLiskDAO $object, - HeraldAdapter $adapter, - HeraldTranscript $transcript) { - - $xactions = array(); - - $cc_phids = $adapter->getCcPHIDs(); - if ($cc_phids) { - $value = array_fuse($cc_phids); - $xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS) - ->setNewValue(array('+' => $value)); - } - - return $xactions; - } - private function buildNewContentTemplate( PhrictionDocument $document) { @@ -810,4 +793,15 @@ final class PhrictionTransactionEditor return $new_content; } + protected function getCustomWorkerState() { + return array( + 'contentDiffURI' => $this->contentDiffURI, + ); + } + + protected function loadCustomWorkerState(array $state) { + $this->contentDiffURI = idx($state, 'contentDiffURI'); + return $this; + } + } diff --git a/src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php b/src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php index 8b6d36edf6..72850bf94a 100644 --- a/src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php +++ b/src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php @@ -3,7 +3,6 @@ final class PhrictionDocumentHeraldAdapter extends HeraldAdapter { private $document; - private $ccPHIDs = array(); public function getAdapterApplicationClass() { return 'PhabricatorPhrictionApplication'; @@ -25,19 +24,11 @@ final class PhrictionDocumentHeraldAdapter extends HeraldAdapter { $this->document = $document; return $this; } + public function getDocument() { return $this->document; } - private function setCcPHIDs(array $cc_phids) { - $this->ccPHIDs = $cc_phids; - return $this; - } - - public function getCcPHIDs() { - return $this->ccPHIDs; - } - public function getAdapterContentName() { return pht('Phriction Documents'); } @@ -72,6 +63,7 @@ final class PhrictionDocumentHeraldAdapter extends HeraldAdapter { return array_merge( array( self::ACTION_ADD_CC, + self::ACTION_REMOVE_CC, self::ACTION_EMAIL, self::ACTION_NOTHING, ), @@ -80,6 +72,7 @@ final class PhrictionDocumentHeraldAdapter extends HeraldAdapter { return array_merge( array( self::ACTION_ADD_CC, + self::ACTION_REMOVE_CC, self::ACTION_EMAIL, self::ACTION_FLAG, self::ACTION_NOTHING, @@ -104,9 +97,6 @@ final class PhrictionDocumentHeraldAdapter extends HeraldAdapter { return $this->getDocument()->getContent()->getContent(); case self::FIELD_AUTHOR: return $this->getDocument()->getContent()->getAuthorPHID(); - case self::FIELD_CC: - return PhabricatorSubscribersQuery::loadSubscribersForPHID( - $this->getDocument()->getPHID()); case self::FIELD_PATH: return $this->getDocument()->getContent()->getSlug(); } @@ -119,28 +109,9 @@ final class PhrictionDocumentHeraldAdapter extends HeraldAdapter { $result = array(); foreach ($effects as $effect) { - $action = $effect->getAction(); - switch ($action) { - case self::ACTION_NOTHING: - $result[] = new HeraldApplyTranscript( - $effect, - true, - pht('Great success at doing nothing.')); - break; - case self::ACTION_ADD_CC: - foreach ($effect->getTarget() as $phid) { - $this->ccPHIDs[] = $phid; - } - $result[] = new HeraldApplyTranscript( - $effect, - true, - pht('Added address to cc list.')); - break; - default: - $result[] = $this->applyStandardEffect($effect); - break; - } + $result[] = $this->applyStandardEffect($effect); } + return $result; } diff --git a/src/applications/phriction/search/PhrictionSearchIndexer.php b/src/applications/phriction/search/PhrictionSearchIndexer.php index 7fdc54e7f9..28b7e34bbf 100644 --- a/src/applications/phriction/search/PhrictionSearchIndexer.php +++ b/src/applications/phriction/search/PhrictionSearchIndexer.php @@ -25,7 +25,7 @@ final class PhrictionSearchIndexer $doc->setDocumentModified($content->getDateModified()); $doc->addField( - PhabricatorSearchField::FIELD_BODY, + PhabricatorSearchDocumentFieldType::FIELD_BODY, $content->getContent()); $doc->addRelationship( diff --git a/src/applications/policy/__tests__/PhabricatorPolicyTestCase.php b/src/applications/policy/__tests__/PhabricatorPolicyTestCase.php index 327c8a2856..b2475cdee5 100644 --- a/src/applications/policy/__tests__/PhabricatorPolicyTestCase.php +++ b/src/applications/policy/__tests__/PhabricatorPolicyTestCase.php @@ -189,6 +189,102 @@ final class PhabricatorPolicyTestCase extends PhabricatorTestCase { } + /** + * Test that extended policies work. + */ + public function testExtendedPolicies() { + $object = $this->buildObject(PhabricatorPolicies::POLICY_USER) + ->setPHID('PHID-TEST-1'); + + $this->expectVisibility( + $object, + array( + 'public' => false, + 'user' => true, + 'admin' => true, + ), + pht('No Extended Policy')); + + // Add a restrictive extended policy. + $extended = $this->buildObject(PhabricatorPolicies::POLICY_ADMIN) + ->setPHID('PHID-TEST-2'); + $object->setExtendedPolicies( + array( + PhabricatorPolicyCapability::CAN_VIEW => array( + array($extended, PhabricatorPolicyCapability::CAN_VIEW), + ), + )); + + $this->expectVisibility( + $object, + array( + 'public' => false, + 'user' => false, + 'admin' => true, + ), + pht('With Extended Policy')); + + // Depend on a different capability. + $object->setExtendedPolicies( + array( + PhabricatorPolicyCapability::CAN_VIEW => array( + array($extended, PhabricatorPolicyCapability::CAN_EDIT), + ), + )); + + $extended->setCapabilities(array(PhabricatorPolicyCapability::CAN_EDIT)); + $extended->setPolicies( + array( + PhabricatorPolicyCapability::CAN_EDIT => + PhabricatorPolicies::POLICY_NOONE, + )); + + $this->expectVisibility( + $object, + array( + 'public' => false, + 'user' => false, + 'admin' => false, + ), + pht('With Extended Policy + Edit')); + } + + + /** + * Test that cyclic extended policies are arrested properly. + */ + public function testExtendedPolicyCycles() { + $object = $this->buildObject(PhabricatorPolicies::POLICY_USER) + ->setPHID('PHID-TEST-1'); + + $this->expectVisibility( + $object, + array( + 'public' => false, + 'user' => true, + 'admin' => true, + ), + pht('No Extended Policy')); + + // Set a self-referential extended policy on the object. This should + // make it fail all policy checks. + $object->setExtendedPolicies( + array( + PhabricatorPolicyCapability::CAN_VIEW => array( + array($object, PhabricatorPolicyCapability::CAN_VIEW), + ), + )); + + $this->expectVisibility( + $object, + array( + 'public' => false, + 'user' => false, + 'admin' => false, + ), + pht('Extended Policy with Cycle')); + } + /** * An omnipotent user should be able to see even objects with invalid * policies. @@ -274,6 +370,7 @@ final class PhabricatorPolicyTestCase extends PhabricatorTestCase { $query->setViewer($viewer); $caught = null; + $result = null; try { $result = $query->executeOne(); } catch (PhabricatorPolicyException $ex) { diff --git a/src/applications/policy/__tests__/PhabricatorPolicyTestObject.php b/src/applications/policy/__tests__/PhabricatorPolicyTestObject.php index 041b612757..60c77aea83 100644 --- a/src/applications/policy/__tests__/PhabricatorPolicyTestObject.php +++ b/src/applications/policy/__tests__/PhabricatorPolicyTestObject.php @@ -4,14 +4,23 @@ * Configurable test object for implementing Policy unit tests. */ final class PhabricatorPolicyTestObject - implements PhabricatorPolicyInterface { + implements + PhabricatorPolicyInterface, + PhabricatorExtendedPolicyInterface { - private $capabilities = array(); - private $policies = array(); - private $automaticCapabilities = array(); + private $phid; + private $capabilities = array(); + private $policies = array(); + private $automaticCapabilities = array(); + private $extendedPolicies = array(); + + public function setPHID($phid) { + $this->phid = $phid; + return $this; + } public function getPHID() { - return null; + return $this->phid; } public function getCapabilities() { @@ -46,4 +55,13 @@ final class PhabricatorPolicyTestObject return null; } + public function setExtendedPolicies(array $extended_policies) { + $this->extendedPolicies = $extended_policies; + return $this; + } + + public function getExtendedPolicy($capability, PhabricatorUser $viewer) { + return idx($this->extendedPolicies, $capability, array()); + } + } diff --git a/src/applications/policy/filter/PhabricatorPolicyFilter.php b/src/applications/policy/filter/PhabricatorPolicyFilter.php index 8d52ddc044..46cb9352f7 100644 --- a/src/applications/policy/filter/PhabricatorPolicyFilter.php +++ b/src/applications/policy/filter/PhabricatorPolicyFilter.php @@ -196,7 +196,6 @@ final class PhabricatorPolicyFilter { } foreach ($objects as $key => $object) { - $object_capabilities = $object->getCapabilities(); foreach ($capabilities as $capability) { if (!$this->checkCapability($object, $capability)) { // If we're missing any capability, move on to the next object. @@ -208,7 +207,218 @@ final class PhabricatorPolicyFilter { $filtered[$key] = $object; } - return $filtered; + // If we survied the primary checks, apply extended checks to objects + // with extended policies. + $results = array(); + $extended = array(); + foreach ($filtered as $key => $object) { + if ($object instanceof PhabricatorExtendedPolicyInterface) { + $extended[$key] = $object; + } else { + $results[$key] = $object; + } + } + + if ($extended) { + $results += $this->applyExtendedPolicyChecks($extended); + // Put results back in the original order. + $results = array_select_keys($results, array_keys($filtered)); + } + + return $results; + } + + private function applyExtendedPolicyChecks(array $extended_objects) { + // First, we're going to detect cycles and reject any objects which are + // part of a cycle. We don't want to loop forever if an object has a + // self-referential or nonsense policy. + + static $in_flight = array(); + + $all_phids = array(); + foreach ($extended_objects as $key => $object) { + $phid = $object->getPHID(); + if (isset($in_flight[$phid])) { + // TODO: This could be more user-friendly. + $this->rejectObject($extended_objects[$key], false, ''); + unset($extended_objects[$key]); + continue; + } + + // We might throw from rejectObject(), so we don't want to actually mark + // anything as in-flight until we survive this entire step. + $all_phids[$phid] = $phid; + } + + foreach ($all_phids as $phid) { + $in_flight[$phid] = true; + } + + $caught = null; + try { + $extended_objects = $this->executeExtendedPolicyChecks($extended_objects); + } catch (Exception $ex) { + $caught = $ex; + } + + foreach ($all_phids as $phid) { + unset($in_flight[$phid]); + } + + if ($caught) { + throw $caught; + } + + return $extended_objects; + } + + private function executeExtendedPolicyChecks(array $extended_objects) { + $viewer = $this->viewer; + $filter_capabilities = $this->capabilities; + + // Iterate over the objects we need to filter and pull all the nonempty + // policies into a flat, structured list. + $all_structs = array(); + foreach ($extended_objects as $key => $extended_object) { + foreach ($filter_capabilities as $extended_capability) { + $extended_policies = $extended_object->getExtendedPolicy( + $extended_capability, + $viewer); + if (!$extended_policies) { + continue; + } + + foreach ($extended_policies as $extended_policy) { + list($object, $capabilities) = $extended_policy; + + // Build a description of the capabilities we need to check. This + // will be something like `"view"`, or `"edit view"`, or possibly + // a longer string with custom capabilities. Later, group the objects + // up into groups which need the same capabilities tested. + $capabilities = (array)$capabilities; + $capabilities = array_fuse($capabilities); + ksort($capabilities); + $group = implode(' ', $capabilities); + + $struct = array( + 'key' => $key, + 'for' => $extended_capability, + 'object' => $object, + 'capabilities' => $capabilities, + 'group' => $group, + ); + + $all_structs[] = $struct; + } + } + } + + // Extract any bare PHIDs from the structs; we need to load these objects. + // These are objects which are required in order to perform an extended + // policy check but which the original viewer did not have permission to + // see (they presumably had other permissions which let them load the + // object in the first place). + $all_phids = array(); + foreach ($all_structs as $idx => $struct) { + $object = $struct['object']; + if (is_string($object)) { + $all_phids[$object] = $object; + } + } + + // If we have some bare PHIDs, we need to load the corresponding objects. + if ($all_phids) { + // We can pull these with the omnipotent user because we're immediately + // filtering them. + $ref_objects = id(new PhabricatorObjectQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs($all_phids) + ->execute(); + $ref_objects = mpull($ref_objects, null, 'getPHID'); + } else { + $ref_objects = array(); + } + + // Group the list of checks by the capabilities we need to check. + $groups = igroup($all_structs, 'group'); + foreach ($groups as $structs) { + $head = head($structs); + + // All of the items in each group are checking for the same capabilities. + $capabilities = $head['capabilities']; + + $key_map = array(); + $objects_in = array(); + foreach ($structs as $struct) { + $extended_key = $struct['key']; + if (empty($extended_objects[$key])) { + // If this object has already been rejected by an earlier filtering + // pass, we don't need to do any tests on it. + continue; + } + + $object = $struct['object']; + if (is_string($object)) { + // This is really a PHID, so look it up. + $object_phid = $object; + if (empty($ref_objects[$object_phid])) { + // We weren't able to load the corresponding object, so just + // reject this result outright. + + $reject = $extended_objects[$key]; + unset($extended_objects[$key]); + + // TODO: This could be friendlier. + $this->rejectObject($reject, false, ''); + continue; + } + $object = $ref_objects[$object_phid]; + } + + $phid = $object->getPHID(); + + $key_map[$phid][] = $extended_key; + $objects_in[$phid] = $object; + } + + if ($objects_in) { + $objects_out = id(new PhabricatorPolicyFilter()) + ->setViewer($viewer) + ->requireCapabilities($capabilities) + ->apply($objects_in); + $objects_out = mpull($objects_out, null, 'getPHID'); + } else { + $objects_out = array(); + } + + // If any objects were removed by filtering, we're going to reject all + // of the original objects which needed them. + foreach ($objects_in as $phid => $object_in) { + if (isset($objects_out[$phid])) { + // This object survived filtering, so we don't need to throw any + // results away. + continue; + } + + foreach ($key_map[$phid] as $extended_key) { + if (empty($extended_objects[$extended_key])) { + // We've already rejected this object, so we don't need to reject + // it again. + continue; + } + + $reject = $extended_objects[$extended_key]; + unset($extended_objects[$extended_key]); + + // TODO: This isn't as user-friendly as it could be. It's possible + // that we're rejecting this object for multiple capability/policy + // failures, though. + $this->rejectObject($reject, false, ''); + } + } + } + + return $extended_objects; } private function checkCapability( @@ -243,6 +453,14 @@ final class PhabricatorPolicyFilter { return true; } + if ($object instanceof PhabricatorSpacesInterface) { + $space_phid = $object->getSpacePHID(); + if (!$this->canViewerSeeObjectsInSpace($viewer, $space_phid)) { + $this->rejectObjectFromSpace($object, $space_phid); + return false; + } + } + if ($object->hasAutomaticCapability($capability, $viewer)) { return true; } @@ -337,35 +555,17 @@ final class PhabricatorPolicyFilter { $details = array_filter(array_merge(array($more), (array)$exceptions)); - // NOTE: Not every type of policy object has a real PHID; just load an - // empty handle if a real PHID isn't available. - $phid = nonempty($object->getPHID(), PhabricatorPHIDConstants::PHID_VOID); - - $handle = id(new PhabricatorHandleQuery()) - ->setViewer($this->viewer) - ->withPHIDs(array($phid)) - ->executeOne(); - - $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); - if ($is_serious) { - $title = pht( - 'Access Denied: %s', - $handle->getObjectName()); - } else { - $title = pht( - 'You Shall Not Pass: %s', - $handle->getObjectName()); - } + $access_denied = $this->renderAccessDenied($object); $full_message = pht( '[%s] (%s) %s // %s', - $title, + $access_denied, $capability_name, $rejection, implode(' ', $details)); $exception = id(new PhabricatorPolicyException($full_message)) - ->setTitle($title) + ->setTitle($access_denied) ->setRejection($rejection) ->setCapabilityName($capability_name) ->setMoreInfo($details); @@ -458,4 +658,90 @@ final class PhabricatorPolicyFilter { } } + private function renderAccessDenied(PhabricatorPolicyInterface $object) { + // NOTE: Not every type of policy object has a real PHID; just load an + // empty handle if a real PHID isn't available. + $phid = nonempty($object->getPHID(), PhabricatorPHIDConstants::PHID_VOID); + + $handle = id(new PhabricatorHandleQuery()) + ->setViewer($this->viewer) + ->withPHIDs(array($phid)) + ->executeOne(); + + $object_name = $handle->getObjectName(); + + $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); + if ($is_serious) { + $access_denied = pht( + 'Access Denied: %s', + $object_name); + } else { + $access_denied = pht( + 'You Shall Not Pass: %s', + $object_name); + } + + return $access_denied; + } + + + private function canViewerSeeObjectsInSpace( + PhabricatorUser $viewer, + $space_phid) { + + $spaces = PhabricatorSpacesNamespaceQuery::getAllSpaces(); + + // If there are no spaces, everything exists in an implicit default space + // with no policy controls. This is the default state. + if (!$spaces) { + if ($space_phid !== null) { + return false; + } else { + return true; + } + } + + if ($space_phid === null) { + $space = PhabricatorSpacesNamespaceQuery::getDefaultSpace(); + } else { + $space = idx($spaces, $space_phid); + } + + if (!$space) { + return false; + } + + // This may be more involved later, but for now being able to see the + // space is equivalent to being able to see everything in it. + return self::hasCapability( + $viewer, + $space, + PhabricatorPolicyCapability::CAN_VIEW); + } + + private function rejectObjectFromSpace( + PhabricatorPolicyInterface $object, + $space_phid) { + + if (!$this->raisePolicyExceptions) { + return; + } + + if ($this->viewer->isOmnipotent()) { + return; + } + + $access_denied = $this->renderAccessDenied($object); + + $rejection = pht( + 'This object is in a space you do not have permission to access.'); + $full_message = pht('[%s] %s', $access_denied, $rejection); + + $exception = id(new PhabricatorPolicyException($full_message)) + ->setTitle($access_denied) + ->setRejection($rejection); + + throw $exception; + } + } diff --git a/src/applications/policy/interface/PhabricatorExtendedPolicyInterface.php b/src/applications/policy/interface/PhabricatorExtendedPolicyInterface.php new file mode 100644 index 0000000000..deeaecdbef --- /dev/null +++ b/src/applications/policy/interface/PhabricatorExtendedPolicyInterface.php @@ -0,0 +1,88 @@ +> List of extended policies. + */ + public function getExtendedPolicy($capability, PhabricatorUser $viewer); + +} + +// TEMPLATE IMPLEMENTATION ///////////////////////////////////////////////////// + +/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */ +/* + + public function getExtendedPolicy($capability, PhabricatorUser $viewer) { + $extended = array(); + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + // ... + break; + } + return $extended; + } + +*/ diff --git a/src/applications/ponder/search/PonderSearchIndexer.php b/src/applications/ponder/search/PonderSearchIndexer.php index a762ade576..1b9e6d3f84 100644 --- a/src/applications/ponder/search/PonderSearchIndexer.php +++ b/src/applications/ponder/search/PonderSearchIndexer.php @@ -16,7 +16,7 @@ final class PonderSearchIndexer ->setDocumentModified($question->getDateModified()); $doc->addField( - PhabricatorSearchField::FIELD_BODY, + PhabricatorSearchDocumentFieldType::FIELD_BODY, $question->getContent()); $doc->addRelationship( @@ -32,7 +32,7 @@ final class PonderSearchIndexer foreach ($answers as $answer) { if (strlen($answer->getContent())) { $doc->addField( - PhabricatorSearchField::FIELD_COMMENT, + PhabricatorSearchDocumentFieldType::FIELD_COMMENT, $answer->getContent()); } } diff --git a/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php b/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php index 47be6aa3f9..a209fd9a40 100644 --- a/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php +++ b/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php @@ -14,6 +14,9 @@ final class ProjectCreateConduitAPIMethod extends ProjectConduitAPIMethod { return array( 'name' => 'required string', 'members' => 'optional list', + 'icon' => 'optional string', + 'color' => 'optional string', + 'tags' => 'optional list', ); } @@ -37,6 +40,24 @@ final class ProjectCreateConduitAPIMethod extends ProjectConduitAPIMethod { ->setTransactionType($type_name) ->setNewValue($request->getValue('name')); + if ($request->getValue('icon')) { + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_ICON) + ->setNewValue($request->getValue('icon')); + } + + if ($request->getValue('color')) { + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_COLOR) + ->setNewValue($request->getValue('color')); + } + + if ($request->getValue('tags')) { + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setNewValue($request->getValue('tags')); + } + $xactions[] = id(new PhabricatorProjectTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setMetadataValue( diff --git a/src/applications/project/conduit/ProjectQueryConduitAPIMethod.php b/src/applications/project/conduit/ProjectQueryConduitAPIMethod.php index f65578e349..3d059d064d 100644 --- a/src/applications/project/conduit/ProjectQueryConduitAPIMethod.php +++ b/src/applications/project/conduit/ProjectQueryConduitAPIMethod.php @@ -27,6 +27,8 @@ final class ProjectQueryConduitAPIMethod extends ProjectConduitAPIMethod { 'names' => 'optional list', 'phids' => 'optional list', 'slugs' => 'optional list', + 'icons' => 'optional list', + 'colors' => 'optional list', 'status' => 'optional '.$status_const, 'members' => 'optional list', @@ -71,6 +73,22 @@ final class ProjectQueryConduitAPIMethod extends ProjectConduitAPIMethod { $query->withSlugs($slugs); } + $request->getValue('icons'); + if ($request->getValue('icons')) { + $icons = array(); + // the internal 'fa-' prefix is a detail hidden from api clients + // but needs to pre prepended to the values in the icons array: + foreach ($request->getValue('icons') as $value) { + $icons[] = 'fa-'.$value; + } + $query->withIcons($icons); + } + + $colors = $request->getValue('colors'); + if ($colors) { + $query->withColors($colors); + } + $members = $request->getValue('members'); if ($members) { $query->withMemberPHIDs($members); diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index 6dedb4eae2..8fd1810f33 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -163,7 +163,7 @@ final class PhabricatorProjectBoardViewController PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, PhabricatorQueryConstraint::OPERATOR_AND, array($project->getPHID())) - ->setOrderBy(ManiphestTaskQuery::ORDER_PRIORITY) + ->setOrder(ManiphestTaskQuery::ORDER_PRIORITY) ->setViewer($viewer) ->execute(); $tasks = mpull($tasks, null, 'getPHID'); diff --git a/src/applications/project/customfield/PhabricatorProjectDescriptionField.php b/src/applications/project/customfield/PhabricatorProjectDescriptionField.php index 1da7ce1305..0c18398293 100644 --- a/src/applications/project/customfield/PhabricatorProjectDescriptionField.php +++ b/src/applications/project/customfield/PhabricatorProjectDescriptionField.php @@ -11,7 +11,7 @@ final class PhabricatorProjectDescriptionField 'name' => pht('Description'), 'type' => 'remarkup', 'description' => pht('Short project description.'), - 'fulltext' => PhabricatorSearchField::FIELD_BODY, + 'fulltext' => PhabricatorSearchDocumentFieldType::FIELD_BODY, ), )); } diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index d449b62801..4193c2e4c4 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -397,14 +397,13 @@ final class PhabricatorProjectTransactionEditor return parent::requireCapabilities($object, $xaction); } - protected function loadEdges( - PhabricatorLiskDAO $object, - array $xactions) { - + protected function willPublish(PhabricatorLiskDAO $object, array $xactions) { $member_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( $object->getPHID(), PhabricatorProjectProjectHasMemberEdgeType::EDGECONST); $object->attachMemberPHIDs($member_phids); + + return $object; } protected function shouldSendMail( diff --git a/src/applications/project/editor/__tests__/PhabricatorProjectEditorTestCase.php b/src/applications/project/editor/__tests__/PhabricatorProjectEditorTestCase.php index 876ed9d0d7..4d19812fe3 100644 --- a/src/applications/project/editor/__tests__/PhabricatorProjectEditorTestCase.php +++ b/src/applications/project/editor/__tests__/PhabricatorProjectEditorTestCase.php @@ -227,7 +227,7 @@ final class PhabricatorProjectEditorTestCase extends PhabricatorTestCase { private function createProject(PhabricatorUser $user) { $project = PhabricatorProject::initializeNewProject($user); - $project->setName('Test Project '.mt_rand()); + $project->setName(pht('Test Project %d', mt_rand())); $project->save(); return $project; @@ -247,7 +247,7 @@ final class PhabricatorProjectEditorTestCase extends PhabricatorTestCase { $user = new PhabricatorUser(); $user->setUsername('unittestuser'.$rand); - $user->setRealName('Unit Test User '.$rand); + $user->setRealName(pht('Unit Test User %d', $rand)); return $user; } diff --git a/src/applications/project/query/PhabricatorProjectQuery.php b/src/applications/project/query/PhabricatorProjectQuery.php index a393130221..bbb0857315 100644 --- a/src/applications/project/query/PhabricatorProjectQuery.php +++ b/src/applications/project/query/PhabricatorProjectQuery.php @@ -95,12 +95,25 @@ final class PhabricatorProjectQuery return $this; } + public function newResultObject() { + return new PhabricatorProject(); + } + protected function getDefaultOrderVector() { return array('name'); } - public function getOrderableColumns() { + public function getBuiltinOrders() { return array( + 'name' => array( + 'vector' => array('name'), + 'name' => pht('Name'), + ), + ) + parent::getBuiltinOrders(); + } + + public function getOrderableColumns() { + return parent::getOrderableColumns() + array( 'name' => array( 'table' => $this->getPrimaryTableAlias(), 'column' => 'name', @@ -120,29 +133,7 @@ final class PhabricatorProjectQuery protected function loadPage() { $table = new PhabricatorProject(); - $conn_r = $table->establishConnection('r'); - - // NOTE: Because visibility checks for projects depend on whether or not - // the user is a project member, we always load their membership. If we're - // loading all members anyway we can piggyback on that; otherwise we - // do an explicit join. - - $select_clause = ''; - if (!$this->needMembers) { - $select_clause = ', vm.dst viewerIsMember'; - } - - $data = queryfx_all( - $conn_r, - 'SELECT p.* %Q FROM %T p %Q %Q %Q %Q %Q', - $select_clause, - $table->getTableName(), - $this->buildJoinClause($conn_r), - $this->buildWhereClause($conn_r), - $this->buildGroupClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - + $data = $this->loadStandardPageRows($table); $projects = $table->loadAllFromArray($data); if ($projects) { @@ -240,8 +231,22 @@ final class PhabricatorProjectQuery return $projects; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildSelectClauseParts(AphrontDatabaseConnection $conn) { + $select = parent::buildSelectClauseParts($conn); + + // NOTE: Because visibility checks for projects depend on whether or not + // the user is a project member, we always load their membership. If we're + // loading all members anyway we can piggyback on that; otherwise we + // do an explicit join. + if (!$this->needMembers) { + $select[] = 'vm.dst viewerIsMember'; + } + + return $select; + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); if ($this->status != self::STATUS_ANY) { switch ($this->status) { @@ -264,86 +269,83 @@ final class PhabricatorProjectQuery $this->status)); } $where[] = qsprintf( - $conn_r, + $conn, 'status IN (%Ld)', $filter); } if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } if ($this->memberPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'e.dst IN (%Ls)', $this->memberPHIDs); } if ($this->slugs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'slug.slug IN (%Ls)', $this->slugs); } if ($this->phrictionSlugs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'phrictionSlug IN (%Ls)', $this->phrictionSlugs); } if ($this->names !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'name IN (%Ls)', $this->names); } if ($this->icons !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'icon IN (%Ls)', $this->icons); } if ($this->colors !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'color IN (%Ls)', $this->colors); } - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); + return $where; } - protected function buildGroupClause(AphrontDatabaseConnection $conn_r) { + protected function shouldGroupQueryResultRows() { if ($this->memberPHIDs || $this->nameTokens) { - return 'GROUP BY p.id'; - } else { - return $this->buildApplicationSearchGroupClause($conn_r); + return true; } + return parent::shouldGroupQueryResultRows(); } - protected function buildJoinClause(AphrontDatabaseConnection $conn_r) { - $joins = array(); + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $joins = parent::buildJoinClauseParts($conn); if (!$this->needMembers !== null) { $joins[] = qsprintf( - $conn_r, + $conn, 'LEFT JOIN %T vm ON vm.src = p.phid AND vm.type = %d AND vm.dst = %s', PhabricatorEdgeConfig::TABLE_NAME_EDGE, PhabricatorProjectProjectHasMemberEdgeType::EDGECONST, @@ -352,7 +354,7 @@ final class PhabricatorProjectQuery if ($this->memberPHIDs !== null) { $joins[] = qsprintf( - $conn_r, + $conn, 'JOIN %T e ON e.src = p.phid AND e.type = %d', PhabricatorEdgeConfig::TABLE_NAME_EDGE, PhabricatorProjectProjectHasMemberEdgeType::EDGECONST); @@ -360,7 +362,7 @@ final class PhabricatorProjectQuery if ($this->slugs !== null) { $joins[] = qsprintf( - $conn_r, + $conn, 'JOIN %T slug on slug.projectPHID = p.phid', id(new PhabricatorProjectSlug())->getTableName()); } @@ -369,7 +371,7 @@ final class PhabricatorProjectQuery foreach ($this->nameTokens as $key => $token) { $token_table = 'token_'.$key; $joins[] = qsprintf( - $conn_r, + $conn, 'JOIN %T %T ON %T.projectID = p.id AND %T.token LIKE %>', PhabricatorProject::TABLE_DATASOURCE_TOKEN, $token_table, @@ -379,9 +381,7 @@ final class PhabricatorProjectQuery } } - $joins[] = $this->buildApplicationSearchJoinClause($conn_r); - - return implode(' ', $joins); + return $joins; } public function getQueryApplicationClass() { diff --git a/src/applications/project/query/PhabricatorProjectSearchEngine.php b/src/applications/project/query/PhabricatorProjectSearchEngine.php index 2b44d579ac..07ddb512d7 100644 --- a/src/applications/project/query/PhabricatorProjectSearchEngine.php +++ b/src/applications/project/query/PhabricatorProjectSearchEngine.php @@ -11,133 +11,66 @@ final class PhabricatorProjectSearchEngine return 'PhabricatorProjectApplication'; } - public function getCustomFieldObject() { - return new PhabricatorProject(); - } - - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - - $saved->setParameter( - 'memberPHIDs', - $this->readUsersFromRequest($request, 'members')); - - $saved->setParameter('status', $request->getStr('status')); - $saved->setParameter('name', $request->getStr('name')); - - $saved->setParameter( - 'icons', - $this->readListFromRequest($request, 'icons')); - - $saved->setParameter( - 'colors', - $this->readListFromRequest($request, 'colors')); - - $this->readCustomFieldsFromRequest($request, $saved); - - return $saved; - } - - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new PhabricatorProjectQuery()) + public function newQuery() { + return id(new PhabricatorProjectQuery()) ->needImages(true); + } - $member_phids = $saved->getParameter('memberPHIDs', array()); - if ($member_phids && is_array($member_phids)) { - $query->withMemberPHIDs($member_phids); - } + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorSearchTextField()) + ->setLabel(pht('Name')) + ->setKey('name'), + id(new PhabricatorSearchUsersField()) + ->setLabel(pht('Members')) + ->setKey('memberPHIDs') + ->setAliases(array('member', 'members')), + id(new PhabricatorSearchSelectField()) + ->setLabel(pht('Status')) + ->setKey('status') + ->setOptions($this->getStatusOptions()), + id(new PhabricatorSearchCheckboxesField()) + ->setLabel(pht('Icons')) + ->setKey('icons') + ->setOptions($this->getIconOptions()), + id(new PhabricatorSearchCheckboxesField()) + ->setLabel(pht('Colors')) + ->setKey('colors') + ->setOptions($this->getColorOptions()), + ); + } - $status = $saved->getParameter('status'); - $status = idx($this->getStatusValues(), $status); - if ($status) { - $query->withStatus($status); - } - $name = $saved->getParameter('name'); - if (strlen($name)) { - $tokens = PhabricatorTypeaheadDatasource::tokenizeString($name); +protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); + + if (strlen($map['name'])) { + $tokens = PhabricatorTypeaheadDatasource::tokenizeString($map['name']); $query->withNameTokens($tokens); } - $icons = $saved->getParameter('icons'); - if ($icons) { - $query->withIcons($icons); + if ($map['memberPHIDs']) { + $query->withMemberPHIDs($map['memberPHIDs']); } - $colors = $saved->getParameter('colors'); - if ($colors) { - $query->withColors($colors); + if ($map['status']) { + $status = idx($this->getStatusValues(), $map['status']); + if ($status) { + $query->withStatus($status); + } } - $this->applyCustomFieldsToQuery($query, $saved); + if ($map['icons']) { + $query->withIcons($map['icons']); + } + + if ($map['colors']) { + $query->withColors($map['colors']); + } return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved) { - - $member_phids = $saved->getParameter('memberPHIDs', array()); - - $status = $saved->getParameter('status'); - $name_match = $saved->getParameter('name'); - - $icons = array_fuse($saved->getParameter('icons', array())); - $colors = array_fuse($saved->getParameter('colors', array())); - - $icon_control = id(new AphrontFormCheckboxControl()) - ->setLabel(pht('Icons')); - foreach (PhabricatorProjectIcon::getIconMap() as $icon => $name) { - $image = id(new PHUIIconView()) - ->setIconFont($icon); - - $icon_control->addCheckbox( - 'icons[]', - $icon, - array($image, ' ', $name), - isset($icons[$icon])); - } - - $color_control = id(new AphrontFormCheckboxControl()) - ->setLabel(pht('Colors')); - foreach (PhabricatorProjectIcon::getColorMap() as $color => $name) { - $tag = id(new PHUITagView()) - ->setType(PHUITagView::TYPE_SHADE) - ->setShade($color) - ->setName($name); - - $color_control->addCheckbox( - 'colors[]', - $color, - $tag, - isset($colors[$color])); - } - - $form - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('name') - ->setLabel(pht('Name')) - ->setValue($name_match)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorPeopleDatasource()) - ->setName('members') - ->setLabel(pht('Members')) - ->setValue($member_phids)) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Status')) - ->setName('status') - ->setOptions($this->getStatusOptions()) - ->setValue($status)) - ->appendChild($icon_control) - ->appendChild($color_control); - - $this->appendCustomFieldsToForm($form, $saved); - } - protected function getURI($path) { return '/project/'.$path; } @@ -192,14 +125,36 @@ final class PhabricatorProjectSearchEngine ); } - private function getColorValues() {} + private function getIconOptions() { + $options = array(); - private function getIconValues() {} + foreach (PhabricatorProjectIcon::getIconMap() as $icon => $name) { + $options[$icon] = array( + id(new PHUIIconView()) + ->setIconFont($icon), + ' ', + $name, + ); + } - protected function getRequiredHandlePHIDsForResultList( - array $projects, - PhabricatorSavedQuery $query) { - return mpull($projects, 'getPHID'); + return $options; + } + + private function getColorOptions() { + $options = array(); + + foreach (PhabricatorProjectIcon::getColorMap() as $color => $name) { + $options[$color] = array( + id(new PHUITagView()) + ->setType(PHUITagView::TYPE_SHADE) + ->setShade($color) + ->setName($name), + ' ', + $name, + ); + } + + return $options; } protected function renderResultList( @@ -208,6 +163,7 @@ final class PhabricatorProjectSearchEngine array $handles) { assert_instances_of($projects, 'PhabricatorProject'); $viewer = $this->requireViewer(); + $handles = $viewer->loadHandles(mpull($projects, 'getPHID')); $list = new PHUIObjectItemListView(); $list->setUser($viewer); diff --git a/src/applications/project/typeahead/PhabricatorProjectDatasource.php b/src/applications/project/typeahead/PhabricatorProjectDatasource.php index d38f6fc5b3..bb8f02cfd4 100644 --- a/src/applications/project/typeahead/PhabricatorProjectDatasource.php +++ b/src/applications/project/typeahead/PhabricatorProjectDatasource.php @@ -64,7 +64,7 @@ final class PhabricatorProjectDatasource $proj_result = id(new PhabricatorTypeaheadResult()) ->setName($all_strings) ->setDisplayName($proj->getName()) - ->setDisplayType('Project') + ->setDisplayType(pht('Project')) ->setURI('/tag/'.$proj->getPrimarySlug().'/') ->setPHID($proj->getPHID()) ->setIcon($proj->getIcon()) diff --git a/src/applications/releeph/controller/request/ReleephRequestEditController.php b/src/applications/releeph/controller/request/ReleephRequestEditController.php index 8888a01070..279fee4bd6 100644 --- a/src/applications/releeph/controller/request/ReleephRequestEditController.php +++ b/src/applications/releeph/controller/request/ReleephRequestEditController.php @@ -250,7 +250,7 @@ final class ReleephRequestEditController extends ReleephBranchController { ->addHiddenInput('requestIdentifierRaw', 'D'.$diff_rev_id) ->appendChild( id(new AphrontFormStaticControl()) - ->setLabel('Diff') + ->setLabel(pht('Diff')) ->setValue($title)); } else { $origin = $branch->getURI(); diff --git a/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php b/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php index 33ae0acc96..3db133a3e0 100644 --- a/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php +++ b/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php @@ -51,7 +51,7 @@ final class DifferentialReleephRequestFieldSpecification { } public function renderLabelForRevisionView() { - return 'Releeph'; + return pht('Releeph'); } public function getRequiredHandlePHIDs() { @@ -246,7 +246,7 @@ final class DifferentialReleephRequestFieldSpecification { } public function renderLabelForCommitMessage() { - return 'Releeph'; + return pht('Releeph'); } public function shouldAppearOnCommitMessageTemplate() { diff --git a/src/applications/releeph/field/specification/ReleephAuthorFieldSpecification.php b/src/applications/releeph/field/specification/ReleephAuthorFieldSpecification.php index b917b63ce6..577280fddb 100644 --- a/src/applications/releeph/field/specification/ReleephAuthorFieldSpecification.php +++ b/src/applications/releeph/field/specification/ReleephAuthorFieldSpecification.php @@ -8,7 +8,7 @@ final class ReleephAuthorFieldSpecification } public function getName() { - return 'Author'; + return pht('Author'); } public function getRequiredHandlePHIDsForPropertyView() { diff --git a/src/applications/releeph/field/specification/ReleephBranchCommitFieldSpecification.php b/src/applications/releeph/field/specification/ReleephBranchCommitFieldSpecification.php index ea9c334bf9..74f747238d 100644 --- a/src/applications/releeph/field/specification/ReleephBranchCommitFieldSpecification.php +++ b/src/applications/releeph/field/specification/ReleephBranchCommitFieldSpecification.php @@ -8,7 +8,7 @@ final class ReleephBranchCommitFieldSpecification } public function getName() { - return 'Commit'; + return pht('Commit'); } public function getRequiredHandlePHIDsForPropertyView() { diff --git a/src/applications/releeph/field/specification/ReleephDiffMessageFieldSpecification.php b/src/applications/releeph/field/specification/ReleephDiffMessageFieldSpecification.php index 98c578361c..cde4ab95dd 100644 --- a/src/applications/releeph/field/specification/ReleephDiffMessageFieldSpecification.php +++ b/src/applications/releeph/field/specification/ReleephDiffMessageFieldSpecification.php @@ -8,7 +8,7 @@ final class ReleephDiffMessageFieldSpecification } public function getName() { - return 'Message'; + return pht('Message'); } public function getStyleForPropertyView() { diff --git a/src/applications/releeph/field/specification/ReleephIntentFieldSpecification.php b/src/applications/releeph/field/specification/ReleephIntentFieldSpecification.php index 4d199f26cc..833dce0b39 100644 --- a/src/applications/releeph/field/specification/ReleephIntentFieldSpecification.php +++ b/src/applications/releeph/field/specification/ReleephIntentFieldSpecification.php @@ -8,7 +8,7 @@ final class ReleephIntentFieldSpecification } public function getName() { - return 'Intent'; + return pht('Intent'); } public function getRequiredHandlePHIDsForPropertyView() { diff --git a/src/applications/releeph/field/specification/ReleephLevelFieldSpecification.php b/src/applications/releeph/field/specification/ReleephLevelFieldSpecification.php index a686eb15c4..7306067dc4 100644 --- a/src/applications/releeph/field/specification/ReleephLevelFieldSpecification.php +++ b/src/applications/releeph/field/specification/ReleephLevelFieldSpecification.php @@ -37,7 +37,7 @@ abstract class ReleephLevelFieldSpecification } $control = id(new AphrontFormRadioButtonControl()) - ->setLabel('Level') + ->setLabel(pht('Level')) ->setName($control_name) ->setValue($level); @@ -75,7 +75,7 @@ abstract class ReleephLevelFieldSpecification public function validate($value) { if ($value === null) { - $this->error = 'Required'; + $this->error = pht('Required'); $label = $this->getName(); throw new ReleephFieldParseException( $this, diff --git a/src/applications/releeph/field/specification/ReleephOriginalCommitFieldSpecification.php b/src/applications/releeph/field/specification/ReleephOriginalCommitFieldSpecification.php index 0d34c34eb7..57c4209ca7 100644 --- a/src/applications/releeph/field/specification/ReleephOriginalCommitFieldSpecification.php +++ b/src/applications/releeph/field/specification/ReleephOriginalCommitFieldSpecification.php @@ -8,7 +8,7 @@ final class ReleephOriginalCommitFieldSpecification } public function getName() { - return 'Commit'; + return pht('Commit'); } public function getRequiredHandlePHIDsForPropertyView() { diff --git a/src/applications/releeph/field/specification/ReleephReasonFieldSpecification.php b/src/applications/releeph/field/specification/ReleephReasonFieldSpecification.php index 831e1d60a3..023ea081a1 100644 --- a/src/applications/releeph/field/specification/ReleephReasonFieldSpecification.php +++ b/src/applications/releeph/field/specification/ReleephReasonFieldSpecification.php @@ -44,7 +44,7 @@ final class ReleephReasonFieldSpecification public function validate($reason) { if (!$reason) { - $this->error = 'Required'; + $this->error = pht('Required'); throw new ReleephFieldParseException( $this, pht('You must give a reason for your request.')); @@ -53,8 +53,8 @@ final class ReleephReasonFieldSpecification public function renderHelpForArcanist() { $text = pht( - "Fully explain why you are requesting this code be included ". - "in the next release.\n"); + 'Fully explain why you are requesting this code be included '. + 'in the next release.')."\n"; return phutil_console_wrap($text, 8); } diff --git a/src/applications/releeph/field/specification/ReleephSeverityFieldSpecification.php b/src/applications/releeph/field/specification/ReleephSeverityFieldSpecification.php index 18beb35a10..c73a17e149 100644 --- a/src/applications/releeph/field/specification/ReleephSeverityFieldSpecification.php +++ b/src/applications/releeph/field/specification/ReleephSeverityFieldSpecification.php @@ -11,7 +11,7 @@ final class ReleephSeverityFieldSpecification } public function getName() { - return 'Severity'; + return pht('Severity'); } public function getStorageKey() { diff --git a/src/applications/releeph/field/specification/ReleephSummaryFieldSpecification.php b/src/applications/releeph/field/specification/ReleephSummaryFieldSpecification.php index 84767bcf6b..2341b0d2b5 100644 --- a/src/applications/releeph/field/specification/ReleephSummaryFieldSpecification.php +++ b/src/applications/releeph/field/specification/ReleephSummaryFieldSpecification.php @@ -14,7 +14,7 @@ final class ReleephSummaryFieldSpecification } public function getName() { - return 'Summary'; + return pht('Summary'); } public function getStorageKey() { @@ -25,7 +25,7 @@ final class ReleephSummaryFieldSpecification public function renderEditControl(array $handles) { return id(new AphrontFormTextControl()) - ->setLabel('Summary') + ->setLabel(pht('Summary')) ->setName('summary') ->setError($this->error) ->setValue($this->getValue()) @@ -34,7 +34,7 @@ final class ReleephSummaryFieldSpecification public function renderHelpForArcanist() { $text = pht( - "A one-line title summarizing this request. ". + 'A one-line title summarizing this request. '. 'Leave blank to use the original commit title.')."\n"; return phutil_console_wrap($text, 8); } diff --git a/src/applications/releeph/query/ReleephRequestSearchEngine.php b/src/applications/releeph/query/ReleephRequestSearchEngine.php index 07e5b06dfb..1a07ec478d 100644 --- a/src/applications/releeph/query/ReleephRequestSearchEngine.php +++ b/src/applications/releeph/query/ReleephRequestSearchEngine.php @@ -157,11 +157,11 @@ final class ReleephRequestSearchEngine if (ReleephDefaultFieldSelector::isFacebook()) { return array( '' => pht('(All Severities)'), - 11 => 'HOTFIX', - 12 => 'PIGGYBACK', - 13 => 'RELEASE', - 14 => 'DAILY', - 15 => 'PARKING', + 11 => pht('HOTFIX'), + 12 => pht('PIGGYBACK'), + 13 => pht('RELEASE'), + 14 => pht('DAILY'), + 15 => pht('PARKING'), ); } else { return array( diff --git a/src/applications/releeph/storage/ReleephProject.php b/src/applications/releeph/storage/ReleephProject.php index 4dd91b38ab..4bb4f6cd15 100644 --- a/src/applications/releeph/storage/ReleephProject.php +++ b/src/applications/releeph/storage/ReleephProject.php @@ -23,7 +23,6 @@ final class ReleephProject extends ReleephDAO protected $details = array(); private $repository = self::ATTACHABLE; - private $arcanistProject = self::ATTACHABLE; protected function getConfiguration() { return array( @@ -68,16 +67,6 @@ final class ReleephProject extends ReleephDAO return $this; } - public function getArcanistProject() { - return $this->assertAttached($this->arcanistProject); - } - - public function attachArcanistProject( - PhabricatorRepositoryArcanistProject $arcanist_project = null) { - $this->arcanistProject = $arcanist_project; - return $this; - } - public function getPushers() { return $this->getDetail('pushers', array()); } diff --git a/src/applications/repository/application/PhabricatorRepositoriesApplication.php b/src/applications/repository/application/PhabricatorRepositoriesApplication.php deleted file mode 100644 index f740d05dcd..0000000000 --- a/src/applications/repository/application/PhabricatorRepositoriesApplication.php +++ /dev/null @@ -1,37 +0,0 @@ - array( - '' => 'PhabricatorRepositoryListController', - ), - ); - } - -} diff --git a/src/applications/repository/constants/PhabricatorRepositoryType.php b/src/applications/repository/constants/PhabricatorRepositoryType.php index 78e3a7e8ec..a99c76d889 100644 --- a/src/applications/repository/constants/PhabricatorRepositoryType.php +++ b/src/applications/repository/constants/PhabricatorRepositoryType.php @@ -18,7 +18,7 @@ final class PhabricatorRepositoryType { public static function getNameForRepositoryType($type) { $map = self::getAllRepositoryTypes(); - return idx($map, $type, 'Unknown'); + return idx($map, $type, pht('Unknown')); } } diff --git a/src/applications/repository/controller/PhabricatorRepositoryController.php b/src/applications/repository/controller/PhabricatorRepositoryController.php deleted file mode 100644 index 9f1192520d..0000000000 --- a/src/applications/repository/controller/PhabricatorRepositoryController.php +++ /dev/null @@ -1,15 +0,0 @@ -getRequest(); - $user = $request->getUser(); - $is_admin = $user->getIsAdmin(); - - $repos = id(new PhabricatorRepositoryQuery()) - ->setViewer($user) - ->execute(); - $repos = msort($repos, 'getName'); - - $rows = array(); - foreach ($repos as $repo) { - - if ($repo->isTracked()) { - $diffusion_link = phutil_tag( - 'a', - array( - 'href' => '/diffusion/'.$repo->getCallsign().'/', - ), - pht('View in Diffusion')); - } else { - $diffusion_link = phutil_tag('em', array(), pht('Not Tracked')); - } - - $rows[] = array( - $repo->getCallsign(), - $repo->getName(), - PhabricatorRepositoryType::getNameForRepositoryType( - $repo->getVersionControlSystem()), - $diffusion_link, - phutil_tag( - 'a', - array( - 'class' => 'button small grey', - 'href' => '/diffusion/'.$repo->getCallsign().'/edit/', - ), - pht('Edit')), - ); - } - - $table = new AphrontTableView($rows); - $table->setNoDataString(pht('No Repositories')); - $table->setHeaders( - array( - pht('Callsign'), - pht('Repository'), - pht('Type'), - pht('Diffusion'), - '', - )); - $table->setColumnClasses( - array( - null, - 'wide', - null, - null, - 'action', - )); - - $table->setColumnVisibility( - array( - true, - true, - true, - true, - $is_admin, - )); - - $panel = new PHUIObjectBoxView(); - $header = new PHUIHeaderView(); - $header->setHeader(pht('Repositories')); - if ($is_admin) { - $button = id(new PHUIButtonView()) - ->setTag('a') - ->setText(pht('Create New Repository')) - ->setHref('/diffusion/new/'); - $header->addActionLink($button); - } - $panel->setHeader($header); - $panel->setTable($table); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Repository List')); - - return $this->buildApplicationPage( - array( - $crumbs, - $panel, - ), - array( - 'title' => pht('Repository List'), - )); - } - -} diff --git a/src/applications/repository/daemon/PhabricatorMercurialGraphStream.php b/src/applications/repository/daemon/PhabricatorMercurialGraphStream.php index 4d70d3b9e5..33f947e2f8 100644 --- a/src/applications/repository/daemon/PhabricatorMercurialGraphStream.php +++ b/src/applications/repository/daemon/PhabricatorMercurialGraphStream.php @@ -100,7 +100,10 @@ final class PhabricatorMercurialGraphStream } throw new Exception( - "No such {$until_type} '{$until_name}' in repository!"); + pht( + "No such %s '%s' in repository!", + $until_type, + $until_name)); } diff --git a/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php b/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php index e2895972dd..3ed78f5161 100644 --- a/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php +++ b/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php @@ -73,6 +73,7 @@ final class PhabricatorRepositoryPullLocalDaemon $queue = array(); while (!$this->shouldExit()) { + PhabricatorCaches::destroyRequestCache(); $pullable = $this->loadPullableRepositories($include, $exclude); // If any repositories have the NEEDS_UPDATE flag set, pull them diff --git a/src/applications/repository/engine/PhabricatorRepositoryEngine.php b/src/applications/repository/engine/PhabricatorRepositoryEngine.php index 8d858a3d89..d5982473c5 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryEngine.php @@ -68,7 +68,10 @@ abstract class PhabricatorRepositoryEngine { $matches = null; if (!preg_match('/^\s*Fetch URL:\s*(.*?)\s*$/m', $remotes, $matches)) { throw new Exception( - "Expected 'Fetch URL' in 'git remote show -n origin'."); + pht( + "Expected '%s' in '%s'.", + 'Fetch URL', + 'git remote show -n origin')); } $remote_uri = $matches[1]; diff --git a/src/applications/repository/mail/PhabricatorRepositoryPushReplyHandler.php b/src/applications/repository/mail/PhabricatorRepositoryPushReplyHandler.php index d418fa3445..38de3fd709 100644 --- a/src/applications/repository/mail/PhabricatorRepositoryPushReplyHandler.php +++ b/src/applications/repository/mail/PhabricatorRepositoryPushReplyHandler.php @@ -8,7 +8,7 @@ final class PhabricatorRepositoryPushReplyHandler } public function getPrivateReplyHandlerEmailAddress( - PhabricatorObjectHandle $handle) { + PhabricatorUser $user) { return null; } diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php index 7fabffe7bd..4da1e03c9b 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php @@ -60,16 +60,16 @@ final class PhabricatorRepositoryManagementImportingWorkflow $status = $row['importStatus']; $need = array(); if (!($status & PhabricatorRepositoryCommit::IMPORTED_MESSAGE)) { - $need[] = 'Message'; + $need[] = pht('Message'); } if (!($status & PhabricatorRepositoryCommit::IMPORTED_CHANGE)) { - $need[] = 'Change'; + $need[] = pht('Change'); } if (!($status & PhabricatorRepositoryCommit::IMPORTED_OWNERS)) { - $need[] = 'Owners'; + $need[] = pht('Owners'); } if (!($status & PhabricatorRepositoryCommit::IMPORTED_HERALD)) { - $need[] = 'Herald'; + $need[] = pht('Herald'); } $console->writeOut(' %s', implode(', ', $need)); diff --git a/src/applications/repository/query/PhabricatorRepositoryQuery.php b/src/applications/repository/query/PhabricatorRepositoryQuery.php index 9a056b202a..7595cd0c8e 100644 --- a/src/applications/repository/query/PhabricatorRepositoryQuery.php +++ b/src/applications/repository/query/PhabricatorRepositoryQuery.php @@ -150,22 +150,13 @@ final class PhabricatorRepositoryQuery $this->identifierMap = array(); } + public function newResultObject() { + return new PhabricatorRepository(); + } + protected function loadPage() { - $table = new PhabricatorRepository(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - '%Q FROM %T r %Q %Q %Q %Q %Q %Q', - $this->buildSelectClause($conn_r), - $table->getTableName(), - $this->buildJoinClause($conn_r), - $this->buildWhereClause($conn_r), - $this->buildGroupClause($conn_r), - $this->buildHavingClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - + $table = $this->newResultObject(); + $data = $this->loadStandardPageRows($table); $repositories = $table->loadAllFromArray($data); if ($this->needCommitCounts) { @@ -386,25 +377,27 @@ final class PhabricatorRepositoryQuery return $map; } - protected function buildSelectClause(AphrontDatabaseConnection $conn) { - $parts = $this->buildSelectClauseParts($conn); + protected function buildSelectClauseParts(AphrontDatabaseConnection $conn) { + $parts = parent::buildSelectClauseParts($conn); + if ($this->shouldJoinSummaryTable()) { $parts[] = 's.*'; } - return $this->formatSelectClause($parts); + + return $parts; } - protected function buildJoinClause(AphrontDatabaseConnection $conn_r) { - $joins = $this->buildJoinClauseParts($conn_r); + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $joins = parent::buildJoinClauseParts($conn); if ($this->shouldJoinSummaryTable()) { $joins[] = qsprintf( - $conn_r, + $conn, 'LEFT JOIN %T s ON r.id = s.repositoryID', PhabricatorRepository::TABLE_SUMMARY); } - return $this->formatJoinClause($joins); + return $joins; } private function shouldJoinSummaryTable() { @@ -428,26 +421,26 @@ final class PhabricatorRepositoryQuery return false; } - protected function buildWhereClauseParts(AphrontDatabaseConnection $conn_r) { - $where = parent::buildWhereClauseParts($conn_r); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); - if ($this->ids) { + if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'r.id IN (%Ld)', $this->ids); } - if ($this->phids) { + if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'r.phid IN (%Ls)', $this->phids); } - if ($this->callsigns) { + if ($this->callsigns !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'r.callsign IN (%Ls)', $this->callsigns); } @@ -459,21 +452,21 @@ final class PhabricatorRepositoryQuery if ($this->numericIdentifiers) { $identifier_clause[] = qsprintf( - $conn_r, + $conn, 'r.id IN (%Ld)', $this->numericIdentifiers); } if ($this->callsignIdentifiers) { $identifier_clause[] = qsprintf( - $conn_r, + $conn, 'r.callsign IN (%Ls)', $this->callsignIdentifiers); } if ($this->phidIdentifiers) { $identifier_clause[] = qsprintf( - $conn_r, + $conn, 'r.phid IN (%Ls)', $this->phidIdentifiers); } @@ -483,21 +476,21 @@ final class PhabricatorRepositoryQuery if ($this->types) { $where[] = qsprintf( - $conn_r, + $conn, 'r.versionControlSystem IN (%Ls)', $this->types); } if ($this->uuids) { $where[] = qsprintf( - $conn_r, + $conn, 'r.uuid IN (%Ls)', $this->uuids); } if (strlen($this->nameContains)) { $where[] = qsprintf( - $conn_r, + $conn, 'name LIKE %~', $this->nameContains); } @@ -511,7 +504,7 @@ final class PhabricatorRepositoryQuery $callsign = $query; } $where[] = qsprintf( - $conn_r, + $conn, 'r.name LIKE %> OR r.callsign LIKE %>', $query, $callsign); diff --git a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php index 21c7fe38ab..2070586498 100644 --- a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php +++ b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php @@ -11,122 +11,66 @@ final class PhabricatorRepositorySearchEngine return 'PhabricatorDiffusionApplication'; } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - - $saved->setParameter('callsigns', $request->getStrList('callsigns')); - $saved->setParameter('status', $request->getStr('status')); - $saved->setParameter('order', $request->getStr('order')); - $saved->setParameter('hosted', $request->getStr('hosted')); - $saved->setParameter('types', $request->getArr('types')); - $saved->setParameter('name', $request->getStr('name')); - - $saved->setParameter( - 'projects', - $this->readProjectsFromRequest($request, 'projects')); - - return $saved; - } - - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new PhabricatorRepositoryQuery()) + public function newQuery() { + return id(new PhabricatorRepositoryQuery()) ->needProjectPHIDs(true) ->needCommitCounts(true) ->needMostRecentCommits(true); - - $callsigns = $saved->getParameter('callsigns'); - if ($callsigns) { - $query->withCallsigns($callsigns); - } - - $status = $saved->getParameter('status'); - $status = idx($this->getStatusValues(), $status); - if ($status) { - $query->withStatus($status); - } - - $this->setQueryOrder($query, $saved); - - $hosted = $saved->getParameter('hosted'); - $hosted = idx($this->getHostedValues(), $hosted); - if ($hosted) { - $query->withHosted($hosted); - } - - $types = $saved->getParameter('types'); - if ($types) { - $query->withTypes($types); - } - - $name = $saved->getParameter('name'); - if (strlen($name)) { - $query->withNameContains($name); - } - - $adjusted = clone $saved; - $adjusted->setParameter('projects', $this->readProjectTokens($saved)); - $this->setQueryProjects($query, $adjusted); - - return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved_query) { + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorSearchStringListField()) + ->setLabel(pht('Callsigns')) + ->setKey('callsigns'), + id(new PhabricatorSearchTextField()) + ->setLabel(pht('Name Contains')) + ->setKey('name'), + id(new PhabricatorSearchSelectField()) + ->setLabel(pht('Status')) + ->setKey('status') + ->setOptions($this->getStatusOptions()), + id(new PhabricatorSearchSelectField()) + ->setLabel(pht('Hosted')) + ->setKey('hosted') + ->setOptions($this->getHostedOptions()), + id(new PhabricatorSearchCheckboxesField()) + ->setLabel(pht('Types')) + ->setKey('types') + ->setOptions(PhabricatorRepositoryType::getAllRepositoryTypes()), + ); + } - $callsigns = $saved_query->getParameter('callsigns', array()); - $types = $saved_query->getParameter('types', array()); - $types = array_fuse($types); - $name = $saved_query->getParameter('name'); - $projects = $this->readProjectTokens($saved_query); + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); - $form - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('callsigns') - ->setLabel(pht('Callsigns')) - ->setValue(implode(', ', $callsigns))) - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('name') - ->setLabel(pht('Name Contains')) - ->setValue($name)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorProjectLogicalDatasource()) - ->setName('projects') - ->setLabel(pht('Projects')) - ->setValue($projects)) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setName('status') - ->setLabel(pht('Status')) - ->setValue($saved_query->getParameter('status')) - ->setOptions($this->getStatusOptions())) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setName('hosted') - ->setLabel(pht('Hosted')) - ->setValue($saved_query->getParameter('hosted')) - ->setOptions($this->getHostedOptions())); - - $type_control = id(new AphrontFormCheckboxControl()) - ->setLabel(pht('Types')); - - $all_types = PhabricatorRepositoryType::getAllRepositoryTypes(); - foreach ($all_types as $key => $name) { - $type_control->addCheckbox( - 'types[]', - $key, - $name, - isset($types[$key])); + if ($map['callsigns']) { + $query->withCallsigns($map['callsigns']); } - $form->appendChild($type_control); - $this->appendOrderFieldsToForm( - $form, - $saved_query, - new PhabricatorRepositoryQuery()); + if ($map['status']) { + $status = idx($this->getStatusValues(), $map['status']); + if ($status) { + $query->withStatus($status); + } + } + + if ($map['hosted']) { + $hosted = idx($this->getHostedValues(), $map['hosted']); + if ($hosted) { + $query->withHosted($hosted); + } + } + + if ($map['types']) { + $query->withTypes($map['types']); + } + + if (strlen($map['name'])) { + $query->withNameContains($map['name']); + } + + return $query; } protected function getURI($path) { @@ -268,15 +212,20 @@ final class PhabricatorRepositorySearchEngine return $list; } - private function readProjectTokens(PhabricatorSavedQuery $saved) { - $projects = $saved->getParameter('projects', array()); + protected function willUseSavedQuery(PhabricatorSavedQuery $saved) { + $project_phids = $saved->getParameter('projectPHIDs', array()); + + $old = $saved->getParameter('projects', array()); + foreach ($old as $phid) { + $project_phids[] = $phid; + } $any = $saved->getParameter('anyProjectPHIDs', array()); foreach ($any as $project) { - $projects[] = 'any('.$project.')'; + $project_phids[] = 'any('.$project.')'; } - return $projects; + $saved->setParameter('projectPHIDs', $project_phids); } } diff --git a/src/applications/repository/search/PhabricatorRepositoryCommitSearchIndexer.php b/src/applications/repository/search/PhabricatorRepositoryCommitSearchIndexer.php index 9565bf7638..f94aa26b4e 100644 --- a/src/applications/repository/search/PhabricatorRepositoryCommitSearchIndexer.php +++ b/src/applications/repository/search/PhabricatorRepositoryCommitSearchIndexer.php @@ -36,7 +36,7 @@ final class PhabricatorRepositoryCommitSearchIndexer $doc->setDocumentTitle($title); $doc->addField( - PhabricatorSearchField::FIELD_BODY, + PhabricatorSearchDocumentFieldType::FIELD_BODY, $commit_message); if ($author_phid) { diff --git a/src/applications/repository/storage/PhabricatorRepositorySymbol.php b/src/applications/repository/storage/PhabricatorRepositorySymbol.php index b450c749c1..d0ce6428c9 100644 --- a/src/applications/repository/storage/PhabricatorRepositorySymbol.php +++ b/src/applications/repository/storage/PhabricatorRepositorySymbol.php @@ -15,6 +15,10 @@ final class PhabricatorRepositorySymbol extends PhabricatorRepositoryDAO { protected $symbolLanguage; protected $pathID; protected $lineNumber; + private $isExternal; + private $source; + private $location; + private $externalURI; private $path = self::ATTACHABLE; private $repository = self::ATTACHABLE; @@ -40,6 +44,10 @@ final class PhabricatorRepositorySymbol extends PhabricatorRepositoryDAO { } public function getURI() { + if ($this->isExternal) { + return $this->externalURI; + } + $request = DiffusionRequest::newFromDictionary( array( 'user' => PhabricatorUser::getOmnipotentUser(), @@ -71,4 +79,32 @@ final class PhabricatorRepositorySymbol extends PhabricatorRepositoryDAO { return $this; } + public function isExternal() { + return $this->isExternal; + } + public function setIsExternal($is_external) { + $this->isExternal = $is_external; + return $this; + } + + public function getSource() { + return $this->source; + } + public function setSource($source) { + $this->source = $source; + return $this; + } + + public function getLocation() { + return $this->location; + } + public function setLocation($location) { + $this->location = $location; + return $this; + } + + public function setExternalURI($external_uri) { + $this->externalURI = $external_uri; + return $this; + } } diff --git a/src/applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php b/src/applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php index 4d0384fe93..d3535102de 100644 --- a/src/applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php +++ b/src/applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php @@ -27,7 +27,7 @@ final class PhabricatorRepositoryURITestCase $repo = PhabricatorRepository::initializeNewRepository($user) ->setVersionControlSystem($svn) - ->setName('Test Repo') + ->setName(pht('Test Repo')) ->setCallsign('TESTREPO') ->setCredentialPHID($http_credential->getPHID()) ->save(); diff --git a/src/applications/repository/worker/PhabricatorRepositoryPushMailWorker.php b/src/applications/repository/worker/PhabricatorRepositoryPushMailWorker.php index 4a56b6bc96..17c6d17d5b 100644 --- a/src/applications/repository/worker/PhabricatorRepositoryPushMailWorker.php +++ b/src/applications/repository/worker/PhabricatorRepositoryPushMailWorker.php @@ -27,6 +27,24 @@ final class PhabricatorRepositoryPushMailWorker return; } + $targets = id(new PhabricatorRepositoryPushReplyHandler()) + ->setMailReceiver($repository) + ->getMailTargets($email_phids, array()); + foreach ($targets as $target) { + $this->sendMail($target, $repository, $event); + } + } + + private function sendMail( + PhabricatorMailTarget $target, + PhabricatorRepository $repository, + PhabricatorRepositoryPushEvent $event) { + + $task_data = $this->getTaskData(); + $viewer = $target->getViewer(); + + $locale = PhabricatorEnv::beginScopedLocale($viewer->getTranslation()); + $logs = $event->getLogs(); list($ref_lines, $ref_list) = $this->renderRefs($logs); @@ -103,20 +121,7 @@ final class PhabricatorRepositoryPushMailWorker ->addHeader('Thread-Topic', $subject) ->setIsBulk(true); - $to_handles = id(new PhabricatorHandleQuery()) - ->setViewer($viewer) - ->withPHIDs($email_phids) - ->execute(); - - $reply_handler = new PhabricatorRepositoryPushReplyHandler(); - $mails = $reply_handler->multiplexMail( - $mail, - $to_handles, - array()); - - foreach ($mails as $mail) { - $mail->saveAndSend(); - } + $target->sendMail($mail); } public function renderForDisplay(PhabricatorUser $viewer) { diff --git a/src/applications/search/constants/PhabricatorSearchField.php b/src/applications/search/constants/PhabricatorSearchDocumentFieldType.php similarity index 65% rename from src/applications/search/constants/PhabricatorSearchField.php rename to src/applications/search/constants/PhabricatorSearchDocumentFieldType.php index c578cdf109..10dbf0ca65 100644 --- a/src/applications/search/constants/PhabricatorSearchField.php +++ b/src/applications/search/constants/PhabricatorSearchDocumentFieldType.php @@ -1,6 +1,6 @@ getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); $phids = $request->getArr('phids'); $handles = id(new PhabricatorHandleQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withPHIDs($phids) ->execute(); $objects = id(new PhabricatorObjectQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withPHIDs($phids) ->execute(); @@ -26,9 +25,15 @@ final class PhabricatorSearchHovercardController foreach ($phids as $phid) { $handle = $handles[$phid]; + $object = $objects[$phid]; - $hovercard = new PhabricatorHovercardView(); - $hovercard->setObjectHandle($handle); + $hovercard = id(new PhabricatorHovercardView()) + ->setUser($viewer) + ->setObjectHandle($handle); + + if ($object) { + $hovercard->setObject($object); + } // Send it to the other side of the world, thanks to PhutilEventEngine $event = new PhabricatorEvent( @@ -36,9 +41,9 @@ final class PhabricatorSearchHovercardController array( 'hovercard' => $hovercard, 'handle' => $handle, - 'object' => idx($objects, $phid), + 'object' => $object, )); - $event->setUser($user); + $event->setUser($viewer); PhutilEventEngine::dispatchEvent($event); $cards[$phid] = $hovercard; diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php index 82471b696f..eeb2d0c0e0 100644 --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -14,7 +14,7 @@ * @task exec Paging and Executing Queries * @task render Rendering Results */ -abstract class PhabricatorApplicationSearchEngine { +abstract class PhabricatorApplicationSearchEngine extends Phobject { private $application; private $viewer; @@ -26,6 +26,23 @@ abstract class PhabricatorApplicationSearchEngine { const CONTEXT_LIST = 'list'; const CONTEXT_PANEL = 'panel'; + public function newResultObject() { + // We may be able to get this automatically if newQuery() is implemented. + $query = $this->newQuery(); + if ($query) { + $object = $query->newResultObject(); + if ($object) { + return $object; + } + } + + return null; + } + + public function newQuery() { + return null; + } + public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; return $this; @@ -69,8 +86,20 @@ abstract class PhabricatorApplicationSearchEngine { * @param AphrontRequest The search request. * @return PhabricatorSavedQuery */ - abstract public function buildSavedQueryFromRequest( - AphrontRequest $request); + public function buildSavedQueryFromRequest(AphrontRequest $request) { + $fields = $this->buildSearchFields(); + $viewer = $this->requireViewer(); + + $saved = new PhabricatorSavedQuery(); + foreach ($fields as $field) { + $field->setViewer($viewer); + + $value = $field->readValueFromRequest($request); + $saved->setParameter($field->getKey(), $value); + } + + return $saved; + } /** * Executes the saved query. @@ -78,8 +107,88 @@ abstract class PhabricatorApplicationSearchEngine { * @param PhabricatorSavedQuery The saved query to operate on. * @return The result of the query. */ - abstract public function buildQueryFromSavedQuery( - PhabricatorSavedQuery $saved); + public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { + $saved = clone $saved; + $this->willUseSavedQuery($saved); + + $fields = $this->buildSearchFields(); + $viewer = $this->requireViewer(); + + $map = array(); + foreach ($fields as $field) { + $field->setViewer($viewer); + $field->readValueFromSavedQuery($saved); + $value = $field->getValueForQuery($field->getValue()); + $map[$field->getKey()] = $value; + } + + $query = $this->buildQueryFromParameters($map); + + $object = $this->newResultObject(); + if (!$object) { + return $query; + } + + if ($object instanceof PhabricatorSubscribableInterface) { + if (!empty($map['subscriberPHIDs'])) { + $query->withEdgeLogicPHIDs( + PhabricatorObjectHasSubscriberEdgeType::EDGECONST, + PhabricatorQueryConstraint::OPERATOR_OR, + $map['subscriberPHIDs']); + } + } + + if ($object instanceof PhabricatorProjectInterface) { + if (!empty($map['projectPHIDs'])) { + $query->withEdgeLogicConstraints( + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, + $map['projectPHIDs']); + } + } + + if ($object instanceof PhabricatorSpacesInterface) { + if (!empty($map['spacePHIDs'])) { + $query->withSpacePHIDs($map['spacePHIDs']); + } + } + + if ($object instanceof PhabricatorCustomFieldInterface) { + $this->applyCustomFieldsToQuery($query, $saved); + } + + $order = $saved->getParameter('order'); + $builtin = $query->getBuiltinOrderAliasMap(); + if (strlen($order) && isset($builtin[$order])) { + $query->setOrder($order); + } else { + // If the order is invalid or not available, we choose the first + // builtin order. This isn't always the default order for the query, + // but is the first value in the "Order" dropdown, and makes the query + // behavior more consistent with the UI. In queries where the two + // orders differ, this order is the preferred order for humans. + $query->setOrder(head_key($builtin)); + } + + return $query; + } + + /** + * Hook for subclasses to adjust saved queries prior to use. + * + * If an application changes how queries are saved, it can implement this + * hook to keep old queries working the way users expect, by reading, + * adjusting, and overwriting parameters. + * + * @param PhabricatorSavedQuery Saved query which will be executed. + * @return void + */ + protected function willUseSavedQuery(PhabricatorSavedQuery $saved) { + return; + } + + protected function buildQueryFromParameters(array $parameters) { + throw new PhutilMethodNotImplementedException(); + } /** * Builds the search form using the request. @@ -88,9 +197,171 @@ abstract class PhabricatorApplicationSearchEngine { * @param PhabricatorSavedQuery The query from which to build the form. * @return void */ - abstract public function buildSearchForm( + public function buildSearchForm( AphrontFormView $form, - PhabricatorSavedQuery $query); + PhabricatorSavedQuery $saved) { + + $saved = clone $saved; + $this->willUseSavedQuery($saved); + + $fields = $this->buildSearchFields(); + $fields = $this->adjustFieldsForDisplay($fields); + $viewer = $this->requireViewer(); + + foreach ($fields as $field) { + $field->setViewer($viewer); + $field->readValueFromSavedQuery($saved); + } + + foreach ($fields as $field) { + foreach ($field->getErrors() as $error) { + $this->addError(last($error)); + } + } + + foreach ($fields as $field) { + $field->appendToForm($form); + } + } + + protected function buildSearchFields() { + $fields = array(); + + foreach ($this->buildCustomSearchFields() as $field) { + $fields[] = $field; + } + + $object = $this->newResultObject(); + if ($object) { + if ($object instanceof PhabricatorSubscribableInterface) { + $fields[] = id(new PhabricatorSearchSubscribersField()) + ->setLabel(pht('Subscribers')) + ->setKey('subscriberPHIDs') + ->setAliases(array('subscriber', 'subscribers')); + } + + if ($object instanceof PhabricatorProjectInterface) { + $fields[] = id(new PhabricatorSearchProjectsField()) + ->setKey('projectPHIDs') + ->setAliases(array('project', 'projects')) + ->setLabel(pht('Projects')); + } + + if ($object instanceof PhabricatorSpacesInterface) { + if (PhabricatorSpacesNamespaceQuery::getSpacesExist()) { + $fields[] = id(new PhabricatorSearchSpacesField()) + ->setKey('spacePHIDs') + ->setAliases(array('space', 'spaces')) + ->setLabel(pht('Spaces')); + } + } + } + + foreach ($this->buildCustomFieldSearchFields() as $custom_field) { + $fields[] = $custom_field; + } + + $query = $this->newQuery(); + if ($query) { + $orders = $query->getBuiltinOrders(); + $orders = ipull($orders, 'name'); + + $fields[] = id(new PhabricatorSearchOrderField()) + ->setLabel(pht('Order By')) + ->setKey('order') + ->setOrderAliases($query->getBuiltinOrderAliasMap()) + ->setOptions($orders); + } + + $field_map = array(); + foreach ($fields as $field) { + $key = $field->getKey(); + if (isset($field_map[$key])) { + throw new Exception( + pht( + 'Two fields in this SearchEngine use the same key ("%s"), but '. + 'each field must use a unique key.', + $key)); + } + $field_map[$key] = $field; + } + + return $field_map; + } + + private function adjustFieldsForDisplay(array $field_map) { + $order = $this->getDefaultFieldOrder(); + + $head_keys = array(); + $tail_keys = array(); + $seen_tail = false; + foreach ($order as $order_key) { + if ($order_key === '...') { + $seen_tail = true; + continue; + } + + if (!$seen_tail) { + $head_keys[] = $order_key; + } else { + $tail_keys[] = $order_key; + } + } + + $head = array_select_keys($field_map, $head_keys); + $body = array_diff_key($field_map, array_fuse($tail_keys)); + $tail = array_select_keys($field_map, $tail_keys); + + $result = $head + $body + $tail; + + foreach ($this->getHiddenFields() as $hidden_key) { + unset($result[$hidden_key]); + } + + return $result; + } + + protected function buildCustomSearchFields() { + throw new PhutilMethodNotImplementedException(); + } + + + /** + * Define the default display order for fields by returning a list of + * field keys. + * + * You can use the special key `...` to mean "all unspecified fields go + * here". This lets you easily put important fields at the top of the form, + * standard fields in the middle of the form, and less important fields at + * the bottom. + * + * For example, you might return a list like this: + * + * return array( + * 'authorPHIDs', + * 'reviewerPHIDs', + * '...', + * 'createdAfter', + * 'createdBefore', + * ); + * + * Any unspecified fields (including custom fields and fields added + * automatically by infrastruture) will be put in the middle. + * + * @return list Default ordering for field keys. + */ + protected function getDefaultFieldOrder() { + return array(); + } + + /** + * Return a list of field keys which should be hidden from the viewer. + * + * @return list Fields to hide. + */ + protected function getHiddenFields() { + return array(); + } public function getErrors() { return $this->errors; @@ -484,7 +755,6 @@ abstract class PhabricatorApplicationSearchEngine { $key, array( PhabricatorProjectProjectPHIDType::TYPECONST, - PhabricatorMailingListListPHIDType::TYPECONST, )); } @@ -667,68 +937,17 @@ abstract class PhabricatorApplicationSearchEngine { } -/* -( Result Ordering )---------------------------------------------------- */ - - - /** - * Save order selection to a @{class:PhabricatorSavedQuery}. - */ - protected function saveQueryOrder( - PhabricatorSavedQuery $saved, - AphrontRequest $request) { - - $saved->setParameter('order', $request->getStr('order')); - - return $this; - } - - - /** - * Set query ordering from a saved value. - */ - protected function setQueryOrder( - PhabricatorCursorPagedPolicyAwareQuery $query, - PhabricatorSavedQuery $saved) { - - $order = $saved->getParameter('order'); - $builtin = $query->getBuiltinOrders(); - if (strlen($order) && isset($builtin[$order])) { - $query->setOrder($order); - } else { - // If the order is invalid or not available, we choose the first - // builtin order. This isn't always the default order for the query, - // but is the first value in the "Order" dropdown, and makes the query - // behavior more consistent with the UI. In queries where the two - // orders differ, this order is the preferred order for humans. - $query->setOrder(head_key($builtin)); - } - - return $this; - } - - - - protected function appendOrderFieldsToForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved, - PhabricatorCursorPagedPolicyAwareQuery $query) { - - $orders = $query->getBuiltinOrders(); - $orders = ipull($orders, 'name'); - - $form->appendControl( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Order')) - ->setName('order') - ->setOptions($orders) - ->setValue($saved->getParameter('order'))); - } - /* -( Paging and Executing Queries )--------------------------------------- */ public function getPageSize(PhabricatorSavedQuery $saved) { - return $saved->getParameter('limit', 100); + $limit = (int)$saved->getParameter('limit'); + + if ($limit > 0) { + return $limit; + } + + return 100; } @@ -833,6 +1052,10 @@ abstract class PhabricatorApplicationSearchEngine { * @task appsearch */ public function getCustomFieldObject() { + $object = $this->newResultObject(); + if ($object instanceof PhabricatorCustomFieldInterface) { + return $object; + } return null; } @@ -861,33 +1084,6 @@ abstract class PhabricatorApplicationSearchEngine { } - /** - * Moves data from the request into a saved query. - * - * @param AphrontRequest Request to read. - * @param PhabricatorSavedQuery Query to write to. - * @return void - * @task appsearch - */ - protected function readCustomFieldsFromRequest( - AphrontRequest $request, - PhabricatorSavedQuery $saved) { - - $list = $this->getCustomFieldList(); - if (!$list) { - return; - } - - foreach ($list->getFields() as $field) { - $key = $this->getKeyForCustomField($field); - $value = $field->readApplicationSearchValueFromRequest( - $this, - $request); - $saved->setParameter($key, $value); - } - } - - /** * Applies data from a saved query to an executable query. * @@ -905,127 +1101,27 @@ abstract class PhabricatorApplicationSearchEngine { } foreach ($list->getFields() as $field) { - $key = $this->getKeyForCustomField($field); $value = $field->applyApplicationSearchConstraintToQuery( $this, $query, - $saved->getParameter($key)); + $saved->getParameter('custom:'.$field->getFieldIndex())); } } - protected function applyOrderByToQuery( - PhabricatorCursorPagedPolicyAwareQuery $query, - array $standard_values, - $order) { - - if (substr($order, 0, 7) === 'custom:') { - $list = $this->getCustomFieldList(); - if (!$list) { - $query->setOrderBy(head($standard_values)); - return; - } - - foreach ($list->getFields() as $field) { - $key = $this->getKeyForCustomField($field); - - if ($key === $order) { - $index = $field->buildOrderIndex(); - - if ($index === null) { - $query->setOrderBy(head($standard_values)); - return; - } - - $query->withApplicationSearchOrder( - $field, - $index, - false); - break; - } - } - } else { - $order = idx($standard_values, $order); - if ($order) { - $query->setOrderBy($order); - } else { - $query->setOrderBy(head($standard_values)); - } - } - } - - - protected function getCustomFieldOrderOptions() { + private function buildCustomFieldSearchFields() { $list = $this->getCustomFieldList(); if (!$list) { - return; + return array(); } - $custom_order = array(); + $fields = array(); foreach ($list->getFields() as $field) { - if ($field->shouldAppearInApplicationSearch()) { - if ($field->buildOrderIndex() !== null) { - $key = $this->getKeyForCustomField($field); - $custom_order[$key] = $field->getFieldName(); - } - } + $fields[] = id(new PhabricatorSearchCustomFieldProxyField()) + ->setSearchEngine($this) + ->setCustomField($field); } - return $custom_order; - } - - /** - * Get a unique key identifying a field. - * - * @param PhabricatorCustomField Field to identify. - * @return string Unique identifier, suitable for use as an input name. - */ - public function getKeyForCustomField(PhabricatorCustomField $field) { - return 'custom:'.$field->getFieldIndex(); - } - - - /** - * Add inputs to an application search form so the user can query on custom - * fields. - * - * @param AphrontFormView Form to update. - * @param PhabricatorSavedQuery Values to prefill. - * @return void - */ - protected function appendCustomFieldsToForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved) { - - $list = $this->getCustomFieldList(); - if (!$list) { - return; - } - - $phids = array(); - foreach ($list->getFields() as $field) { - $key = $this->getKeyForCustomField($field); - $value = $saved->getParameter($key); - $phids[$key] = $field->getRequiredHandlePHIDsForApplicationSearch($value); - } - $all_phids = array_mergev($phids); - - $handles = array(); - if ($all_phids) { - $handles = id(new PhabricatorHandleQuery()) - ->setViewer($this->requireViewer()) - ->withPHIDs($all_phids) - ->execute(); - } - - foreach ($list->getFields() as $field) { - $key = $this->getKeyForCustomField($field); - $value = $saved->getParameter($key); - $field->appendToApplicationSearchForm( - $this, - $form, - $value, - array_select_keys($handles, $phids[$key])); - } + return $fields; } } diff --git a/src/applications/search/field/PhabricatorSearchCheckboxesField.php b/src/applications/search/field/PhabricatorSearchCheckboxesField.php new file mode 100644 index 0000000000..de55c98d2d --- /dev/null +++ b/src/applications/search/field/PhabricatorSearchCheckboxesField.php @@ -0,0 +1,40 @@ +options = $options; + return $this; + } + + public function getOptions() { + return $this->options; + } + + protected function getDefaultValue() { + return array(); + } + + protected function getValueFromRequest(AphrontRequest $request, $key) { + return $this->getListFromRequest($request, $key); + } + + protected function newControl() { + $value = array_fuse($this->getValue()); + + $control = new AphrontFormCheckboxControl(); + foreach ($this->getOptions() as $key => $option) { + $control->addCheckbox( + $this->getKey().'[]', + $key, + $option, + isset($value[$key])); + } + + return $control; + } + +} diff --git a/src/applications/search/field/PhabricatorSearchCustomFieldProxyField.php b/src/applications/search/field/PhabricatorSearchCustomFieldProxyField.php new file mode 100644 index 0000000000..15cbfcb09e --- /dev/null +++ b/src/applications/search/field/PhabricatorSearchCustomFieldProxyField.php @@ -0,0 +1,58 @@ +searchEngine = $engine; + return $this; + } + + public function getSearchEngine() { + return $this->searchEngine; + } + + public function setCustomField(PhabricatorCustomField $field) { + $this->customField = $field; + $this->setKey('custom:'.$field->getFieldIndex()); + + $aliases = array(); + $aliases[] = $field->getFieldKey(); + $this->setAliases($aliases); + + return $this; + } + + public function getCustomField() { + return $this->customField; + } + + protected function getDefaultValue() { + return null; + } + + protected function getValueExistsInRequest(AphrontRequest $request, $key) { + // TODO: For historical reasons, the keys we look for don't line up with + // the keys that CustomFields use. Just skip the check for existence and + // always read the value. It would be vaguely nice to make rendering more + // consistent instead. + return true; + } + + protected function getValueFromRequest(AphrontRequest $request, $key) { + return $this->getCustomField()->readApplicationSearchValueFromRequest( + $this->getSearchEngine(), + $request); + } + + public function appendToForm(AphrontFormView $form) { + return $this->getCustomField()->appendToApplicationSearchForm( + $this->getSearchEngine(), + $form, + $this->getValue()); + } + +} diff --git a/src/applications/search/field/PhabricatorSearchDatasourceField.php b/src/applications/search/field/PhabricatorSearchDatasourceField.php new file mode 100644 index 0000000000..2e05d60643 --- /dev/null +++ b/src/applications/search/field/PhabricatorSearchDatasourceField.php @@ -0,0 +1,17 @@ +datasource); + } + + public function setDatasource(PhabricatorTypeaheadDatasource $datasource) { + $this->datasource = $datasource; + return $this; + } + +} diff --git a/src/applications/search/field/PhabricatorSearchDateField.php b/src/applications/search/field/PhabricatorSearchDateField.php new file mode 100644 index 0000000000..c67c7178cc --- /dev/null +++ b/src/applications/search/field/PhabricatorSearchDateField.php @@ -0,0 +1,41 @@ +getStr($key); + } + + public function getValueForQuery($value) { + return $this->parseDateTime($value); + } + + protected function validateControlValue($value) { + if (!strlen($value)) { + return; + } + + $epoch = $this->parseDateTime($value); + if ($epoch) { + return; + } + + $this->addError( + pht('Invalid'), + pht('Date value for "%s" can not be parsed.', $this->getLabel())); + } + + protected function parseDateTime($value) { + if (!strlen($value)) { + return null; + } + + return PhabricatorTime::parseLocalTime($value, $this->getViewer()); + } + +} diff --git a/src/applications/search/field/PhabricatorSearchField.php b/src/applications/search/field/PhabricatorSearchField.php new file mode 100644 index 0000000000..8a3e06ee08 --- /dev/null +++ b/src/applications/search/field/PhabricatorSearchField.php @@ -0,0 +1,266 @@ +key = $key; + return $this; + } + + + /** + * Get the field's key. + * + * @return string Unique key for this field. + * @task config + */ + public function getKey() { + return $this->key; + } + + + /** + * Set a human-readable label for the field. + * + * This should be a short text string, like "Reviewers" or "Colors". + * + * @param string Short, human-readable field label. + * @return this + * task config + */ + public function setLabel($label) { + $this->label = $label; + return $this; + } + + + /** + * Get the field's human-readable label. + * + * @return string Short, human-readable field label. + * @task config + */ + public function getLabel() { + return $this->label; + } + + + /** + * Set the acting viewer. + * + * Engines do not need to do this explicitly; it will be done on their + * behalf by the caller. + * + * @param PhabricatorUser Viewer. + * @return this + * @task config + */ + public function setViewer(PhabricatorUser $viewer) { + $this->viewer = $viewer; + return $this; + } + + + /** + * Get the acting viewer. + * + * @return PhabricatorUser Viewer. + * @task config + */ + public function getViewer() { + return $this->viewer; + } + + + /** + * Provide alternate field aliases, usually more human-readable versions + * of the key. + * + * These aliases can be used when building GET requests, so you can provide + * an alias like `authors` to let users write `&authors=alincoln` instead of + * `&authorPHIDs=alincoln`. This is a little easier to use. + * + * @param list List of aliases for this field. + * @return this + * @task config + */ + public function setAliases(array $aliases) { + $this->aliases = $aliases; + return $this; + } + + + /** + * Get aliases for this field. + * + * @return list List of aliases for this field. + * @task config + */ + public function getAliases() { + return $this->aliases; + } + + +/* -( Handling Errors )---------------------------------------------------- */ + + + protected function addError($short, $long) { + $this->errors[] = array($short, $long); + return $this; + } + + public function getErrors() { + return $this->errors; + } + + protected function validateControlValue($value) { + return; + } + + protected function getShortError() { + $error = head($this->getErrors()); + if ($error) { + return head($error); + } + return null; + } + + +/* -( Reading and Writing Field Values )----------------------------------- */ + + + public function readValueFromRequest(AphrontRequest $request) { + $check = array_merge(array($this->getKey()), $this->getAliases()); + foreach ($check as $key) { + if ($this->getValueExistsInRequest($request, $key)) { + return $this->getValueFromRequest($request, $key); + } + } + return $this->getDefaultValue(); + } + + protected function getValueExistsInRequest(AphrontRequest $request, $key) { + return $request->getExists($key); + } + + abstract protected function getValueFromRequest( + AphrontRequest $request, + $key); + + public function readValueFromSavedQuery(PhabricatorSavedQuery $saved) { + $value = $saved->getParameter( + $this->getKey(), + $this->getDefaultValue()); + $this->value = $this->didReadValueFromSavedQuery($value); + $this->validateControlValue($value); + return $this; + } + + protected function didReadValueFromSavedQuery($value) { + return $value; + } + + public function getValue() { + return $this->value; + } + + protected function getValueForControl() { + return $this->value; + } + + protected function getDefaultValue() { + return null; + } + + public function getValueForQuery($value) { + return $value; + } + + +/* -( Rendering Controls )------------------------------------------------- */ + + + protected function newControl() { + throw new PhutilMethodNotImplementedException(); + } + + + protected function renderControl() { + // TODO: We should `setError($this->getShortError())` here, but it looks + // terrible in the form layout. + + return $this->newControl() + ->setValue($this->getValueForControl()) + ->setName($this->getKey()) + ->setLabel($this->getLabel()); + } + + public function appendToForm(AphrontFormView $form) { + $form->appendControl($this->renderControl()); + return $this; + } + + +/* -( Utility Methods )----------------------------------------------------- */ + + + /** + * Read a list of items from the request, in either array format or string + * format: + * + * list[]=item1&list[]=item2 + * list=item1,item2 + * + * This provides flexibility when constructing URIs, especially from external + * sources. + * + * @param AphrontRequest Request to read strings from. + * @param string Key to read in the request. + * @return list List of values. + * @task utility + */ + protected function getListFromRequest( + AphrontRequest $request, + $key) { + + $list = $request->getArr($key, null); + if ($list === null) { + $list = $request->getStrList($key); + } + + if (!$list) { + return array(); + } + + return $list; + } +} diff --git a/src/applications/search/field/PhabricatorSearchOrderField.php b/src/applications/search/field/PhabricatorSearchOrderField.php new file mode 100644 index 0000000000..70b94d1b35 --- /dev/null +++ b/src/applications/search/field/PhabricatorSearchOrderField.php @@ -0,0 +1,50 @@ +orderAliases = $order_aliases; + return $this; + } + + public function getOrderAliases() { + return $this->orderAliases; + } + + public function setOptions(array $options) { + $this->options = $options; + return $this; + } + + public function getOptions() { + return $this->options; + } + + protected function getDefaultValue() { + return null; + } + + protected function getValueFromRequest(AphrontRequest $request, $key) { + return $request->getStr($key); + } + + protected function getValueForControl() { + // If the SavedQuery has an alias for an order, map it to the canonical + // name for the order so the correct option is selected in the dropdown. + $value = parent::getValueForControl(); + if (isset($this->orderAliases[$value])) { + $value = $this->orderAliases[$value]; + } + return $value; + } + + protected function newControl() { + return id(new AphrontFormSelectControl()) + ->setOptions($this->getOptions()); + } + +} diff --git a/src/applications/search/field/PhabricatorSearchOwnersField.php b/src/applications/search/field/PhabricatorSearchOwnersField.php new file mode 100644 index 0000000000..6d7d8db407 --- /dev/null +++ b/src/applications/search/field/PhabricatorSearchOwnersField.php @@ -0,0 +1,18 @@ +getUsersFromRequest($request, $key); + } + + protected function newDatasource() { + return new PhabricatorPeopleOwnerDatasource(); + } + +} diff --git a/src/applications/search/field/PhabricatorSearchProjectsField.php b/src/applications/search/field/PhabricatorSearchProjectsField.php new file mode 100644 index 0000000000..cf26e5354c --- /dev/null +++ b/src/applications/search/field/PhabricatorSearchProjectsField.php @@ -0,0 +1,50 @@ +getListFromRequest($request, $key); + + $phids = array(); + $slugs = array(); + $project_type = PhabricatorProjectProjectPHIDType::TYPECONST; + foreach ($list as $item) { + $type = phid_get_type($item); + if ($type == $project_type) { + $phids[] = $item; + } else { + if (PhabricatorTypeaheadDatasource::isFunctionToken($item)) { + // If this is a function, pass it through unchanged; we'll evaluate + // it later. + $phids[] = $item; + } else { + $slugs[] = $item; + } + } + } + + if ($slugs) { + $projects = id(new PhabricatorProjectQuery()) + ->setViewer($this->requireViewer()) + ->withSlugs($slugs) + ->execute(); + foreach ($projects as $project) { + $phids[] = $project->getPHID(); + } + $phids = array_unique($phids); + } + + return $phids; + + } + +} diff --git a/src/applications/search/field/PhabricatorSearchSelectField.php b/src/applications/search/field/PhabricatorSearchSelectField.php new file mode 100644 index 0000000000..0806174220 --- /dev/null +++ b/src/applications/search/field/PhabricatorSearchSelectField.php @@ -0,0 +1,36 @@ +options = $options; + return $this; + } + + public function getOptions() { + return $this->options; + } + + protected function getDefaultValue() { + return $this->default; + } + + public function setDefault($default) { + $this->default = $default; + return $this; + } + + protected function getValueFromRequest(AphrontRequest $request, $key) { + return $request->getStr($key); + } + + protected function newControl() { + return id(new AphrontFormSelectControl()) + ->setOptions($this->getOptions()); + } + +} diff --git a/src/applications/search/field/PhabricatorSearchSpacesField.php b/src/applications/search/field/PhabricatorSearchSpacesField.php new file mode 100644 index 0000000000..670e47db11 --- /dev/null +++ b/src/applications/search/field/PhabricatorSearchSpacesField.php @@ -0,0 +1,43 @@ +getViewer(); + $list = $this->getListFromRequest($request, $key); + + $type = new PhabricatorSpacesNamespacePHIDType(); + $phids = array(); + $names = array(); + foreach ($list as $item) { + if ($type->canLoadNamedObject($item)) { + $names[] = $item; + } else { + $phids[] = $item; + } + } + + if ($names) { + $spaces = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withNames($names) + ->execute(); + foreach (mpull($spaces, 'getPHID') as $phid) { + $phids[] = $phid; + } + $phids = array_unique($phids); + } + + return $phids; + } + +} diff --git a/src/applications/search/field/PhabricatorSearchStringListField.php b/src/applications/search/field/PhabricatorSearchStringListField.php new file mode 100644 index 0000000000..2db384ea90 --- /dev/null +++ b/src/applications/search/field/PhabricatorSearchStringListField.php @@ -0,0 +1,22 @@ +getStrList($key); + } + + protected function newControl() { + return new AphrontFormTextControl(); + } + + protected function getValueForControl() { + return implode(', ', parent::getValueForControl()); + } + +} diff --git a/src/applications/search/field/PhabricatorSearchSubscribersField.php b/src/applications/search/field/PhabricatorSearchSubscribersField.php new file mode 100644 index 0000000000..6a0e82b85b --- /dev/null +++ b/src/applications/search/field/PhabricatorSearchSubscribersField.php @@ -0,0 +1,21 @@ +getUsersFromRequest($request, $key, $allow_types); + } + + protected function newDatasource() { + return new PhabricatorMetaMTAMailableFunctionDatasource(); + } + +} diff --git a/src/applications/search/field/PhabricatorSearchTextField.php b/src/applications/search/field/PhabricatorSearchTextField.php new file mode 100644 index 0000000000..e3cf1f845c --- /dev/null +++ b/src/applications/search/field/PhabricatorSearchTextField.php @@ -0,0 +1,18 @@ +getStr($key); + } + + protected function newControl() { + return new AphrontFormTextControl(); + } + +} diff --git a/src/applications/search/field/PhabricatorSearchThreeStateField.php b/src/applications/search/field/PhabricatorSearchThreeStateField.php new file mode 100644 index 0000000000..f7e7a80c5b --- /dev/null +++ b/src/applications/search/field/PhabricatorSearchThreeStateField.php @@ -0,0 +1,48 @@ +options = array( + '' => $null, + 'true' => $yes, + 'false' => $no, + ); + return $this; + } + + public function getOptions() { + return $this->options; + } + + protected function getDefaultValue() { + return null; + } + + protected function getValueFromRequest(AphrontRequest $request, $key) { + if (!strlen($request->getStr($key))) { + return null; + } + return $request->getBool($key); + } + + protected function newControl() { + return id(new AphrontFormSelectControl()) + ->setOptions($this->getOptions()); + } + + protected function getValueForControl() { + $value = parent::getValueForControl(); + if ($value === true) { + return 'true'; + } + if ($value === false) { + return 'false'; + } + return null; + } + +} diff --git a/src/applications/search/field/PhabricatorSearchTokenizerField.php b/src/applications/search/field/PhabricatorSearchTokenizerField.php new file mode 100644 index 0000000000..9da7f9908c --- /dev/null +++ b/src/applications/search/field/PhabricatorSearchTokenizerField.php @@ -0,0 +1,70 @@ +getListFromRequest($request, $key); + } + + public function getValueForQuery($value) { + return $this->newDatasource() + ->setViewer($this->getViewer()) + ->evaluateTokens($value); + } + + protected function newControl() { + return id(new AphrontFormTokenizerControl()) + ->setDatasource($this->newDatasource()); + } + + + abstract protected function newDatasource(); + + + protected function getUsersFromRequest( + AphrontRequest $request, + $key, + array $allow_types = array()) { + $list = $this->getListFromRequest($request, $key); + + $phids = array(); + $names = array(); + $allow_types = array_fuse($allow_types); + $user_type = PhabricatorPeopleUserPHIDType::TYPECONST; + foreach ($list as $item) { + $type = phid_get_type($item); + if ($type == $user_type) { + $phids[] = $item; + } else if (isset($allow_types[$type])) { + $phids[] = $item; + } else { + if (PhabricatorTypeaheadDatasource::isFunctionToken($item)) { + // If this is a function, pass it through unchanged; we'll evaluate + // it later. + $phids[] = $item; + } else { + $names[] = $item; + } + } + } + + if ($names) { + $users = id(new PhabricatorPeopleQuery()) + ->setViewer($this->getViewer()) + ->withUsernames($names) + ->execute(); + foreach ($users as $user) { + $phids[] = $user->getPHID(); + } + $phids = array_unique($phids); + } + + return $phids; + } + +} diff --git a/src/applications/search/field/PhabricatorSearchUsersField.php b/src/applications/search/field/PhabricatorSearchUsersField.php new file mode 100644 index 0000000000..889afe99be --- /dev/null +++ b/src/applications/search/field/PhabricatorSearchUsersField.php @@ -0,0 +1,18 @@ +getUsersFromRequest($request, $key); + } + + protected function newDatasource() { + return new PhabricatorPeopleUserFunctionDatasource(); + } + +} diff --git a/src/applications/search/index/PhabricatorSearchAbstractDocument.php b/src/applications/search/index/PhabricatorSearchAbstractDocument.php index 3e7626f798..09923c7dcc 100644 --- a/src/applications/search/index/PhabricatorSearchAbstractDocument.php +++ b/src/applications/search/index/PhabricatorSearchAbstractDocument.php @@ -22,7 +22,7 @@ final class PhabricatorSearchAbstractDocument { public function setDocumentTitle($title) { $this->documentTitle = $title; - $this->addField(PhabricatorSearchField::FIELD_TITLE, $title); + $this->addField(PhabricatorSearchDocumentFieldType::FIELD_TITLE, $title); return $this; } diff --git a/src/applications/search/index/PhabricatorSearchDocumentIndexer.php b/src/applications/search/index/PhabricatorSearchDocumentIndexer.php index b3e90148ca..5e2140d4e0 100644 --- a/src/applications/search/index/PhabricatorSearchDocumentIndexer.php +++ b/src/applications/search/index/PhabricatorSearchDocumentIndexer.php @@ -154,7 +154,7 @@ abstract class PhabricatorSearchDocumentIndexer extends Phobject { $comment = $xaction->getComment(); $doc->addField( - PhabricatorSearchField::FIELD_COMMENT, + PhabricatorSearchDocumentFieldType::FIELD_COMMENT, $comment->getContent()); } } diff --git a/src/applications/settings/controller/PhabricatorSettingsMainController.php b/src/applications/settings/controller/PhabricatorSettingsMainController.php index 9f95b01695..80753b17ca 100644 --- a/src/applications/settings/controller/PhabricatorSettingsMainController.php +++ b/src/applications/settings/controller/PhabricatorSettingsMainController.php @@ -97,6 +97,8 @@ final class PhabricatorSettingsMainController $result = array(); foreach ($panels as $key => $panel) { + $panel->setUser($this->user); + if (!$panel->isEnabled()) { continue; } diff --git a/src/applications/settings/panel/PhabricatorAccountSettingsPanel.php b/src/applications/settings/panel/PhabricatorAccountSettingsPanel.php index 7acd5e0a54..72a225249a 100644 --- a/src/applications/settings/panel/PhabricatorAccountSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorAccountSettingsPanel.php @@ -14,8 +14,13 @@ final class PhabricatorAccountSettingsPanel extends PhabricatorSettingsPanel { return pht('Account Information'); } + public function isEditableByAdministrators() { + return true; + } + public function processRequest(AphrontRequest $request) { - $user = $request->getUser(); + $viewer = $this->getViewer(); + $user = $this->getUser(); $username = $user->getUsername(); $errors = array(); @@ -74,7 +79,7 @@ final class PhabricatorAccountSettingsPanel extends PhabricatorSettingsPanel { $form = new AphrontFormView(); $form - ->setUser($user) + ->setUser($viewer) ->appendChild( id(new AphrontFormSelectControl()) ->setOptions($translations) diff --git a/src/applications/settings/panel/PhabricatorConduitCertificateSettingsPanel.php b/src/applications/settings/panel/PhabricatorConduitCertificateSettingsPanel.php index d7210a3c8f..aca40d5521 100644 --- a/src/applications/settings/panel/PhabricatorConduitCertificateSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorConduitCertificateSettingsPanel.php @@ -19,6 +19,14 @@ final class PhabricatorConduitCertificateSettingsPanel return pht('Authentication'); } + public function isEnabled() { + if ($this->getUser()->getIsMailingList()) { + return false; + } + + return true; + } + public function processRequest(AphrontRequest $request) { $user = $this->getUser(); $viewer = $request->getUser(); diff --git a/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php b/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php index 306a815de3..fa2694848d 100644 --- a/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php @@ -15,8 +15,16 @@ final class PhabricatorEmailAddressesSettingsPanel return pht('Email'); } + public function isEditableByAdministrators() { + if ($this->getUser()->getIsMailingList()) { + return true; + } + + return false; + } + public function processRequest(AphrontRequest $request) { - $user = $request->getUser(); + $user = $this->getUser(); $editable = PhabricatorEnv::getEnvConfig('account.editable'); $uri = $request->getRequestURI(); @@ -157,7 +165,8 @@ final class PhabricatorEmailAddressesSettingsPanel PhutilURI $uri, $new) { - $user = $request->getUser(); + $user = $this->getUser(); + $viewer = $this->getViewer(); $e_email = true; $email = null; @@ -171,7 +180,7 @@ final class PhabricatorEmailAddressesSettingsPanel } PhabricatorSystemActionEngine::willTakeAction( - array($user->getPHID()), + array($viewer->getPHID()), new PhabricatorSettingsAddEmailAction(), 1); @@ -201,12 +210,24 @@ final class PhabricatorEmailAddressesSettingsPanel ->setAddress($email) ->setIsVerified(0); - try { + // If an administrator is editing a mailing list, automatically verify + // the address. + if ($viewer->getPHID() != $user->getPHID()) { + if ($viewer->getIsAdmin()) { + $object->setIsVerified(1); + } + } + try { id(new PhabricatorUserEditor()) - ->setActor($user) + ->setActor($viewer) ->addEmail($user, $object); + if ($object->getIsVerified()) { + // If we autoverified the address, just reload the page. + return id(new AphrontReloadResponse())->setURI($uri); + } + $object->sendVerificationEmail($user); $dialog = id(new AphrontDialogView()) @@ -242,7 +263,7 @@ final class PhabricatorEmailAddressesSettingsPanel ->setError($e_email)); $dialog = id(new AphrontDialogView()) - ->setUser($user) + ->setUser($viewer) ->addHiddenInput('new', 'true') ->setTitle(pht('New Address')) ->appendChild($errors) @@ -257,8 +278,8 @@ final class PhabricatorEmailAddressesSettingsPanel AphrontRequest $request, PhutilURI $uri, $email_id) { - - $user = $request->getUser(); + $user = $this->getUser(); + $viewer = $this->getViewer(); // NOTE: You can only delete your own email addresses, and you can not // delete your primary address. @@ -272,9 +293,8 @@ final class PhabricatorEmailAddressesSettingsPanel } if ($request->isFormPost()) { - id(new PhabricatorUserEditor()) - ->setActor($user) + ->setActor($viewer) ->removeEmail($user, $email); return id(new AphrontRedirectResponse())->setURI($uri); @@ -283,7 +303,7 @@ final class PhabricatorEmailAddressesSettingsPanel $address = $email->getAddress(); $dialog = id(new AphrontDialogView()) - ->setUser($user) + ->setUser($viewer) ->addHiddenInput('delete', $email_id) ->setTitle(pht("Really delete address '%s'?", $address)) ->appendParagraph( @@ -304,8 +324,8 @@ final class PhabricatorEmailAddressesSettingsPanel AphrontRequest $request, PhutilURI $uri, $email_id) { - - $user = $request->getUser(); + $user = $this->getUser(); + $viewer = $this->getViewer(); // NOTE: You can only send more email for your unverified addresses. $email = id(new PhabricatorUserEmail())->loadOneWhere( @@ -325,7 +345,7 @@ final class PhabricatorEmailAddressesSettingsPanel $address = $email->getAddress(); $dialog = id(new AphrontDialogView()) - ->setUser($user) + ->setUser($viewer) ->addHiddenInput('verify', $email_id) ->setTitle(pht('Send Another Verification Email?')) ->appendChild(phutil_tag('p', array(), pht( @@ -341,11 +361,11 @@ final class PhabricatorEmailAddressesSettingsPanel AphrontRequest $request, PhutilURI $uri, $email_id) { - - $user = $request->getUser(); + $user = $this->getUser(); + $viewer = $this->getViewer(); $token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession( - $user, + $viewer, $request, $this->getPanelURI()); @@ -360,9 +380,8 @@ final class PhabricatorEmailAddressesSettingsPanel } if ($request->isFormPost()) { - id(new PhabricatorUserEditor()) - ->setActor($user) + ->setActor($viewer) ->changePrimaryEmail($user, $email); return id(new AphrontRedirectResponse())->setURI($uri); @@ -371,7 +390,7 @@ final class PhabricatorEmailAddressesSettingsPanel $address = $email->getAddress(); $dialog = id(new AphrontDialogView()) - ->setUser($user) + ->setUser($viewer) ->addHiddenInput('primary', $email_id) ->setTitle(pht('Change primary email address?')) ->appendParagraph( diff --git a/src/applications/settings/panel/PhabricatorEmailFormatSettingsPanel.php b/src/applications/settings/panel/PhabricatorEmailFormatSettingsPanel.php index f7dd7909ed..0d42149441 100644 --- a/src/applications/settings/panel/PhabricatorEmailFormatSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorEmailFormatSettingsPanel.php @@ -15,8 +15,17 @@ final class PhabricatorEmailFormatSettingsPanel return pht('Email'); } + public function isEditableByAdministrators() { + if ($this->getUser()->getIsMailingList()) { + return true; + } + + return false; + } + public function processRequest(AphrontRequest $request) { - $user = $request->getUser(); + $viewer = $this->getViewer(); + $user = $this->getUser(); $preferences = $user->loadPreferences(); @@ -98,7 +107,7 @@ final class PhabricatorEmailFormatSettingsPanel $form = new AphrontFormView(); $form - ->setUser($user); + ->setUser($viewer); if (PhabricatorMetaMTAMail::shouldMultiplexAllMail()) { $html_email_control = id(new AphrontFormSelectControl()) diff --git a/src/applications/settings/panel/PhabricatorEmailPreferencesSettingsPanel.php b/src/applications/settings/panel/PhabricatorEmailPreferencesSettingsPanel.php index 73c65dd7fe..13defb380e 100644 --- a/src/applications/settings/panel/PhabricatorEmailPreferencesSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorEmailPreferencesSettingsPanel.php @@ -15,8 +15,17 @@ final class PhabricatorEmailPreferencesSettingsPanel return pht('Email'); } + public function isEditableByAdministrators() { + if ($this->getUser()->getIsMailingList()) { + return true; + } + + return false; + } + public function processRequest(AphrontRequest $request) { - $user = $request->getUser(); + $viewer = $this->getViewer(); + $user = $this->getUser(); $preferences = $user->loadPreferences(); @@ -52,7 +61,7 @@ final class PhabricatorEmailPreferencesSettingsPanel $form = new AphrontFormView(); $form - ->setUser($user) + ->setUser($viewer) ->appendRemarkupInstructions( pht( 'These settings let you control how Phabricator notifies you about '. diff --git a/src/applications/settings/panel/PhabricatorSSHKeysSettingsPanel.php b/src/applications/settings/panel/PhabricatorSSHKeysSettingsPanel.php index a45e0223f5..2fa05914ec 100644 --- a/src/applications/settings/panel/PhabricatorSSHKeysSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorSSHKeysSettingsPanel.php @@ -19,6 +19,10 @@ final class PhabricatorSSHKeysSettingsPanel extends PhabricatorSettingsPanel { } public function isEnabled() { + if ($this->getUser()->getIsMailingList()) { + return false; + } + return true; } diff --git a/src/applications/spaces/__tests__/PhabricatorSpacesTestCase.php b/src/applications/spaces/__tests__/PhabricatorSpacesTestCase.php new file mode 100644 index 0000000000..f17d0f404a --- /dev/null +++ b/src/applications/spaces/__tests__/PhabricatorSpacesTestCase.php @@ -0,0 +1,233 @@ + true, + ); + } + + public function testSpacesAnnihilation() { + $this->destroyAllSpaces(); + + // Test that our helper methods work correctly. + + $actor = $this->generateNewTestUser(); + + $default = $this->newSpace($actor, pht('Test Space'), true); + $this->assertEqual(1, count($this->loadAllSpaces())); + $this->assertEqual( + 1, + count(PhabricatorSpacesNamespaceQuery::getAllSpaces())); + $cache_default = PhabricatorSpacesNamespaceQuery::getDefaultSpace(); + $this->assertEqual($default->getPHID(), $cache_default->getPHID()); + + $this->destroyAllSpaces(); + $this->assertEqual(0, count($this->loadAllSpaces())); + $this->assertEqual( + 0, + count(PhabricatorSpacesNamespaceQuery::getAllSpaces())); + $this->assertEqual( + null, + PhabricatorSpacesNamespaceQuery::getDefaultSpace()); + } + + public function testSpacesSeveralSpaces() { + $this->destroyAllSpaces(); + + // Try creating a few spaces, one of which is a default space. This should + // work fine. + + $actor = $this->generateNewTestUser(); + $default = $this->newSpace($actor, pht('Default Space'), true); + $this->newSpace($actor, pht('Alternate Space'), false); + $this->assertEqual(2, count($this->loadAllSpaces())); + $this->assertEqual( + 2, + count(PhabricatorSpacesNamespaceQuery::getAllSpaces())); + + $cache_default = PhabricatorSpacesNamespaceQuery::getDefaultSpace(); + $this->assertEqual($default->getPHID(), $cache_default->getPHID()); + } + + public function testSpacesRequireNames() { + $this->destroyAllSpaces(); + + // Spaces must have nonempty names. + + $actor = $this->generateNewTestUser(); + + $caught = null; + try { + $options = array( + 'continueOnNoEffect' => true, + ); + $this->newSpace($actor, '', true, $options); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $caught = $ex; + } + + $this->assertTrue(($caught instanceof Exception)); + } + + public function testSpacesUniqueDefaultSpace() { + $this->destroyAllSpaces(); + + // It shouldn't be possible to create two default spaces. + + $actor = $this->generateNewTestUser(); + $this->newSpace($actor, pht('Default Space'), true); + + $caught = null; + try { + $this->newSpace($actor, pht('Default Space #2'), true); + } catch (AphrontDuplicateKeyQueryException $ex) { + $caught = $ex; + } + + $this->assertTrue(($caught instanceof Exception)); + } + + public function testSpacesPolicyFiltering() { + $this->destroyAllSpaces(); + + $creator = $this->generateNewTestUser(); + $viewer = $this->generateNewTestUser(); + + // Create a new paste. + $paste = PhabricatorPaste::initializeNewPaste($creator) + ->setViewPolicy(PhabricatorPolicies::POLICY_USER) + ->setFilePHID('') + ->setLanguage('') + ->save(); + + // It should be visible. + $this->assertTrue( + PhabricatorPolicyFilter::hasCapability( + $viewer, + $paste, + PhabricatorPolicyCapability::CAN_VIEW)); + + // Create a default space with an open view policy. + $default = $this->newSpace($creator, pht('Default Space'), true) + ->setViewPolicy(PhabricatorPolicies::POLICY_USER) + ->save(); + PhabricatorSpacesNamespaceQuery::destroySpacesCache(); + + // The paste should now be in the space implicitly, but still visible + // because the space view policy is open. + $this->assertTrue( + PhabricatorPolicyFilter::hasCapability( + $viewer, + $paste, + PhabricatorPolicyCapability::CAN_VIEW)); + + // Make the space view policy restrictive. + $default + ->setViewPolicy(PhabricatorPolicies::POLICY_NOONE) + ->save(); + PhabricatorSpacesNamespaceQuery::destroySpacesCache(); + + // The paste should be in the space implicitly, and no longer visible. + $this->assertFalse( + PhabricatorPolicyFilter::hasCapability( + $viewer, + $paste, + PhabricatorPolicyCapability::CAN_VIEW)); + + // Put the paste in the space explicitly. + $paste + ->setSpacePHID($default->getPHID()) + ->save(); + PhabricatorSpacesNamespaceQuery::destroySpacesCache(); + + // This should still fail, we're just in the space explicitly now. + $this->assertFalse( + PhabricatorPolicyFilter::hasCapability( + $viewer, + $paste, + PhabricatorPolicyCapability::CAN_VIEW)); + + // Create an alternate space with more permissive policies, then move the + // paste to that space. + $alternate = $this->newSpace($creator, pht('Alternate Space'), false) + ->setViewPolicy(PhabricatorPolicies::POLICY_USER) + ->save(); + $paste + ->setSpacePHID($alternate->getPHID()) + ->save(); + PhabricatorSpacesNamespaceQuery::destroySpacesCache(); + + // Now the paste should be visible again. + $this->assertTrue( + PhabricatorPolicyFilter::hasCapability( + $viewer, + $paste, + PhabricatorPolicyCapability::CAN_VIEW)); + } + + private function loadAllSpaces() { + return id(new PhabricatorSpacesNamespaceQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->execute(); + } + + private function destroyAllSpaces() { + PhabricatorSpacesNamespaceQuery::destroySpacesCache(); + $spaces = $this->loadAllSpaces(); + foreach ($spaces as $space) { + $engine = new PhabricatorDestructionEngine(); + $engine->destroyObject($space); + } + } + + private function newSpace( + PhabricatorUser $actor, + $name, + $is_default, + array $options = array()) { + + $space = PhabricatorSpacesNamespace::initializeNewNamespace($actor); + + $type_name = PhabricatorSpacesNamespaceTransaction::TYPE_NAME; + $type_default = PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT; + $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; + $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; + + $xactions = array(); + + $xactions[] = id(new PhabricatorSpacesNamespaceTransaction()) + ->setTransactionType($type_name) + ->setNewValue($name); + + $xactions[] = id(new PhabricatorSpacesNamespaceTransaction()) + ->setTransactionType($type_view) + ->setNewValue($actor->getPHID()); + + $xactions[] = id(new PhabricatorSpacesNamespaceTransaction()) + ->setTransactionType($type_edit) + ->setNewValue($actor->getPHID()); + + if ($is_default) { + $xactions[] = id(new PhabricatorSpacesNamespaceTransaction()) + ->setTransactionType($type_default) + ->setNewValue($is_default); + } + + $content_source = PhabricatorContentSource::newConsoleSource(); + + $editor = id(new PhabricatorSpacesNamespaceEditor()) + ->setActor($actor) + ->setContentSource($content_source); + + if (isset($options['continueOnNoEffect'])) { + $editor->setContinueOnNoEffect(true); + } + + $editor->applyTransactions($space, $xactions); + + return $space; + } + +} diff --git a/src/applications/spaces/application/PhabricatorSpacesApplication.php b/src/applications/spaces/application/PhabricatorSpacesApplication.php new file mode 100644 index 0000000000..380f80569e --- /dev/null +++ b/src/applications/spaces/application/PhabricatorSpacesApplication.php @@ -0,0 +1,73 @@ +[1-9]\d*)' => 'PhabricatorSpacesViewController', + '/spaces/' => array( + '(?:query/(?P[^/]+)/)?' => 'PhabricatorSpacesListController', + 'create/' => 'PhabricatorSpacesEditController', + 'edit/(?:(?P\d+)/)?' => 'PhabricatorSpacesEditController', + ), + ); + } + + protected function getCustomCapabilities() { + return array( + PhabricatorSpacesCapabilityCreateSpaces::CAPABILITY => array( + 'default' => PhabricatorPolicies::POLICY_ADMIN, + ), + PhabricatorSpacesCapabilityDefaultView::CAPABILITY => array( + 'caption' => pht('Default view policy for newly created spaces.'), + ), + PhabricatorSpacesCapabilityDefaultEdit::CAPABILITY => array( + 'caption' => pht('Default edit policy for newly created spaces.'), + 'default' => PhabricatorPolicies::POLICY_ADMIN, + ), + ); + } + +} diff --git a/src/applications/spaces/capability/PhabricatorSpacesCapabilityCreateSpaces.php b/src/applications/spaces/capability/PhabricatorSpacesCapabilityCreateSpaces.php new file mode 100644 index 0000000000..4cecae1d88 --- /dev/null +++ b/src/applications/spaces/capability/PhabricatorSpacesCapabilityCreateSpaces.php @@ -0,0 +1,16 @@ +getUser(); + + $make_default = false; + + $id = $request->getURIData('id'); + if ($id) { + $space = id(new PhabricatorSpacesNamespaceQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$space) { + return new Aphront404Response(); + } + + $is_new = false; + $cancel_uri = '/'.$space->getMonogram(); + + $header_text = pht('Edit %s', $space->getNamespaceName()); + $title = pht('Edit Space'); + $button_text = pht('Save Changes'); + } else { + $this->requireApplicationCapability( + PhabricatorSpacesCapabilityCreateSpaces::CAPABILITY); + + $space = PhabricatorSpacesNamespace::initializeNewNamespace($viewer); + + $is_new = true; + $cancel_uri = $this->getApplicationURI(); + + $header_text = pht('Create Space'); + $title = pht('Create Space'); + $button_text = pht('Create Space'); + + $default = id(new PhabricatorSpacesNamespaceQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withIsDefaultNamespace(true) + ->execute(); + if (!$default) { + $make_default = true; + } + } + + $validation_exception = null; + $e_name = true; + $v_name = $space->getNamespaceName(); + $v_view = $space->getViewPolicy(); + $v_edit = $space->getEditPolicy(); + + if ($request->isFormPost()) { + $xactions = array(); + $e_name = null; + + $v_name = $request->getStr('name'); + $v_view = $request->getStr('viewPolicy'); + $v_edit = $request->getStr('editPolicy'); + + $type_name = PhabricatorSpacesNamespaceTransaction::TYPE_NAME; + $type_default = PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT; + $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; + $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; + + $xactions[] = id(new PhabricatorSpacesNamespaceTransaction()) + ->setTransactionType($type_name) + ->setNewValue($v_name); + + $xactions[] = id(new PhabricatorSpacesNamespaceTransaction()) + ->setTransactionType($type_view) + ->setNewValue($v_view); + + $xactions[] = id(new PhabricatorSpacesNamespaceTransaction()) + ->setTransactionType($type_edit) + ->setNewValue($v_edit); + + if ($make_default) { + $xactions[] = id(new PhabricatorSpacesNamespaceTransaction()) + ->setTransactionType($type_default) + ->setNewValue(1); + } + + $editor = id(new PhabricatorSpacesNamespaceEditor()) + ->setActor($viewer) + ->setContinueOnNoEffect(true) + ->setContentSourceFromRequest($request); + + try { + $editor->applyTransactions($space, $xactions); + + return id(new AphrontRedirectResponse()) + ->setURI('/'.$space->getMonogram()); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $validation_exception = $ex; + + $e_name = $ex->getShortMessage($type_name); + } + } + + $policies = id(new PhabricatorPolicyQuery()) + ->setViewer($viewer) + ->setObject($space) + ->execute(); + + $form = id(new AphrontFormView()) + ->setUser($viewer); + + if ($make_default) { + $form->appendRemarkupInstructions( + pht( + 'NOTE: You are creating the **default space**. All existing '. + 'objects will be put into this space. You must create a default '. + 'space before you can create other spaces.')); + } + + $form + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel(pht('Name')) + ->setName('name') + ->setValue($v_name) + ->setError($e_name)) + ->appendChild( + id(new AphrontFormPolicyControl()) + ->setUser($viewer) + ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) + ->setPolicyObject($space) + ->setPolicies($policies) + ->setValue($v_view) + ->setName('viewPolicy')) + ->appendChild( + id(new AphrontFormPolicyControl()) + ->setUser($viewer) + ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) + ->setPolicyObject($space) + ->setPolicies($policies) + ->setValue($v_edit) + ->setName('editPolicy')) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue($button_text) + ->addCancelButton($cancel_uri)); + + $box = id(new PHUIObjectBoxView()) + ->setHeaderText($header_text) + ->setValidationException($validation_exception) + ->appendChild($form); + + $crumbs = $this->buildApplicationCrumbs(); + if (!$is_new) { + $crumbs->addTextCrumb( + $space->getMonogram(), + $cancel_uri); + } + $crumbs->addTextCrumb($title); + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + ), + array( + 'title' => $title, + )); + } +} diff --git a/src/applications/spaces/controller/PhabricatorSpacesListController.php b/src/applications/spaces/controller/PhabricatorSpacesListController.php new file mode 100644 index 0000000000..fe439b0097 --- /dev/null +++ b/src/applications/spaces/controller/PhabricatorSpacesListController.php @@ -0,0 +1,52 @@ +getRequest(); + $controller = id(new PhabricatorApplicationSearchController($request)) + ->setQueryKey($request->getURIData('queryKey')) + ->setSearchEngine(new PhabricatorSpacesNamespaceSearchEngine()) + ->setNavigation($this->buildSideNavView()); + + return $this->delegateToController($controller); + } + + public function buildSideNavView($for_app = false) { + $user = $this->getRequest()->getUser(); + + $nav = new AphrontSideNavFilterView(); + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); + + id(new PhabricatorSpacesNamespaceSearchEngine()) + ->setViewer($user) + ->addNavigationItems($nav->getMenu()); + + $nav->selectFilter(null); + + return $nav; + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $can_create = $this->hasApplicationCapability( + PhabricatorSpacesCapabilityCreateSpaces::CAPABILITY); + + $crumbs->addAction( + id(new PHUIListItemView()) + ->setName(pht('Create Space')) + ->setHref($this->getApplicationURI('create/')) + ->setIcon('fa-plus-square') + ->setDisabled(!$can_create) + ->setWorkflow(!$can_create)); + + return $crumbs; + } + +} diff --git a/src/applications/spaces/controller/PhabricatorSpacesViewController.php b/src/applications/spaces/controller/PhabricatorSpacesViewController.php new file mode 100644 index 0000000000..911549234f --- /dev/null +++ b/src/applications/spaces/controller/PhabricatorSpacesViewController.php @@ -0,0 +1,104 @@ +getViewer(); + + $space = id(new PhabricatorSpacesNamespaceQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) + ->executeOne(); + if (!$space) { + return new Aphront404Response(); + } + + $action_list = $this->buildActionListView($space); + $property_list = $this->buildPropertyListView($space); + $property_list->setActionList($action_list); + + $xactions = id(new PhabricatorSpacesNamespaceTransactionQuery()) + ->setViewer($viewer) + ->withObjectPHIDs(array($space->getPHID())) + ->execute(); + + $timeline = $this->buildTransactionTimeline( + $space, + new PhabricatorSpacesNamespaceTransactionQuery()); + $timeline->setShouldTerminate(true); + + $header = id(new PHUIHeaderView()) + ->setUser($viewer) + ->setHeader($space->getNamespaceName()) + ->setPolicyObject($space); + + $box = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->addPropertyList($property_list); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb($space->getMonogram()); + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + $timeline, + ), + array( + 'title' => array($space->getMonogram(), $space->getNamespaceName()), + )); + } + + private function buildPropertyListView(PhabricatorSpacesNamespace $space) { + $viewer = $this->getRequest()->getUser(); + + $list = id(new PHUIPropertyListView()) + ->setUser($viewer); + + $list->addProperty( + pht('Default Space'), + $space->getIsDefaultNamespace() + ? pht('Yes') + : pht('No')); + + $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( + $viewer, + $space); + + $list->addProperty( + pht('Editable By'), + $descriptions[PhabricatorPolicyCapability::CAN_EDIT]); + + return $list; + } + + private function buildActionListView(PhabricatorSpacesNamespace $space) { + $viewer = $this->getRequest()->getUser(); + + $list = id(new PhabricatorActionListView()) + ->setUser($viewer) + ->setObjectURI('/'.$space->getMonogram()); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $space, + PhabricatorPolicyCapability::CAN_EDIT); + + $list->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Space')) + ->setIcon('fa-pencil') + ->setHref($this->getApplicationURI('edit/'.$space->getID().'/')) + ->setWorkflow(!$can_edit) + ->setDisabled(!$can_edit)); + + return $list; + } + +} diff --git a/src/applications/spaces/editor/PhabricatorSpacesNamespaceEditor.php b/src/applications/spaces/editor/PhabricatorSpacesNamespaceEditor.php new file mode 100644 index 0000000000..23b01531e9 --- /dev/null +++ b/src/applications/spaces/editor/PhabricatorSpacesNamespaceEditor.php @@ -0,0 +1,132 @@ +getTransactionType()) { + case PhabricatorSpacesNamespaceTransaction::TYPE_NAME: + $name = $object->getNamespaceName(); + if (!strlen($name)) { + return null; + } + return $name; + case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT: + return $object->getIsDefaultNamespace() ? 1 : null; + case PhabricatorTransactions::TYPE_VIEW_POLICY: + return $object->getViewPolicy(); + case PhabricatorTransactions::TYPE_EDIT_POLICY: + return $object->getEditPolicy(); + } + + return parent::getCustomTransactionOldValue($object, $xaction); + } + + protected function getCustomTransactionNewValue( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorSpacesNamespaceTransaction::TYPE_NAME: + case PhabricatorTransactions::TYPE_VIEW_POLICY: + case PhabricatorTransactions::TYPE_EDIT_POLICY: + return $xaction->getNewValue(); + case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT: + return $xaction->getNewValue() ? 1 : null; + } + + return parent::getCustomTransactionNewValue($object, $xaction); + } + + protected function applyCustomInternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + $new = $xaction->getNewValue(); + + switch ($xaction->getTransactionType()) { + case PhabricatorSpacesNamespaceTransaction::TYPE_NAME: + $object->setNamespaceName($new); + return; + case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT: + $object->setIsDefaultNamespace($new ? 1 : null); + return; + case PhabricatorTransactions::TYPE_VIEW_POLICY: + $object->setViewPolicy($new); + return; + case PhabricatorTransactions::TYPE_EDIT_POLICY: + $object->setEditPolicy($new); + return; + } + + return parent::applyCustomInternalTransaction($object, $xaction); + } + + protected function applyCustomExternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorSpacesNamespaceTransaction::TYPE_NAME: + case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT: + case PhabricatorTransactions::TYPE_VIEW_POLICY: + case PhabricatorTransactions::TYPE_EDIT_POLICY: + return; + } + + return parent::applyCustomExternalTransaction($object, $xaction); + } + + protected function validateTransaction( + PhabricatorLiskDAO $object, + $type, + array $xactions) { + + $errors = parent::validateTransaction($object, $type, $xactions); + + switch ($type) { + case PhabricatorSpacesNamespaceTransaction::TYPE_NAME: + $missing = $this->validateIsEmptyTextField( + $object->getNamespaceName(), + $xactions); + + if ($missing) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Required'), + pht('Spaces must have a name.'), + nonempty(last($xactions), null)); + + $error->setIsMissingFieldError(true); + $errors[] = $error; + } + break; + } + + return $errors; + } + +} diff --git a/src/applications/spaces/interface/PhabricatorSpacesInterface.php b/src/applications/spaces/interface/PhabricatorSpacesInterface.php new file mode 100644 index 0000000000..d60fd06281 --- /dev/null +++ b/src/applications/spaces/interface/PhabricatorSpacesInterface.php @@ -0,0 +1,18 @@ +spacePHID; + } + +*/ diff --git a/src/applications/mailinglists/phid/PhabricatorMailingListListPHIDType.php b/src/applications/spaces/phid/PhabricatorSpacesNamespacePHIDType.php similarity index 53% rename from src/applications/mailinglists/phid/PhabricatorMailingListListPHIDType.php rename to src/applications/spaces/phid/PhabricatorSpacesNamespacePHIDType.php index 367a771144..22dfcffac4 100644 --- a/src/applications/mailinglists/phid/PhabricatorMailingListListPHIDType.php +++ b/src/applications/spaces/phid/PhabricatorSpacesNamespacePHIDType.php @@ -1,26 +1,27 @@ withPHIDs($phids); } @@ -30,15 +31,16 @@ final class PhabricatorMailingListListPHIDType extends PhabricatorPHIDType { array $objects) { foreach ($handles as $phid => $handle) { - $list = $objects[$phid]; + $namespace = $objects[$phid]; + $monogram = $namespace->getMonogram(); - $handle->setName($list->getName()); - $handle->setURI($list->getURI()); + $handle->setName($namespace->getNamespaceName()); + $handle->setURI('/'.$monogram); } } public function canLoadNamedObject($name) { - return preg_match('/^.+@.+/', $name); + return preg_match('/^S[1-9]\d*$/i', $name); } public function loadNamedObjects( @@ -47,20 +49,18 @@ final class PhabricatorMailingListListPHIDType extends PhabricatorPHIDType { $id_map = array(); foreach ($names as $name) { - // Maybe normalize these some day? - $id = $name; + $id = (int)substr($name, 1); $id_map[$id][] = $name; } - $objects = id(new PhabricatorMailingListQuery()) + $objects = id(new PhabricatorSpacesNamespaceQuery()) ->setViewer($query->getViewer()) - ->withEmails(array_keys($id_map)) + ->withIDs(array_keys($id_map)) ->execute(); $results = array(); foreach ($objects as $id => $object) { - $email = $object->getEmail(); - foreach (idx($id_map, $email, array()) as $name) { + foreach (idx($id_map, $id, array()) as $name) { $results[$name] = $object; } } diff --git a/src/applications/spaces/query/PhabricatorSpacesNamespaceQuery.php b/src/applications/spaces/query/PhabricatorSpacesNamespaceQuery.php new file mode 100644 index 0000000000..ac02c306e7 --- /dev/null +++ b/src/applications/spaces/query/PhabricatorSpacesNamespaceQuery.php @@ -0,0 +1,177 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withIsDefaultNamespace($default) { + $this->isDefaultNamespace = $default; + return $this; + } + + public function getQueryApplicationClass() { + return 'PhabricatorSpacesApplication'; + } + + protected function loadPage() { + $table = new PhabricatorSpacesNamespace(); + $conn_r = $table->establishConnection('r'); + + $rows = queryfx_all( + $conn_r, + 'SELECT * FROM %T %Q %Q %Q', + $table->getTableName(), + $this->buildWhereClause($conn_r), + $this->buildOrderClause($conn_r), + $this->buildLimitClause($conn_r)); + + return $table->loadAllFromArray($rows); + } + + protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + $where = array(); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn_r, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn_r, + 'phid IN (%Ls)', + $this->phids); + } + + if ($this->isDefaultNamespace !== null) { + if ($this->isDefaultNamespace) { + $where[] = qsprintf( + $conn_r, + 'isDefaultNamespace = 1'); + } else { + $where[] = qsprintf( + $conn_r, + 'isDefaultNamespace IS NULL'); + } + } + + $where[] = $this->buildPagingClause($conn_r); + return $this->formatWhereClause($where); + } + + public static function destroySpacesCache() { + $cache = PhabricatorCaches::getRequestCache(); + $cache->deleteKeys( + array( + self::KEY_ALL, + self::KEY_DEFAULT, + )); + } + + public static function getSpacesExist() { + return (bool)self::getAllSpaces(); + } + + public static function getAllSpaces() { + $cache = PhabricatorCaches::getRequestCache(); + $cache_key = self::KEY_ALL; + + $spaces = $cache->getKey($cache_key); + if ($spaces === null) { + $spaces = id(new PhabricatorSpacesNamespaceQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->execute(); + $spaces = mpull($spaces, null, 'getPHID'); + $cache->setKey($cache_key, $spaces); + } + + return $spaces; + } + + public static function getDefaultSpace() { + $cache = PhabricatorCaches::getRequestCache(); + $cache_key = self::KEY_DEFAULT; + + $default_space = $cache->getKey($cache_key, false); + if ($default_space === false) { + $default_space = null; + + $spaces = self::getAllSpaces(); + foreach ($spaces as $space) { + if ($space->getIsDefaultNamespace()) { + $default_space = $space; + break; + } + } + + $cache->setKey($cache_key, $default_space); + } + + return $default_space; + } + + public static function getViewerSpaces(PhabricatorUser $viewer) { + $spaces = self::getAllSpaces(); + + $result = array(); + foreach ($spaces as $key => $space) { + $can_see = PhabricatorPolicyFilter::hasCapability( + $viewer, + $space, + PhabricatorPolicyCapability::CAN_VIEW); + if ($can_see) { + $result[$key] = $space; + } + } + + return $result; + } + + /** + * Get the Space PHID for an object, if one exists. + * + * This is intended to simplify performing a bunch of redundant checks; you + * can intentionally pass any value in (including `null`). + * + * @param wild + * @return phid|null + */ + public static function getObjectSpacePHID($object) { + if (!$object) { + return null; + } + + if (!($object instanceof PhabricatorSpacesInterface)) { + return null; + } + + $space_phid = $object->getSpacePHID(); + if ($space_phid === null) { + $default_space = self::getDefaultSpace(); + if ($default_space) { + $space_phid = $default_space->getPHID(); + } + } + + return $space_phid; + } + +} diff --git a/src/applications/spaces/query/PhabricatorSpacesNamespaceSearchEngine.php b/src/applications/spaces/query/PhabricatorSpacesNamespaceSearchEngine.php new file mode 100644 index 0000000000..9b4014c3dd --- /dev/null +++ b/src/applications/spaces/query/PhabricatorSpacesNamespaceSearchEngine.php @@ -0,0 +1,81 @@ + pht('All Spaces'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function renderResultList( + array $spaces, + PhabricatorSavedQuery $query, + array $handles) { + assert_instances_of($spaces, 'PhabricatorSpacesNamespace'); + + $viewer = $this->requireViewer(); + + $list = new PHUIObjectItemListView(); + $list->setUser($viewer); + foreach ($spaces as $space) { + $item = id(new PHUIObjectItemView()) + ->setObjectName($space->getMonogram()) + ->setHeader($space->getNamespaceName()) + ->setHref('/'.$space->getMonogram()); + + if ($space->getIsDefaultNamespace()) { + $item->addIcon('fa-certificate', pht('Default Space')); + } + + $list->addItem($item); + } + + return $list; + } + +} diff --git a/src/applications/spaces/query/PhabricatorSpacesNamespaceTransactionQuery.php b/src/applications/spaces/query/PhabricatorSpacesNamespaceTransactionQuery.php new file mode 100644 index 0000000000..34d7e43570 --- /dev/null +++ b/src/applications/spaces/query/PhabricatorSpacesNamespaceTransactionQuery.php @@ -0,0 +1,10 @@ +getEngine()->getConfig('viewer'); + return id(new PhabricatorSpacesNamespaceQuery()) + ->setViewer($viewer) + ->withIDs($ids) + ->execute(); + } + +} diff --git a/src/applications/spaces/storage/PhabricatorSpacesDAO.php b/src/applications/spaces/storage/PhabricatorSpacesDAO.php new file mode 100644 index 0000000000..0fcae93fb6 --- /dev/null +++ b/src/applications/spaces/storage/PhabricatorSpacesDAO.php @@ -0,0 +1,9 @@ +setViewer($actor) + ->withClasses(array('PhabricatorSpacesApplication')) + ->executeOne(); + + $view_policy = $app->getPolicy( + PhabricatorSpacesCapabilityDefaultView::CAPABILITY); + $edit_policy = $app->getPolicy( + PhabricatorSpacesCapabilityDefaultEdit::CAPABILITY); + + return id(new PhabricatorSpacesNamespace()) + ->setIsDefaultNamespace(null) + ->setViewPolicy($view_policy) + ->setEditPolicy($edit_policy); + } + + protected function getConfiguration() { + return array( + self::CONFIG_AUX_PHID => true, + self::CONFIG_COLUMN_SCHEMA => array( + 'namespaceName' => 'text255', + 'isDefaultNamespace' => 'bool?', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_default' => array( + 'columns' => array('isDefaultNamespace'), + 'unique' => true, + ), + ), + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + PhabricatorSpacesNamespacePHIDType::TYPECONST); + } + + public function getMonogram() { + return 'S'.$this->getID(); + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function getPolicy($capability) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return $this->getViewPolicy(); + case PhabricatorPolicyCapability::CAN_EDIT: + return $this->getEditPolicy(); + } + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new PhabricatorSpacesNamespaceEditor(); + } + + public function getApplicationTransactionObject() { + return $this; + } + + public function getApplicationTransactionTemplate() { + return new PhabricatorSpacesNamespaceTransaction(); + } + + public function willRenderTimeline( + PhabricatorApplicationTransactionView $timeline, + AphrontRequest $request) { + return $timeline; + } + + +/* -( PhabricatorDestructibleInterface )----------------------------------- */ + + + public function destroyObjectPermanently( + PhabricatorDestructionEngine $engine) { + $this->delete(); + } + +} diff --git a/src/applications/spaces/storage/PhabricatorSpacesNamespaceTransaction.php b/src/applications/spaces/storage/PhabricatorSpacesNamespaceTransaction.php new file mode 100644 index 0000000000..672cb9d8ad --- /dev/null +++ b/src/applications/spaces/storage/PhabricatorSpacesNamespaceTransaction.php @@ -0,0 +1,49 @@ +getOldValue(); + $new = $this->getNewValue(); + + $author_phid = $this->getAuthorPHID(); + + switch ($this->getTransactionType()) { + case self::TYPE_NAME: + if ($old === null) { + return pht( + '%s created this space.', + $this->renderHandleLink($author_phid)); + } else { + return pht( + '%s renamed this space from "%s" to "%s".', + $this->renderHandleLink($author_phid), + $old, + $new); + } + case self::TYPE_DEFAULT: + return pht( + '%s made this the default space.', + $this->renderHandleLink($author_phid)); + } + + return parent::getTitle(); + } + +} diff --git a/src/applications/spaces/typeahead/PhabricatorSpacesNamespaceDatasource.php b/src/applications/spaces/typeahead/PhabricatorSpacesNamespaceDatasource.php new file mode 100644 index 0000000000..046e3b0f4f --- /dev/null +++ b/src/applications/spaces/typeahead/PhabricatorSpacesNamespaceDatasource.php @@ -0,0 +1,32 @@ +executeQuery($query); + $results = array(); + foreach ($spaces as $space) { + $results[] = id(new PhabricatorTypeaheadResult()) + ->setName($space->getNamespaceName()) + ->setPHID($space->getPHID()); + } + + return $this->filterResultsAgainstTokens($results); + } + +} diff --git a/src/applications/spaces/view/PHUISpacesNamespaceContextView.php b/src/applications/spaces/view/PHUISpacesNamespaceContextView.php new file mode 100644 index 0000000000..ed28f53349 --- /dev/null +++ b/src/applications/spaces/view/PHUISpacesNamespaceContextView.php @@ -0,0 +1,36 @@ +object = $object; + return $this; + } + + public function getObject() { + return $this->object; + } + + public function render() { + $object = $this->getObject(); + + $space_phid = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID($object); + if (!$space_phid) { + return null; + } + + $viewer = $this->getUser(); + return phutil_tag( + 'span', + array( + 'class' => 'spaces-name', + ), + array( + $viewer->renderHandle($space_phid), + ' | ', + )); + } + +} diff --git a/src/applications/spaces/view/PhabricatorSpacesControl.php b/src/applications/spaces/view/PhabricatorSpacesControl.php new file mode 100644 index 0000000000..aa40238c56 --- /dev/null +++ b/src/applications/spaces/view/PhabricatorSpacesControl.php @@ -0,0 +1,49 @@ +object = $object; + return $this; + } + + protected function getCustomControlClass() { + return ''; + } + + protected function getOptions() { + $viewer = $this->getUser(); + $viewer_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($viewer); + + $map = mpull($viewer_spaces, 'getNamespaceName', 'getPHID'); + asort($map); + + return $map; + } + + protected function renderInput() { + $viewer = $this->getUser(); + + $this->setLabel(pht('Space')); + + $value = $this->getValue(); + if ($value === null) { + $value = $viewer->getDefaultSpacePHID(); + } + + return AphrontFormSelectControl::renderSelectTag( + $value, + $this->getOptions(), + array( + 'name' => $this->getName(), + )); + } + +} diff --git a/src/applications/system/engine/PhabricatorDestructionEngine.php b/src/applications/system/engine/PhabricatorDestructionEngine.php index eddc2f73fb..fd7ba7c30a 100644 --- a/src/applications/system/engine/PhabricatorDestructionEngine.php +++ b/src/applications/system/engine/PhabricatorDestructionEngine.php @@ -68,8 +68,30 @@ final class PhabricatorDestructionEngine extends Phobject { } // TODO: Remove stuff from search indexes? - // TODO: PhabricatorFlaggableInterface - // TODO: PhabricatorTokenReceiverInterface + + if ($object instanceof PhabricatorFlaggableInterface) { + $flags = id(new PhabricatorFlag())->loadAllWhere( + 'objectPHID = %s', $object_phid); + + foreach ($flags as $flag) { + $flag->delete(); + } + } + + $flags = id(new PhabricatorFlag())->loadAllWhere( + 'ownerPHID = %s', $object_phid); + foreach ($flags as $flag) { + $flag->delete(); + } + + if ($object instanceof PhabricatorTokenReceiverInterface) { + $tokens = id(new PhabricatorTokenGiven())->loadAllWhere( + 'objectPHID = %s', $object_phid); + + foreach ($tokens as $token) { + $token->delete(); + } + } } private function destroyEdges($src_phid) { @@ -116,7 +138,7 @@ final class PhabricatorDestructionEngine extends Phobject { } private function destroyNotifications($object_phid) { - $table = id(new PhabricatorFeedStoryNotification()); + $table = new PhabricatorFeedStoryNotification(); $conn_w = $table->establishConnection('w'); queryfx( diff --git a/src/applications/transactions/constants/PhabricatorTransactions.php b/src/applications/transactions/constants/PhabricatorTransactions.php index 5abc9fa486..6bdbd2f2db 100644 --- a/src/applications/transactions/constants/PhabricatorTransactions.php +++ b/src/applications/transactions/constants/PhabricatorTransactions.php @@ -12,6 +12,7 @@ final class PhabricatorTransactions { const TYPE_BUILDABLE = 'harbormaster:buildable'; const TYPE_TOKEN = 'token:give'; const TYPE_INLINESTATE = 'core:inlinestate'; + const TYPE_SPACE = 'core:space'; const COLOR_RED = 'red'; const COLOR_ORANGE = 'orange'; diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index 7c8a1cff42..d89c782900 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -1,10 +1,41 @@ object instanceof PhabricatorSpacesInterface) { + $types[] = PhabricatorTransactions::TYPE_SPACE; + } + return $types; } @@ -254,6 +296,21 @@ abstract class PhabricatorApplicationTransactionEditor return $object->getEditPolicy(); case PhabricatorTransactions::TYPE_JOIN_POLICY: return $object->getJoinPolicy(); + case PhabricatorTransactions::TYPE_SPACE: + $space_phid = $object->getSpacePHID(); + if ($space_phid === null) { + if ($this->getIsNewObject()) { + // In this case, just return `null` so we know this is the initial + // transaction and it should be hidden. + return null; + } + + $default_space = PhabricatorSpacesNamespaceQuery::getDefaultSpace(); + if ($default_space) { + $space_phid = $default_space->getPHID(); + } + } + return $space_phid; case PhabricatorTransactions::TYPE_EDGE: $edge_type = $xaction->getMetadataValue('edge:type'); if (!$edge_type) { @@ -300,6 +357,16 @@ abstract class PhabricatorApplicationTransactionEditor case PhabricatorTransactions::TYPE_TOKEN: case PhabricatorTransactions::TYPE_INLINESTATE: return $xaction->getNewValue(); + case PhabricatorTransactions::TYPE_SPACE: + $space_phid = $xaction->getNewValue(); + if (!strlen($space_phid)) { + // If an install has no Spaces, we might end up with the empty string + // here instead of a strict `null`. Just make this work like callers + // might reasonably expect. + return null; + } else { + return $space_phid; + } case PhabricatorTransactions::TYPE_EDGE: return $this->getEdgeTransactionNewValue($xaction); case PhabricatorTransactions::TYPE_CUSTOMFIELD: @@ -399,6 +466,7 @@ abstract class PhabricatorApplicationTransactionEditor case PhabricatorTransactions::TYPE_SUBSCRIBERS: case PhabricatorTransactions::TYPE_INLINESTATE: case PhabricatorTransactions::TYPE_EDGE: + case PhabricatorTransactions::TYPE_SPACE: case PhabricatorTransactions::TYPE_COMMENT: return $this->applyBuiltinInternalTransaction($object, $xaction); } @@ -447,6 +515,7 @@ abstract class PhabricatorApplicationTransactionEditor case PhabricatorTransactions::TYPE_EDIT_POLICY: case PhabricatorTransactions::TYPE_JOIN_POLICY: case PhabricatorTransactions::TYPE_INLINESTATE: + case PhabricatorTransactions::TYPE_SPACE: case PhabricatorTransactions::TYPE_COMMENT: return $this->applyBuiltinExternalTransaction($object, $xaction); } @@ -499,6 +568,9 @@ abstract class PhabricatorApplicationTransactionEditor case PhabricatorTransactions::TYPE_JOIN_POLICY: $object->setJoinPolicy($xaction->getNewValue()); break; + case PhabricatorTransactions::TYPE_SPACE: + $object->setSpacePHID($xaction->getNewValue()); + break; } } @@ -764,7 +836,12 @@ abstract class PhabricatorApplicationTransactionEditor $xactions = $this->didApplyInternalEffects($object, $xactions); - $object->save(); + try { + $object->save(); + } catch (AphrontDuplicateKeyQueryException $ex) { + $object->killTransaction(); + throw $ex; + } foreach ($xactions as $xaction) { $xaction->setObjectPHID($object->getPHID()); @@ -856,46 +933,16 @@ abstract class PhabricatorApplicationTransactionEditor $object, $herald_xactions); + $adapter = $this->getHeraldAdapter(); + $this->heraldEmailPHIDs = $adapter->getEmailPHIDs(); + $this->heraldForcedEmailPHIDs = $adapter->getForcedEmailPHIDs(); + // Merge the new transactions into the transaction list: we want to // send email and publish feed stories about them, too. $xactions = array_merge($xactions, $herald_xactions); } } - // Before sending mail or publishing feed stories, reload the object - // subscribers to pick up changes caused by Herald (or by other side effects - // in various transaction phases). - $this->loadSubscribers($object); - // Hook for other edges that may need (re-)loading - $this->loadEdges($object, $xactions); - - $this->loadHandles($xactions); - - $mail = null; - if (!$this->getDisableEmail()) { - if ($this->shouldSendMail($object, $xactions)) { - $mail = $this->sendMail($object, $xactions); - } - } - - if ($this->supportsSearch()) { - id(new PhabricatorSearchIndexer()) - ->queueDocumentForIndexing( - $object->getPHID(), - $this->getSearchContextParameter($object, $xactions)); - } - - if ($this->shouldPublishFeedStory($object, $xactions)) { - $mailed = array(); - if ($mail) { - $mailed = $mail->buildRecipientList(); - } - $this->publishFeedStory( - $object, - $xactions, - $mailed); - } - $this->didApplyTransactions($xactions); if ($object instanceof PhabricatorCustomFieldInterface) { @@ -916,6 +963,86 @@ abstract class PhabricatorApplicationTransactionEditor $fields->rebuildIndexes($object); } + $herald_xscript = $this->getHeraldTranscript(); + if ($herald_xscript) { + $herald_header = $herald_xscript->getXHeraldRulesHeader(); + $herald_header = HeraldTranscript::saveXHeraldRulesHeader( + $object->getPHID(), + $herald_header); + } else { + $herald_header = HeraldTranscript::loadXHeraldRulesHeader( + $object->getPHID()); + } + $this->heraldHeader = $herald_header; + + // We're going to compute some of the data we'll use to publish these + // transactions here, before queueing a worker. + // + // Primarily, this is more correct: we want to publish the object as it + // exists right now. The worker may not execute for some time, and we want + // to use the current To/CC list, not respect any changes which may occur + // between now and when the worker executes. + // + // As a secondary benefit, this tends to reduce the amount of state that + // Editors need to pass into workers. + $object = $this->willPublish($object, $xactions); + + if (!$this->getDisableEmail()) { + if ($this->shouldSendMail($object, $xactions)) { + $this->mailToPHIDs = $this->getMailTo($object); + $this->mailCCPHIDs = $this->getMailCC($object); + } + } + + if ($this->shouldPublishFeedStory($object, $xactions)) { + $this->feedRelatedPHIDs = $this->getFeedRelatedPHIDs($object, $xactions); + $this->feedNotifyPHIDs = $this->getFeedNotifyPHIDs($object, $xactions); + } + + PhabricatorWorker::scheduleTask( + 'PhabricatorApplicationTransactionPublishWorker', + array( + 'objectPHID' => $object->getPHID(), + 'actorPHID' => $this->getActingAsPHID(), + 'xactionPHIDs' => mpull($xactions, 'getPHID'), + 'state' => $this->getWorkerState(), + ), + array( + 'objectPHID' => $object->getPHID(), + 'priority' => PhabricatorWorker::PRIORITY_ALERTS, + )); + + return $xactions; + } + + public function publishTransactions( + PhabricatorLiskDAO $object, + array $xactions) { + + // Hook for edges or other properties that may need (re-)loading + $object = $this->willPublish($object, $xactions); + + $mailed = array(); + if (!$this->getDisableEmail()) { + if ($this->shouldSendMail($object, $xactions)) { + $mailed = $this->sendMail($object, $xactions); + } + } + + if ($this->supportsSearch()) { + id(new PhabricatorSearchIndexer()) + ->queueDocumentForIndexing( + $object->getPHID(), + $this->getSearchContextParameter($object, $xactions)); + } + + if ($this->shouldPublishFeedStory($object, $xactions)) { + $this->publishFeedStory( + $object, + $xactions, + $mailed); + } + return $xactions; } @@ -993,12 +1120,6 @@ abstract class PhabricatorApplicationTransactionEditor } } - protected function loadEdges( - PhabricatorLiskDAO $object, - array $xactions) { - return; - } - private function validateEditParameters( PhabricatorLiskDAO $object, array $xactions) { @@ -1099,18 +1220,9 @@ abstract class PhabricatorApplicationTransactionEditor PhabricatorPolicyCapability::CAN_VIEW); break; case PhabricatorTransactions::TYPE_VIEW_POLICY: - PhabricatorPolicyFilter::requireCapability( - $actor, - $object, - PhabricatorPolicyCapability::CAN_EDIT); - break; case PhabricatorTransactions::TYPE_EDIT_POLICY: - PhabricatorPolicyFilter::requireCapability( - $actor, - $object, - PhabricatorPolicyCapability::CAN_EDIT); - break; case PhabricatorTransactions::TYPE_JOIN_POLICY: + case PhabricatorTransactions::TYPE_SPACE: PhabricatorPolicyFilter::requireCapability( $actor, $object, @@ -1791,6 +1903,12 @@ abstract class PhabricatorApplicationTransactionEditor $type, PhabricatorPolicyCapability::CAN_EDIT); break; + case PhabricatorTransactions::TYPE_SPACE: + $errors[] = $this->validateSpaceTransactions( + $object, + $xactions, + $type); + break; case PhabricatorTransactions::TYPE_CUSTOMFIELD: $groups = array(); foreach ($xactions as $xaction) { @@ -1877,6 +1995,52 @@ abstract class PhabricatorApplicationTransactionEditor return $errors; } + + private function validateSpaceTransactions( + PhabricatorLiskDAO $object, + array $xactions, + $transaction_type) { + $errors = array(); + + $all_spaces = PhabricatorSpacesNamespaceQuery::getAllSpaces(); + $viewer_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces( + $this->getActor()); + foreach ($xactions as $xaction) { + $space_phid = $xaction->getNewValue(); + + if ($space_phid === null) { + if (!$all_spaces) { + // The install doesn't have any spaces, so this is fine. + continue; + } + + // The install has some spaces, so every object needs to be put + // in a valid space. + $errors[] = new PhabricatorApplicationTransactionValidationError( + $transaction_type, + pht('Invalid'), + pht('You must choose a space for this object.'), + $xaction); + continue; + } + + // If the PHID isn't `null`, it needs to be a valid space that the + // viewer can see. + if (empty($viewer_spaces[$space_phid])) { + $errors[] = new PhabricatorApplicationTransactionValidationError( + $transaction_type, + pht('Invalid'), + pht( + 'You can not shift this object in the selected space, because '. + 'the space does not exist or you do not have access to it.'), + $xaction); + } + } + + return $errors; + } + + protected function adjustObjectForPolicyChecks( PhabricatorLiskDAO $object, array $xactions) { @@ -2019,8 +2183,60 @@ abstract class PhabricatorApplicationTransactionEditor PhabricatorLiskDAO $object, array $xactions) { - // Check if any of the transactions are visible. If we don't have any - // visible transactions, don't send the mail. + $email_to = $this->mailToPHIDs; + $email_cc = $this->mailCCPHIDs; + $email_cc = array_merge($email_cc, $this->heraldEmailPHIDs); + + $targets = $this->buildReplyHandler($object) + ->getMailTargets($email_to, $email_cc); + + // Set this explicitly before we start swapping out the effective actor. + $this->setActingAsPHID($this->getActingAsPHID()); + + + $mailed = array(); + foreach ($targets as $target) { + $original_actor = $this->getActor(); + + $viewer = $target->getViewer(); + $this->setActor($viewer); + $locale = PhabricatorEnv::beginScopedLocale($viewer->getTranslation()); + + $caught = null; + $mail = null; + try { + // Reload handles for the new viewer. + $this->loadHandles($xactions); + + $mail = $this->sendMailToTarget($object, $xactions, $target); + } catch (Exception $ex) { + $caught = $ex; + } + + $this->setActor($original_actor); + unset($locale); + + if ($caught) { + throw $ex; + } + + if ($mail) { + foreach ($mail->buildRecipientList() as $phid) { + $mailed[$phid] = true; + } + } + } + + return array_keys($mailed); + } + + private function sendMailToTarget( + PhabricatorLiskDAO $object, + array $xactions, + PhabricatorMailTarget $target) { + + // Check if any of the transactions are visible for this viewer. If we + // don't have any visible transactions, don't send the mail. $any_visible = false; foreach ($xactions as $xaction) { @@ -2031,88 +2247,52 @@ abstract class PhabricatorApplicationTransactionEditor } if (!$any_visible) { - return; + return null; } - $email_force = array(); - $email_to = $this->getMailTo($object); - $email_cc = $this->getMailCC($object); - - $adapter = $this->getHeraldAdapter(); - if ($adapter) { - $email_cc = array_merge($email_cc, $adapter->getEmailPHIDs()); - $email_force = $adapter->getForcedEmailPHIDs(); - } - - $phids = array_merge($email_to, $email_cc); - $handles = id(new PhabricatorHandleQuery()) - ->setViewer($this->requireActor()) - ->withPHIDs($phids) - ->execute(); - - $template = $this->buildMailTemplate($object); + $mail = $this->buildMailTemplate($object); $body = $this->buildMailBody($object, $xactions); $mail_tags = $this->getMailTags($object, $xactions); $action = $this->getMailAction($object, $xactions); - $reply_handler = $this->buildReplyHandler($object); + if (PhabricatorEnv::getEnvConfig('metamta.email-preferences')) { + $this->addEmailPreferenceSectionToMailBody( + $body, + $object, + $xactions); + } - $body->addEmailPreferenceSection(); - - $template + $mail ->setFrom($this->getActingAsPHID()) ->setSubjectPrefix($this->getMailSubjectPrefix()) ->setVarySubjectPrefix('['.$action.']') ->setThreadID($this->getMailThreadID($object), $this->getIsNewObject()) ->setRelatedPHID($object->getPHID()) ->setExcludeMailRecipientPHIDs($this->getExcludeMailRecipientPHIDs()) - ->setForceHeraldMailRecipientPHIDs($email_force) + ->setForceHeraldMailRecipientPHIDs($this->heraldForcedEmailPHIDs) ->setMailTags($mail_tags) ->setIsBulk(true) ->setBody($body->render()) ->setHTMLBody($body->renderHTML()); foreach ($body->getAttachments() as $attachment) { - $template->addAttachment($attachment); + $mail->addAttachment($attachment); } - $herald_xscript = $this->getHeraldTranscript(); - if ($herald_xscript) { - $herald_header = $herald_xscript->getXHeraldRulesHeader(); - $herald_header = HeraldTranscript::saveXHeraldRulesHeader( - $object->getPHID(), - $herald_header); - } else { - $herald_header = HeraldTranscript::loadXHeraldRulesHeader( - $object->getPHID()); - } - - if ($herald_header) { - $template->addHeader('X-Herald-Rules', $herald_header); + if ($this->heraldHeader) { + $mail->addHeader('X-Herald-Rules', $this->heraldHeader); } if ($object instanceof PhabricatorProjectInterface) { - $this->addMailProjectMetadata($object, $template); + $this->addMailProjectMetadata($object, $mail); } if ($this->getParentMessageID()) { - $template->setParentMessageID($this->getParentMessageID()); + $mail->setParentMessageID($this->getParentMessageID()); } - $mails = $reply_handler->multiplexMail( - $template, - array_select_keys($handles, $email_to), - array_select_keys($handles, $email_cc)); - - foreach ($mails as $mail) { - $mail->saveAndSend(); - } - - $template->addTos($email_to); - $template->addCCs($email_cc); - - return $template; + return $target->sendMail($mail); } private function addMailProjectMetadata( @@ -2241,7 +2421,8 @@ abstract class PhabricatorApplicationTransactionEditor $has_support = false; if ($object instanceof PhabricatorSubscribableInterface) { - $phids[] = $this->subscribers; + $phid = $object->getPHID(); + $phids[] = PhabricatorSubscribersQuery::loadSubscribersForPHID($phid); $has_support = true; } @@ -2308,6 +2489,21 @@ abstract class PhabricatorApplicationTransactionEditor return $body; } + + /** + * @task mail + */ + protected function addEmailPreferenceSectionToMailBody( + PhabricatorMetaMTAMailBody $body, + PhabricatorLiskDAO $object, + array $xactions) { + + $href = PhabricatorEnv::getProductionURI( + '/settings/panel/emailpreferences/'); + $body->addLinkSection(pht('EMAIL PREFERENCES'), $href); + } + + /** * @task mail */ @@ -2456,8 +2652,8 @@ abstract class PhabricatorApplicationTransactionEditor return; } - $related_phids = $this->getFeedRelatedPHIDs($object, $xactions); - $subscribed_phids = $this->getFeedNotifyPHIDs($object, $xactions); + $related_phids = $this->feedRelatedPHIDs; + $subscribed_phids = $this->feedNotifyPHIDs; $story_type = $this->getFeedStoryType(); $story_data = $this->getFeedStoryData($object, $xactions); @@ -2709,6 +2905,13 @@ abstract class PhabricatorApplicationTransactionEditor continue; } + if ($node instanceof PhabricatorUser) { + // TODO: At least for now, don't record inverse edge transactions + // for users (for example, "alincoln joined project X"): Feed fills + // this role instead. + continue; + } + $editor = $node->getApplicationTransactionEditor(); $template = $node->getApplicationTransactionTemplate(); $target = $node->getApplicationTransactionObject(); @@ -2740,4 +2943,121 @@ abstract class PhabricatorApplicationTransactionEditor } } + +/* -( Workers )------------------------------------------------------------ */ + + + /** + * Load any object state which is required to publish transactions. + * + * This hook is invoked in the main process before we compute data related + * to publishing transactions (like email "To" and "CC" lists), and again in + * the worker before publishing occurs. + * + * @return object Publishable object. + * @task workers + */ + protected function willPublish(PhabricatorLiskDAO $object, array $xactions) { + return $object; + } + + + /** + * Convert the editor state to a serializable dictionary which can be passed + * to a worker. + * + * This data will be loaded with @{method:loadWorkerState} in the worker. + * + * @return dict Serializable editor state. + * @task workers + */ + final private function getWorkerState() { + $state = array(); + foreach ($this->getAutomaticStateProperties() as $property) { + $state[$property] = $this->$property; + } + + $state += array( + 'excludeMailRecipientPHIDs' => $this->getExcludeMailRecipientPHIDs(), + 'custom' => $this->getCustomWorkerState(), + ); + + return $state; + } + + + /** + * Hook; return custom properties which need to be passed to workers. + * + * @return dict Custom properties. + * @task workers + */ + protected function getCustomWorkerState() { + return array(); + } + + + /** + * Load editor state using a dictionary emitted by @{method:getWorkerState}. + * + * This method is used to load state when running worker operations. + * + * @param dict Editor state, from @{method:getWorkerState}. + * @return this + * @task workers + */ + final public function loadWorkerState(array $state) { + foreach ($this->getAutomaticStateProperties() as $property) { + $this->$property = idx($state, $property); + } + + $exclude = idx($state, 'excludeMailRecipientPHIDs', array()); + $this->setExcludeMailRecipientPHIDs($exclude); + + $custom = idx($state, 'custom', array()); + $this->loadCustomWorkerState($custom); + + return $this; + } + + + /** + * Hook; set custom properties on the editor from data emitted by + * @{method:getCustomWorkerState}. + * + * @param dict Custom state, + * from @{method:getCustomWorkerState}. + * @return this + * @task workers + */ + protected function loadCustomWorkerState(array $state) { + return $this; + } + + + /** + * Get a list of object properties which should be automatically sent to + * workers in the state data. + * + * These properties will be automatically stored and loaded by the editor in + * the worker. + * + * @return list List of properties. + * @task workers + */ + private function getAutomaticStateProperties() { + return array( + 'parentMessageID', + 'disableEmail', + 'isNewObject', + 'heraldEmailPHIDs', + 'heraldForcedEmailPHIDs', + 'heraldHeader', + 'mailToPHIDs', + 'mailCCPHIDs', + 'feedNotifyPHIDs', + 'feedRelatedPHIDs', + ); + } + } diff --git a/src/applications/transactions/exception/PhabricatorApplicationTransactionNoEffectException.php b/src/applications/transactions/exception/PhabricatorApplicationTransactionNoEffectException.php index 48c3b85f52..c1bf00c111 100644 --- a/src/applications/transactions/exception/PhabricatorApplicationTransactionNoEffectException.php +++ b/src/applications/transactions/exception/PhabricatorApplicationTransactionNoEffectException.php @@ -15,7 +15,7 @@ final class PhabricatorApplicationTransactionNoEffectException $this->hasComment = $has_comment; $message = array(); - $message[] = 'Transactions have no effect:'; + $message[] = pht('Transactions have no effect:'); foreach ($this->transactions as $transaction) { $message[] = ' - '.$transaction->getNoEffectDescription(); } diff --git a/src/applications/transactions/replyhandler/PhabricatorApplicationTransactionReplyHandler.php b/src/applications/transactions/replyhandler/PhabricatorApplicationTransactionReplyHandler.php index cc4c1606f2..14e27edc73 100644 --- a/src/applications/transactions/replyhandler/PhabricatorApplicationTransactionReplyHandler.php +++ b/src/applications/transactions/replyhandler/PhabricatorApplicationTransactionReplyHandler.php @@ -6,9 +6,9 @@ abstract class PhabricatorApplicationTransactionReplyHandler abstract public function getObjectPrefix(); public function getPrivateReplyHandlerEmailAddress( - PhabricatorObjectHandle $handle) { + PhabricatorUser $user) { return $this->getDefaultPrivateReplyHandlerEmailAddress( - $handle, + $user, $this->getObjectPrefix()); } diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index a6c1715115..7439c8728e 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -250,6 +250,14 @@ abstract class PhabricatorApplicationTransaction $phids[] = array($new); } break; + case PhabricatorTransactions::TYPE_SPACE: + if ($old) { + $phids[] = array($old); + } + if ($new) { + $phids[] = array($new); + } + break; case PhabricatorTransactions::TYPE_TOKEN: break; case PhabricatorTransactions::TYPE_BUILDABLE: @@ -369,6 +377,8 @@ abstract class PhabricatorApplicationTransaction return 'fa-wrench'; case PhabricatorTransactions::TYPE_TOKEN: return 'fa-trophy'; + case PhabricatorTransactions::TYPE_SPACE: + return 'fa-th-large'; } return 'fa-pencil'; @@ -438,6 +448,7 @@ abstract class PhabricatorApplicationTransaction case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: case PhabricatorTransactions::TYPE_JOIN_POLICY: + case PhabricatorTransactions::TYPE_SPACE: if ($this->getOldValue() === null) { return true; } else { @@ -597,6 +608,8 @@ abstract class PhabricatorApplicationTransaction return pht( 'All users are already subscribed to this %s.', $this->getApplicationObjectTypeName()); + case PhabricatorTransactions::TYPE_SPACE: + return pht('This object is already in that space.'); case PhabricatorTransactions::TYPE_EDGE: return pht('Edges already exist; transaction has no effect.'); } @@ -636,6 +649,12 @@ abstract class PhabricatorApplicationTransaction $this->getApplicationObjectTypeName(), $this->renderPolicyName($old, 'old'), $this->renderPolicyName($new, 'new')); + case PhabricatorTransactions::TYPE_SPACE: + return pht( + '%s shifted this object from the %s space to the %s space.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($old), + $this->renderHandleLink($new)); case PhabricatorTransactions::TYPE_SUBSCRIBERS: $add = array_diff($new, $old); $rem = array_diff($old, $new); @@ -821,6 +840,13 @@ abstract class PhabricatorApplicationTransaction '%s updated subscribers of %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); + case PhabricatorTransactions::TYPE_SPACE: + return pht( + '%s shifted %s from the %s space to the %s space.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid), + $this->renderHandleLink($old), + $this->renderHandleLink($new)); case PhabricatorTransactions::TYPE_EDGE: $new = ipull($new, 'dst'); $old = ipull($old, 'dst'); diff --git a/src/applications/transactions/worker/PhabricatorApplicationTransactionPublishWorker.php b/src/applications/transactions/worker/PhabricatorApplicationTransactionPublishWorker.php new file mode 100644 index 0000000000..ce1166f23c --- /dev/null +++ b/src/applications/transactions/worker/PhabricatorApplicationTransactionPublishWorker.php @@ -0,0 +1,134 @@ +loadObject(); + $editor = $this->buildEditor($object); + $xactions = $this->loadTransactions($object); + + $editor->publishTransactions($object, $xactions); + } + + + /** + * Load the object the transactions affect. + */ + private function loadObject() { + $data = $this->getTaskData(); + $viewer = PhabricatorUser::getOmnipotentUser(); + + $phid = idx($data, 'objectPHID'); + if (!$phid) { + throw new PhabricatorWorkerPermanentFailureException( + pht('Task has no object PHID!')); + } + + $object = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withPHIDs(array($phid)) + ->executeOne(); + if (!$object) { + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'Unable to load object with PHID "%s"!', + $phid)); + } + + return $object; + } + + + /** + * Build and configure an Editor to publish these transactions. + */ + private function buildEditor( + PhabricatorApplicationTransactionInterface $object) { + $data = $this->getTaskData(); + + $daemon_source = PhabricatorContentSource::newForSource( + PhabricatorContentSource::SOURCE_DAEMON, + array()); + + $viewer = PhabricatorUser::getOmnipotentUser(); + $editor = $object->getApplicationTransactionEditor() + ->setActor($viewer) + ->setContentSource($daemon_source) + ->setActingAsPHID(idx($data, 'actorPHID')) + ->loadWorkerState(idx($data, 'state', array())); + + return $editor; + } + + + /** + * Load the transactions to be published. + */ + private function loadTransactions( + PhabricatorApplicationTransactionInterface $object) { + $data = $this->getTaskData(); + + $xaction_phids = idx($data, 'xactionPHIDs'); + if (!$xaction_phids) { + throw new PhabricatorWorkerPermanentFailureException( + pht('Task has no transaction PHIDs!')); + } + + $viewer = PhabricatorUser::getOmnipotentUser(); + + $type = phid_get_subtype(head($xaction_phids)); + $xactions = $this->buildTransactionQuery($type) + ->setViewer($viewer) + ->withPHIDs($xaction_phids) + ->needComments(true) + ->execute(); + $xactions = mpull($xactions, null, 'getPHID'); + + $missing = array_diff($xaction_phids, array_keys($xactions)); + if ($missing) { + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'Unable to load transactions: %s.', + implode(', ', $missing))); + } + + return array_select_keys($xactions, $xaction_phids); + } + + + /** + * Build a new transaction query of the appropriate class so we can load + * the transactions. + */ + private function buildTransactionQuery($type) { + $queries = id(new PhutilSymbolLoader()) + ->setAncestorClass('PhabricatorApplicationTransactionQuery') + ->loadObjects(); + + foreach ($queries as $query) { + $query_type = $query + ->getTemplateApplicationTransaction() + ->getApplicationTransactionType(); + if ($query_type == $type) { + return $query; + } + } + + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'Unable to load query for transaction type "%s"!', + $type)); + } + +} diff --git a/src/applications/uiexample/examples/PHUITimelineExample.php b/src/applications/uiexample/examples/PHUITimelineExample.php index 33b4fe9752..18cd180fd8 100644 --- a/src/applications/uiexample/examples/PHUITimelineExample.php +++ b/src/applications/uiexample/examples/PHUITimelineExample.php @@ -96,8 +96,8 @@ final class PHUITimelineExample extends PhabricatorUIExample { $events[] = id(new PHUITimelineEventView()) ->setUserHandle($handle) ->setIcon('fa-tag') - ->setTitle(str_repeat('Long Text Title ', 64)) - ->appendChild(str_repeat('Long Text Body ', 64)) + ->setTitle(str_repeat(pht('Long Text Title').' ', 64)) + ->appendChild(str_repeat(pht('Long Text Body').' ', 64)) ->setColor(PhabricatorTransactions::COLOR_ORANGE); $events[] = id(new PHUITimelineEventView()) diff --git a/src/applications/uiexample/examples/PhabricatorAphrontBarUIExample.php b/src/applications/uiexample/examples/PhabricatorAphrontBarUIExample.php index d28d145ec6..f66c5c1618 100644 --- a/src/applications/uiexample/examples/PhabricatorAphrontBarUIExample.php +++ b/src/applications/uiexample/examples/PhabricatorAphrontBarUIExample.php @@ -53,7 +53,7 @@ final class PhabricatorAphrontBarUIExample extends PhabricatorUIExample { } return $this->wrap( - 'Glyph bars in weird order', + pht('Glyph bars in weird order'), $views); } @@ -61,12 +61,13 @@ final class PhabricatorAphrontBarUIExample extends PhabricatorUIExample { $bar = id(new AphrontGlyphBarView()) ->setValue(50) ->setMax(100) - ->setCaption('Glyphs!') + ->setCaption(pht('Glyphs!')) ->setNumGlyphs(10) ->setGlyph(hsprintf('%s', '*')); return $this->wrap( - 'Ascii star glyph bar', $bar); + pht('ASCII star glyph bar'), + $bar); } } diff --git a/src/applications/uiexample/examples/PhabricatorPagedFormUIExample.php b/src/applications/uiexample/examples/PhabricatorPagedFormUIExample.php index fb0f7a6362..3f42be9ce1 100644 --- a/src/applications/uiexample/examples/PhabricatorPagedFormUIExample.php +++ b/src/applications/uiexample/examples/PhabricatorPagedFormUIExample.php @@ -22,28 +22,28 @@ final class PhabricatorPagedFormUIExample extends PhabricatorUIExample { ->addControl( id(new AphrontFormTextControl()) ->setName('page1') - ->setLabel('Page 1')); + ->setLabel(pht('Page 1'))); $page2 = id(new PHUIFormPageView()) ->setPageName(pht('Page 2')) ->addControl( id(new AphrontFormTextControl()) ->setName('page2') - ->setLabel('Page 2')); + ->setLabel(pht('Page 2'))); $page3 = id(new PHUIFormPageView()) ->setPageName(pht('Page 3')) ->addControl( id(new AphrontFormTextControl()) ->setName('page3') - ->setLabel('Page 3')); + ->setLabel(pht('Page 3'))); $page4 = id(new PHUIFormPageView()) ->setPageName(pht('Page 4')) ->addControl( id(new AphrontFormTextControl()) ->setName('page4') - ->setLabel('Page 4')); + ->setLabel(pht('Page 4'))); $form = new PHUIPagedFormView(); $form->setUser($user); diff --git a/src/applications/uiexample/examples/PhabricatorPagerUIExample.php b/src/applications/uiexample/examples/PhabricatorPagerUIExample.php index 52df0f96bc..b2c58df551 100644 --- a/src/applications/uiexample/examples/PhabricatorPagerUIExample.php +++ b/src/applications/uiexample/examples/PhabricatorPagerUIExample.php @@ -23,7 +23,7 @@ final class PhabricatorPagerUIExample extends PhabricatorUIExample { $rows = array(); for ($ii = $offset; $ii < min($item_count, $offset + $page_size); $ii++) { $rows[] = array( - 'Item #'.($ii + 1), + pht('Item #%d', $ii + 1), ); } diff --git a/src/applications/uiexample/examples/PhabricatorSortTableUIExample.php b/src/applications/uiexample/examples/PhabricatorSortTableUIExample.php index c9c1747ac1..113c38b92a 100644 --- a/src/applications/uiexample/examples/PhabricatorSortTableUIExample.php +++ b/src/applications/uiexample/examples/PhabricatorSortTableUIExample.php @@ -18,28 +18,28 @@ final class PhabricatorSortTableUIExample extends PhabricatorUIExample { 'model' => 'Civic', 'year' => 2004, 'price' => 3199, - 'color' => 'Blue', + 'color' => pht('Blue'), ), array( 'make' => 'Ford', 'model' => 'Focus', 'year' => 2001, 'price' => 2549, - 'color' => 'Red', + 'color' => pht('Red'), ), array( 'make' => 'Toyota', 'model' => 'Camry', 'year' => 2009, 'price' => 4299, - 'color' => 'Black', + 'color' => pht('Black'), ), array( 'make' => 'NASA', 'model' => 'Shuttle', 'year' => 1998, 'price' => 1000000000, - 'color' => 'White', + 'color' => pht('White'), ), ); diff --git a/src/applications/xhprof/controller/PhabricatorXHProfController.php b/src/applications/xhprof/controller/PhabricatorXHProfController.php index 6c595d1455..6dda92560c 100644 --- a/src/applications/xhprof/controller/PhabricatorXHProfController.php +++ b/src/applications/xhprof/controller/PhabricatorXHProfController.php @@ -5,7 +5,7 @@ abstract class PhabricatorXHProfController extends PhabricatorController { public function buildStandardPageResponse($view, array $data) { $page = $this->buildStandardPageView(); - $page->setApplicationName('XHProf'); + $page->setApplicationName(pht('XHProf')); $page->setBaseURI('/xhprof/'); $page->setTitle(idx($data, 'title')); $page->setGlyph("\xE2\x98\x84"); diff --git a/src/docs/book/contributor.book b/src/docs/book/contributor.book index 776417aa6d..d5b2ae90ae 100644 --- a/src/docs/book/contributor.book +++ b/src/docs/book/contributor.book @@ -11,13 +11,13 @@ }, "exclude": [ "(^externals/)", - "(^webroot/rsrc/externals/)", - "(^scripts/)", - "(^support/)", "(^resources/)", - "(^src/docs/user/)", + "(^scripts/)", + "(^src/docs/flavor/)", "(^src/docs/tech/)", - "(^src/docs/flavor/)" + "(^src/docs/user/)", + "(^support/)", + "(^webroot/rsrc/externals/)" ], "groups": { "contrib": { @@ -26,11 +26,11 @@ "detail": { "name": "Contributing in Detail" }, - "standards": { - "name": "Coding Standards" - }, "developer": { "name": "Developer Guides" + }, + "standards": { + "name": "Coding Standards" } } } diff --git a/src/docs/book/flavor.book b/src/docs/book/flavor.book index b13ed9956c..978244f19d 100644 --- a/src/docs/book/flavor.book +++ b/src/docs/book/flavor.book @@ -11,23 +11,20 @@ }, "exclude": [ "(^externals/)", - "(^webroot/rsrc/externals/)", - "(^scripts/)", - "(^support/)", "(^resources/)", - "(^src/docs/user/)", + "(^scripts/)", + "(^src/docs/contributor/)", "(^src/docs/tech/)", - "(^src/docs/contributor/)" + "(^src/docs/user/)", + "(^support/)", + "(^webroot/rsrc/externals/)" ], "groups": { "overview": { "name": "Overview" }, - "review": { - "name": "Revision Control and Code Review" - }, - "sundry": { - "name": "Sundries" + "javascript": { + "name": "Javascript" }, "lore": { "name": "Phabricator Lore" @@ -35,8 +32,11 @@ "php": { "name": "PHP" }, - "javascript": { - "name": "Javascript" + "review": { + "name": "Revision Control and Code Review" + }, + "sundry": { + "name": "Sundries" } } } diff --git a/src/docs/book/phabricator.book b/src/docs/book/phabricator.book index d0f26f64e9..ed74ddd012 100644 --- a/src/docs/book/phabricator.book +++ b/src/docs/book/phabricator.book @@ -7,20 +7,32 @@ "uri.source": "https://secure.phabricator.com/diffusion/P/browse/master/%f$%l", "rules": { - "(\\.php$)": "DivinerPHPAtomizer", - "(\\.diviner$)": "DivinerArticleAtomizer" + "(\\.diviner$)": "DivinerArticleAtomizer", + "(\\.php$)": "DivinerPHPAtomizer" }, "exclude": [ "(^externals/)", - "(^webroot/rsrc/externals/)", - "(^scripts/)", - "(^support/)", "(^resources/)", - "(^src/docs/user/)", + "(^scripts/)", + "(^src/docs/contributor/)", "(^src/docs/flavor/)", - "(^src/docs/contributor/)" + "(^src/docs/user/)", + "(^support/)", + "(^webroot/rsrc/externals/)" ], "groups": { + "aphront": { + "name": "Aphront", + "include": "(^src/aphront/)" + }, + "almanac": { + "name": "Almanac", + "include": "(^src/applications/almanac/)" + }, + "aphlict": { + "name": "Aphlict", + "include": "(^src/applications/aphlict/)" + }, "arcanist": { "name": "Arcanist Integration", "include": "(^src/applications/arcanist/)" @@ -45,6 +57,10 @@ "name": "Calendar", "include": "(^src/applications/calendar/)" }, + "celerity": { + "name": "Celerity", + "include": "(^src/applications/celerity/)" + }, "chatlog": { "name": "Chatlog", "include": "(^src/applications/chatlog/)" @@ -61,13 +77,28 @@ "name": "Conpherence", "include": "(^src/applications/conpherence/)" }, + "console": { + "name": "Console", + "include": "(^src/applications/console/)" + }, "countdown": { "name": "Countdown", "include": "(^src/applications/countdown/)" }, + "customfield": { + "name": "Custom Fields", + "include": "(^src/infrastructure/customfield/)" + }, "daemon": { "name": "Daemons", - "include": "(^src/applications/daemon/)" + "include": [ + "(^src/applications/daemon/)", + "(^src/infrastructure/daemon/)" + ] + }, + "dashboard": { + "name": "Dashboard", + "include": "(^src/applications/dashboard/)" }, "differential": { "name": "Differential", @@ -77,10 +108,6 @@ "name": "Diffusion", "include": "(^src/applications/diffusion/)" }, - "directory": { - "name": "Directory", - "include": "(^src/applications/directory/)" - }, "diviner": { "name": "Diviner", "include": "(^src/applications/diviner/)" @@ -97,6 +124,14 @@ "name": "Drydock", "include": "(^src/applications/drydock/)" }, + "edges": { + "name": "Edges", + "include": "(^src/infrastructure/edges/)" + }, + "events": { + "name": "Events", + "include": "(^src/infrastructure/events/)" + }, "fact": { "name": "Fact", "include": "(^src/applications/fact/)" @@ -113,6 +148,10 @@ "name": "Flags", "include": "(^src/applications/flag/)" }, + "fund": { + "name": "Fund", + "include": "(^src/applications/fund/)" + }, "harbormaster": { "name": "Harbormaster", "include": "(^src/applications/harbormaster/)" @@ -125,6 +164,10 @@ "name": "Herald", "include": "(^src/applications/herald/)" }, + "home": { + "name": "Home", + "include": "(^src/applications/home/)" + }, "legalpad": { "name": "Legalpad", "include": "(^src/applications/legalpad/)" @@ -137,26 +180,30 @@ "name": "Macro", "include": "(^src/applications/macro/)" }, - "mailinglists": { - "name": "Mailing Lists", - "include": "(^src/applications/mailinglists/)" - }, "maniphest": { "name": "Maniphest", "include": "(^src/applications/maniphest/)" }, "meta": { - "name": "Meta", + "name": "Applications", "include": "(^src/applications/meta/)" }, "metamta": { "name": "MetaMTA", "include": "(^src/applications/metamta/)" }, + "multimeter": { + "name": "Multimeter", + "include": "(^src/applications/multimeter/)" + }, "notification": { "name": "Notifications", "include": "(^src/applications/notification/)" }, + "nuance": { + "name": "Nuance", + "include": "(^src/applications/nuance/)" + }, "oauthserver": { "name": "OAuth Server", "include": "(^src/applications/oauthserver/)" @@ -165,6 +212,10 @@ "name": "Owners", "include": "(^src/applications/owners/)" }, + "passphrase": { + "name": "Passphrase", + "include": "(^src/applications/passphrase/)" + }, "paste": { "name": "Paste", "include": "(^src/applications/paste/)" @@ -197,6 +248,10 @@ "name": "PHPAST", "include": "(^src/applications/phpast/)" }, + "phragment": { + "name": "Phragment", + "include": "(^src/applications/phragment/)" + }, "phrequent": { "name": "Phrequent", "include": "(^src/applications/phrequent/)" @@ -205,6 +260,10 @@ "name": "Phriction", "include": "(^src/applications/phriction/)" }, + "phui": { + "name": "PHUI", + "include": "(^src/view/phui/)" + }, "policy": { "name": "Policy", "include": "(^src/applications/policy/)" @@ -223,7 +282,10 @@ }, "remarkup": { "name": "Remarkup", - "include": "(^src/applications/remarkup/)" + "include": [ + "(^src/applications/remarkup/)", + "(^src/infrastructure/markup/)" + ] }, "repository": { "name": "Repositories", @@ -241,10 +303,22 @@ "name": "Slowvote", "include": "(^src/applications/slowvote/)" }, + "spaces": { + "name": "Spaces", + "include": "(^src/applications/spaces/)" + }, + "storage": { + "name": "Storage", + "include": "(^src/infrastructure/storage/)" + }, "subscriptions": { "name": "Subscriptions", "include": "(^src/applications/subscriptions/)" }, + "support": { + "name": "Support", + "include": "(^src/applications/support/)" + }, "system": { "name": "System", "include": "(^src/applications/system/)" diff --git a/src/docs/book/user.book b/src/docs/book/user.book index 18b6c6db6c..e6f44abe36 100644 --- a/src/docs/book/user.book +++ b/src/docs/book/user.book @@ -11,13 +11,13 @@ }, "exclude": [ "(^externals/)", - "(^webroot/rsrc/externals/)", - "(^scripts/)", - "(^support/)", "(^resources/)", - "(^src/docs/tech/)", + "(^scripts/)", + "(^src/docs/contributor/)", "(^src/docs/flavor/)", - "(^src/docs/contributor/)" + "(^src/docs/tech/)", + "(^support/)", + "(^webroot/rsrc/externals/)" ], "groups": { "intro": { @@ -28,24 +28,6 @@ }, "userguide": { "name": "Application User Guides" - }, - "differential": { - "name": "Differential (Code Review)" - }, - "diffusion": { - "name": "Diffusion (Repository Browser)" - }, - "maniphest": { - "name": "Maniphest (Task Tracking)" - }, - "slowvote": { - "name": "Slowvote (Polls)" - }, - "herald": { - "name": "Herald (Notifications)" - }, - "phriction": { - "name": "Phriction (Wiki)" } } } diff --git a/src/docs/contributor/adding_new_css_and_js.diviner b/src/docs/contributor/adding_new_css_and_js.diviner index 1f5fd94ac6..00a3808fba 100644 --- a/src/docs/contributor/adding_new_css_and_js.diviner +++ b/src/docs/contributor/adding_new_css_and_js.diviner @@ -18,8 +18,7 @@ To add a new CSS or JS file, create it in an appropriate location in `webroot/rsrc/css/` or `webroot/rsrc/js/` inside your `phabricator/` directory. -Each file must ##@provides## itself as a component, declared in a header -comment: +Each file must `@provides` itself as a component, declared in a header comment: LANG=css /** diff --git a/src/docs/contributor/darkconsole.diviner b/src/docs/contributor/darkconsole.diviner index cf512710c6..cddef89422 100644 --- a/src/docs/contributor/darkconsole.diviner +++ b/src/docs/contributor/darkconsole.diviner @@ -37,7 +37,7 @@ can use them to access different debugging and performance features. = Plugin: Error Log = The "Error Log" plugin shows errors that occurred while generating the page, -similar to the httpd ##error.log##. You can send information to the error log +similar to the httpd `error.log`. You can send information to the error log explicitly with the @{function@libphutil:phlog} function. If errors occurred, a red dot will appear on the plugin tab. diff --git a/src/docs/contributor/javascript_coding_standards.diviner b/src/docs/contributor/javascript_coding_standards.diviner index 65e15e6b55..3b47a566a6 100644 --- a/src/docs/contributor/javascript_coding_standards.diviner +++ b/src/docs/contributor/javascript_coding_standards.diviner @@ -18,9 +18,9 @@ guidelines, you can probably get away with skimming this document. - Use two spaces for indentation. Don't use literal tab characters. - Use Unix linebreaks ("\n"), not MSDOS ("\r\n") or OS9 ("\r"). - - Put a space after control keywords like ##if## and ##for##. + - Put a space after control keywords like `if` and `for`. - Put a space after commas in argument lists. - - Put space around operators like ##=##, ##<##, etc. + - Put space around operators like `=`, `<`, etc. - Don't put spaces after function names. - Parentheses should hug their contents. - Generally, prefer to wrap code at 80 columns. @@ -30,26 +30,26 @@ guidelines, you can probably get away with skimming this document. The Javascript language unambiguously dictates casing/naming rules; follow those rules. - - Name variables using ##lowercase_with_underscores##. - - Name classes using ##UpperCamelCase##. - - Name methods and properties using ##lowerCamelCase##. - - Name global functions using ##lowerCamelCase##. Avoid defining global + - Name variables using `lowercase_with_underscores`. + - Name classes using `UpperCamelCase`. + - Name methods and properties using `lowerCamelCase`. + - Name global functions using `lowerCamelCase`. Avoid defining global functions. - - Name constants using ##UPPERCASE##. - - Write ##true##, ##false##, and ##null## in lowercase. + - Name constants using `UPPERCASE`. + - Write `true`, `false`, and `null` in lowercase. - "Internal" methods and properties should be prefixed with an underscore. For more information about what "internal" means, see **Leading Underscores**, below. = Comments = - - Strongly prefer ##//## comments for making comments inside the bodies of + - Strongly prefer `//` comments for making comments inside the bodies of functions and methods (this lets someone easily comment out a block of code while debugging later). = Javascript Language = - - Use ##[]## and ##{}##, not ##new Array## and ##new Object##. + - Use `[]` and `{}`, not `new Array` and `new Object`. - When creating an object literal, do not quote keys unless required. = Examples = @@ -66,7 +66,7 @@ rules. } You should always put braces around the body of an if clause, even if it is only -one line. Note that operators like ##>## and ##===## are also surrounded by +one line. Note that operators like `>` and `===` are also surrounded by spaces. **for (iteration):** @@ -107,10 +107,10 @@ see @{article:Javascript Object and Array}. } `break` statements should be indented to block level. If you don't push them -in, you end up with an inconsistent rule for conditional ##break## statements, -as in the ##2## case. +in, you end up with an inconsistent rule for conditional `break` statements, +as in the `2` case. -If you insist on having a "fall through" case that does not end with ##break##, +If you insist on having a "fall through" case that does not end with `break`, make it clear in a comment that you wrote this intentionally. For instance: lang=js diff --git a/src/docs/contributor/n_plus_one.diviner b/src/docs/contributor/n_plus_one.diviner index 1f3b78424e..6d259671a1 100644 --- a/src/docs/contributor/n_plus_one.diviner +++ b/src/docs/contributor/n_plus_one.diviner @@ -14,11 +14,11 @@ The N+1 query problem is a common performance antipattern. It looks like this: // ... } -Assuming ##load_cats()## has an implementation that boils down to: +Assuming `load_cats()` has an implementation that boils down to: SELECT * FROM cat WHERE ... -..and ##load_hats_for_cat($cat)## has an implementation something like this: +..and `load_hats_for_cat($cat)` has an implementation something like this: SELECT * FROM hat WHERE catID = ... diff --git a/src/docs/contributor/phabricator_code_layout.diviner b/src/docs/contributor/phabricator_code_layout.diviner index 0b021d667b..d563d0e2e0 100644 --- a/src/docs/contributor/phabricator_code_layout.diviner +++ b/src/docs/contributor/phabricator_code_layout.diviner @@ -20,21 +20,21 @@ most of its code in standardized subdirectories and classes. = Best Practice Class and Subdirectory Organization = -Suppose you were working on the application ##Derp##. +Suppose you were working on the application `Derp`. phabricator/src/applications/derp/ -If ##Derp## were as simple as possible, it would have one subdirectory: +If `Derp` were as simple as possible, it would have one subdirectory: phabricator/src/applications/derp/controller/ -containing the file ##DerpController.php## with the class +containing the file `DerpController.php` with the class - - ##DerpController##: minimally implements a ##processRequest()## method + - `DerpController`: minimally implements a `processRequest()` method which returns some @{class:AphrontResponse} object. The class would probably extend @{class:PhabricatorController}. -If ##Derp## were (relatively) complex, one could reasonably expect to see +If `Derp` were (relatively) complex, one could reasonably expect to see the following directory layout: phabricator/src/applications/derp/conduit/ @@ -54,54 +54,54 @@ this document. See @{article:Adding New CSS and JS}.) phabricator/webroot/rsrc/js/application/derp/ phabricator/webroot/rsrc/css/application/derp/ -These directories under ##phabricator/src/applications/derp/## represent +These directories under `phabricator/src/applications/derp/` represent the basic set of class types from which most Phabrictor applications are -assembled. Each would contain a class file. For ##Derp##, these classes could be +assembled. Each would contain a class file. For `Derp`, these classes could be something like: - - **DerpConstants**: constants used in the ##Derp## application. + - **DerpConstants**: constants used in the `Derp` application. - **DerpController**: business logic providing functionality for a given URI. Typically, controllers load data via Storage or Query classes, then present the data to the user via one or more View classes. - **DerpEditor**: business logic for workflows that change one or more Storage objects. Editor classes are only necessary for particularly complicated edits and should be used pragmatically versus Storage objects. - - **DerpException**: exceptions used in the ##Derp## application. - - **DerpQuery**: query one or more storage objects for pertinent ##Derp## + - **DerpException**: exceptions used in the `Derp` application. + - **DerpQuery**: query one or more storage objects for pertinent `Derp` application data. @{class:PhabricatorOffsetPagedQuery} is particularly handy for pagination and works well with @{class:AphrontPagerView}. - **DerpReplyHandler**: business logic from any configured email interactions - users can have with the ##Derp## application. - - **DerpStorage**: storage objects for the ##Derp## application. Typically + users can have with the `Derp` application. + - **DerpStorage**: storage objects for the `Derp` application. Typically there is a base class which extends @{class:PhabricatorLiskDAO} to configure application-wide storage settings like the application (thus database) name. Reading more about the @{class:LiskDAO} is highly recommended. - - **DerpView**: view objects for the ##Derp## application. Typically these + - **DerpView**: view objects for the `Derp` application. Typically these extend @{class:AphrontView}. - - **DerpConduitAPIMethod**: provides any and all ##Derp## application + - **DerpConduitAPIMethod**: provides any and all `Derp` application functionality that is accessible over Conduit. -However, it is likely that ##Derp## is even more complex, and rather than +However, it is likely that `Derp` is even more complex, and rather than containing one class, each directory has several classes. A typical example happens around the CRUD of an object: - **DerpBaseController**: typically extends @{class:PhabricatorController}, - implements ##buildStandardPageResponse## with the ##Derp## application name - and other ##Derp##-specific meta-data, and contains any controller-specific - functionality used throughout the ##Derp## application. - - **DerpDeleteController**: typically extends ##DerpBaseController## and - presents a confirmation dialogue to the user about deleting a ##Derp##. - - **DerpEditController**: typically extends ##DerpBaseController## and - presents a form to create and edit ##Derps##. Most likely uses - @{class:AphrontFormView} and various ##AphrontFormXControl## classes such as + implements `buildStandardPageResponse` with the `Derp` application name + and other `Derp`-specific meta-data, and contains any controller-specific + functionality used throughout the `Derp` application. + - **DerpDeleteController**: typically extends `DerpBaseController` and + presents a confirmation dialogue to the user about deleting a `Derp`. + - **DerpEditController**: typically extends `DerpBaseController` and + presents a form to create and edit `Derps`. Most likely uses + @{class:AphrontFormView} and various `AphrontFormXControl` classes such as @{class:AphrontFormTextControl} to create the form. - - **DerpListController**: typically extends ##DerpBaseController## and displays - a set of one or more ##Derps##. Might use @{class:AphrontTableView} to create - a table of ##Derps##. - - **DerpViewController**: typically extends ##DerpBaseController## and displays - a single ##Derp##. + - **DerpListController**: typically extends `DerpBaseController` and displays + a set of one or more `Derps`. Might use @{class:AphrontTableView} to create + a table of `Derps`. + - **DerpViewController**: typically extends `DerpBaseController` and displays + a single `Derp`. -Some especially awesome directories might have a ##__tests__## subdirectory +Some especially awesome directories might have a `__tests__` subdirectory containing all pertinent unit test code for the class. = Next Steps = diff --git a/src/docs/contributor/php_coding_standards.diviner b/src/docs/contributor/php_coding_standards.diviner index d79d340326..03f0bc158b 100644 --- a/src/docs/contributor/php_coding_standards.diviner +++ b/src/docs/contributor/php_coding_standards.diviner @@ -19,21 +19,21 @@ guidelines, you probably don't need to read this super thoroughly. - Use two spaces for indentation. Don't use tab literal characters. - Use Unix linebreaks ("\n"), not MSDOS ("\r\n") or OS9 ("\r"). - - Put a space after control keywords like ##if## and ##for##. + - Put a space after control keywords like `if` and `for`. - Put a space after commas in argument lists. - - Put a space around operators like ##=##, ##<##, etc. + - Put a space around operators like `=`, `<`, etc. - Don't put spaces after function names. - Parentheses should hug their contents. - Generally, prefer to wrap code at 80 columns. = Case and Capitalization = - - Name variables and functions using ##lowercase_with_underscores##. - - Name classes using ##UpperCamelCase##. - - Name methods and properties using ##lowerCamelCase##. + - Name variables and functions using `lowercase_with_underscores`. + - Name classes using `UpperCamelCase`. + - Name methods and properties using `lowerCamelCase`. - Use uppercase for common acronyms like ID and HTML. - - Name constants using ##UPPERCASE##. - - Write ##true##, ##false## and ##null## in lowercase. + - Name constants using `UPPERCASE`. + - Write `true`, `false` and `null` in lowercase. = Comments = @@ -43,9 +43,9 @@ guidelines, you probably don't need to read this super thoroughly. = PHP Language Style = - Use "" tag. - - Prefer casts like ##(string)## to casting functions like ##strval()##. - - Prefer type checks like ##$v === null## to type functions like - ##is_null()##. + - Prefer casts like `(string)` to casting functions like `strval()`. + - Prefer type checks like `$v === null` to type functions like + `is_null()`. - Avoid all crazy alternate forms of language constructs like "endwhile" and "<>". - Always put braces around conditional and loop blocks. diff --git a/src/docs/contributor/unit_tests.diviner b/src/docs/contributor/unit_tests.diviner index ac0d57e44d..3ac14b3e00 100644 --- a/src/docs/contributor/unit_tests.diviner +++ b/src/docs/contributor/unit_tests.diviner @@ -9,7 +9,7 @@ libphutil, Arcanist and Phabricator provide and use a simple unit test framework. This document is aimed at project contributors and describes how to use it to add and run tests in these projects or other libphutil libraries. -In the general case, you can integrate ##arc## with a custom unit test engine +In the general case, you can integrate `arc` with a custom unit test engine (like PHPUnit or any other unit testing library) to run tests in other projects. See @{article:Arcanist User Guide: Customizing Lint, Unit Tests and Workflows} for information on customizing engines. @@ -18,20 +18,20 @@ for information on customizing engines. To add new tests to a libphutil, Arcanist or Phabricator module: - - Create a ##__tests__/## directory in the module if it doesn't exist yet. - - Add classes to the ##__tests__/## directory which extend from + - Create a `__tests__/` directory in the module if it doesn't exist yet. + - Add classes to the `__tests__/` directory which extend from @{class:PhabricatorTestCase} (in Phabricator) or @{class@arcanist:PhutilTestCase} (elsewhere). - - Run ##arc liberate## on the library root so your classes are loadable. + - Run `arc liberate` on the library root so your classes are loadable. = Running Tests = Once you've added test classes, you can run them with: - - ##arc unit path/to/module/##, to explicitly run module tests. - - ##arc unit##, to run tests for all modules affected by changes in the + - `arc unit path/to/module/`, to explicitly run module tests. + - `arc unit`, to run tests for all modules affected by changes in the working copy. - - ##arc diff## will also run ##arc unit## for you. + - `arc diff` will also run `arc unit` for you. = Example Test Case = @@ -76,11 +76,11 @@ in a number of ways. From best to worst: is reasonable. - Build a real database simulation layer (fairly complex). - Disable isolation for a single test by using - ##LiskDAO::endIsolateAllLiskEffectsToCurrentProcess();## before your test - and ##LiskDAO::beginIsolateAllLiskEffectsToCurrentProcess();## after your + `LiskDAO::endIsolateAllLiskEffectsToCurrentProcess();` before your test + and `LiskDAO::beginIsolateAllLiskEffectsToCurrentProcess();` after your test. This will disable isolation for one test. NOT RECOMMENDED. - Disable isolation for your entire test case by overriding - ##getPhabricatorTestCaseConfiguration()## and providing - ##self::PHABRICATOR_TESTCONFIG_ISOLATE_LISK => false## in the configuration + `getPhabricatorTestCaseConfiguration()` and providing + `self::PHABRICATOR_TESTCONFIG_ISOLATE_LISK => false` in the configuration dictionary you return. This will disable isolation entirely. STRONGLY NOT RECOMMENDED. diff --git a/src/docs/contributor/using_oauthserver.diviner b/src/docs/contributor/using_oauthserver.diviner index dc8b377671..ff6f33a0c1 100644 --- a/src/docs/contributor/using_oauthserver.diviner +++ b/src/docs/contributor/using_oauthserver.diviner @@ -105,7 +105,7 @@ https://phabricator.example.com/api/user.whoami?access_token=ykc7ly7vtibj334oga4 If the token has expired or is otherwise invalid, the client will receive an error indicating as such. In these cases, the client should re-initiate -the entire ##Authorization Code Grant## flow. +the entire `Authorization Code Grant` flow. NOTE: See "Scopes" section below for more information on what data is currently exposed through the OAuth Server. diff --git a/src/docs/flavor/javascript_object_array.diviner b/src/docs/flavor/javascript_object_array.diviner index cf7e3371e6..cdba029e9a 100644 --- a/src/docs/flavor/javascript_object_array.diviner +++ b/src/docs/flavor/javascript_object_array.diviner @@ -8,7 +8,7 @@ behavior. = Primitives = Javascript has two native datatype primitives, Object and Array. Both are -classes, so you can use ##new## to instantiate new objects and arrays: +classes, so you can use `new` to instantiate new objects and arrays: COUNTEREXAMPLE var a = new Array(); // Not preferred. @@ -42,7 +42,7 @@ and Array are both classes, but "object" is also a primitive type. Object is = Objects are Maps, Arrays are Lists = -PHP has a single ##array## datatype which behaves like as both map and a list, +PHP has a single `array` datatype which behaves like as both map and a list, and a common mistake is to treat Javascript arrays (or objects) in the same way. **Don't do this.** It sort of works until it doesn't. Instead, learn how Javascript's native datatypes work and use them properly. @@ -94,9 +94,9 @@ Iterate over a list like this: NOTE: There's some sparse array nonsense being omitted here, see below. -If you try to use ##for (var k in ...)## syntax to iterate over an Array, you'll +If you try to use `for (var k in ...)` syntax to iterate over an Array, you'll pick up a whole pile of keys you didn't intend to and it won't work. If you try -to use ##for (var ii = 0; ...)## syntax to iterate over an Object, it won't work +to use `for (var ii = 0; ...)` syntax to iterate over an Object, it won't work at all. If you consistently treat Arrays as lists and Objects as maps and use the @@ -106,7 +106,7 @@ way. = hasOwnProperty() = An issue with this model is that if you write stuff to Object.prototype, it will -show up every time you use enumeration ##for##: +show up every time you use enumeration `for`: COUNTEREXAMPLE var o = {}; @@ -117,7 +117,7 @@ show up every time you use enumeration ##for##: There are two ways to avoid this: - - test that ##k## exists on ##o## by calling ##o.hasOwnProperty(k)## in every + - test that `k` exists on `o` by calling `o.hasOwnProperty(k)` in every single loop everywhere in your program and only use libraries which also do this and never forget to do it ever; or - don't write to Object.prototype. diff --git a/src/docs/flavor/javascript_pitfalls.diviner b/src/docs/flavor/javascript_pitfalls.diviner index beef1b83e6..9276ae4712 100644 --- a/src/docs/flavor/javascript_pitfalls.diviner +++ b/src/docs/flavor/javascript_pitfalls.diviner @@ -27,7 +27,7 @@ insert semicolons after each statement. You should also prefer to break lines in places where insertion of a semicolon would not make the unparseable parseable, usually after operators. -= ##with## is Bad News = += `with` is Bad News = `with` is a pretty bad feature, for this reason among others: @@ -35,12 +35,12 @@ usually after operators. property = 3; // Might be on object, might be on window: who knows. } -Avoid ##with##. +Avoid `with`. -= ##arguments## is not an Array = += `arguments` is not an Array = -You can convert ##arguments## to an array using JX.$A() or similar. Note that -you can pass ##arguments## to Function.prototype.apply() without converting it. +You can convert `arguments` to an array using JX.$A() or similar. Note that +you can pass `arguments` to Function.prototype.apply() without converting it. = Object, Array, and iteration are needlessly hard = @@ -82,6 +82,6 @@ objects and can have methods and properties, and inherit from Array.prototype, Number.prototype, etc.) and their logical behavior is at best absurd and at worst strictly wrong. -**Never use** ##new Number()##, ##new String()## or ##new Boolean()## unless +**Never use** `new Number()`, `new String()` or `new Boolean()` unless your Javascript is God Tier and you are absolutely sure you know what you are doing. diff --git a/src/docs/flavor/php_pitfalls.diviner b/src/docs/flavor/php_pitfalls.diviner index 2b8373f8cc..09e0f108f0 100644 --- a/src/docs/flavor/php_pitfalls.diviner +++ b/src/docs/flavor/php_pitfalls.diviner @@ -31,7 +31,7 @@ complex variables safely. = isset(), empty() and Truthiness = -A value is "truthy" if it evaluates to true in an ##if## clause: +A value is "truthy" if it evaluates to true in an `if` clause: $value = something(); if ($value) { @@ -61,7 +61,7 @@ This is wrong because it prevents users from making the comment "0". //THIS COMMENT IS TOTALLY AWESOME AND I MAKE IT ALL THE TIME SO YOU HAD BETTER NOT BREAK IT!!!// A better test is probably strlen(). -In addition to truth tests with ##if##, PHP has two special truthiness operators +In addition to truth tests with `if`, PHP has two special truthiness operators which look like functions but aren't: empty() and isset(). These operators help deal with undeclared variables. @@ -147,12 +147,12 @@ In a libphutil environment, you can often do this easily with These functions are much slower for even moderately large inputs than array_intersect_key() and array_diff_key(), because they can not make the -assumption that their inputs are unique scalars as the ##key## varieties can. -Strongly prefer the ##key## varieties. +assumption that their inputs are unique scalars as the `key` varieties can. +Strongly prefer the `key` varieties. = array_uintersect() and array_udiff() are Definitely Slow Too = -These functions have the problems of both the ##usort()## family and the +These functions have the problems of both the `usort()` family and the `array_diff()` family. Avoid them. = foreach() Does Not Create Scope = @@ -205,7 +205,7 @@ emerge unmodified. That is, if you have a function that takes references: $x = 41; call_user_func('add_one', $x); -...##$x## will not be modified. The solution is to use call_user_func_array() +...`$x` will not be modified. The solution is to use call_user_func_array() and wrap the reference in an array: $x = 41; @@ -243,7 +243,7 @@ dictionary (vs a list) when passed to json_encode(). = Invoking "new" With an Argument Vector is Really Hard = -If you have some ##$class_name## and some ##$argv## of constructor +If you have some `$class_name` and some `$argv` of constructor arguments and you want to do this: new $class_name($argv[0], $argv[1], ...); diff --git a/src/docs/flavor/recommendations_on_revision_control.diviner b/src/docs/flavor/recommendations_on_revision_control.diviner index 0cb0eb5e6c..8e30e2f4f1 100644 --- a/src/docs/flavor/recommendations_on_revision_control.diviner +++ b/src/docs/flavor/recommendations_on_revision_control.diviner @@ -24,11 +24,11 @@ master/remote version of the repository. Specifically, this means that an entire conceptual changeset ("add a foo widget") is represented in the remote as exactly one commit (in some form), not a sequence of checkpoint commits. - - In SVN, this means don't ##commit## until after an idea has been completely + - In SVN, this means don't `commit` until after an idea has been completely written. All reasonable SVN workflows naturally enforce this. - - In Git, this means squashing checkpoint commits as you go (with ##git commit - --amend##) or before pushing (with ##git rebase -i## or ##git merge - --squash##), or having a strict policy where your master/trunk contains only + - In Git, this means squashing checkpoint commits as you go (with `git commit + --amend`) or before pushing (with `git rebase -i` or `git merge + --squash`), or having a strict policy where your master/trunk contains only merge commits and each is a merge between the old master and a branch which represents a single idea. Although this preserves the checkpoint commits along the branches, you can view master alone as a series of single-idea diff --git a/src/docs/flavor/soon_static_resources.diviner b/src/docs/flavor/soon_static_resources.diviner index 78820c5081..96f28cfe2f 100644 --- a/src/docs/flavor/soon_static_resources.diviner +++ b/src/docs/flavor/soon_static_resources.diviner @@ -84,7 +84,7 @@ eventually moved to automatic generation based on production data. = Caches and Serving Content = In the simplest implementation of static resources, you write out a raw JS tag -with something like ##src="/js/base.js"##. This will break disastrously as you +with something like `src="/js/base.js"`. This will break disastrously as you scale, because clients will be running with stale versions of resources. There are bunch of subtle problems (especially once you have a CDN), but the big one is that if a user is browsing your site as you push/deploy, their client will diff --git a/src/docs/tech/celerity.diviner b/src/docs/tech/celerity.diviner index 91a53d6e8d..e1b744645b 100644 --- a/src/docs/tech/celerity.diviner +++ b/src/docs/tech/celerity.diviner @@ -29,7 +29,7 @@ Celerity's primary API is @{function:require_celerity_resource}, which marks a resource for inclusion when a response is rendered (e.g., when the HTML page is generated, or when the response to an Ajax request is built). For instance, if you use a CSS class like "widget-view", you must ensure the appropriate CSS is -included by calling ##require_celerity_resource('widget-view-css')## (or +included by calling `require_celerity_resource('widget-view-css')` (or similar), at your use site. This function uses @{class:CelerityAPI} to access the active @@ -48,14 +48,14 @@ and not all of its dependencies) and any packaging rules (so it may be able to generate fewer resource requests, improving performance). It then generates `