1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-18 19:40:55 +01:00

Modularize application transactions in Paste, mostly

Summary:
Ref T9789. `Transaction` and `Editor` classes are the last major pieces of infrastructure that haven't been fully modularized.

Some of the specific issues are:

  - `Editor` classes rely on a bunch of `instanceof` stuff in the base class to pick up transaction types like "subscribe", "projects", etc. Instead, applications should be adding these, and third-party applications should be able to add them.
  - Code is spread across `Transaction` and `Editor` classes somewhat oddly. For example, generating old/new values would probably make more sense at the `Transaction` level, but it currently exists at the `Editor` level.
  - Both types of classes have a lot of functions based on `switch()` statements, which require a ton of boilerplate and are just generally kind of hard to work with.

This creates classes for each type of transaction, and moves almost all of the logic to them. These classes are simpler and more focused than the old stuff was, and can organize related code better.

This starts inching toward defining `CoreTransactions` for features shared across applications. It only defines the "Create" transaction so far, but at some point I plan to move all the other shared transactions to Core and let them control which objects they're available for.

Test Plan:
  - Created pastes with web UI and API.
  - Edited all paste properites.
  - Archived/activated.
  - Verified files got reasonable names.
  - Reviewed timeline and feed.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T9789

Differential Revision: https://secure.phabricator.com/D16111
This commit is contained in:
epriestley 2016-06-09 16:00:06 -07:00
parent d68b2cc0e4
commit 33ec855449
21 changed files with 747 additions and 468 deletions

View file

@ -1,47 +1,5 @@
<?php
$table = new PhabricatorPaste();
$x_table = new PhabricatorPasteTransaction();
$conn_w = $table->establishConnection('w');
$conn_w->openTransaction();
echo pht('Adding transactions for existing paste objects...')."\n";
$rows = new LiskRawMigrationIterator($conn_w, 'pastebin_paste');
foreach ($rows as $row) {
$id = $row['id'];
echo pht('Adding transactions for paste id %d...', $id)."\n";
$xaction_phid = PhabricatorPHID::generateNewPHID(
PhabricatorApplicationTransactionTransactionPHIDType::TYPECONST);
queryfx(
$conn_w,
'INSERT INTO %T (phid, authorPHID, objectPHID, viewPolicy, editPolicy,
transactionType, oldValue, newValue,
contentSource, metadata, dateCreated, dateModified,
commentVersion)
VALUES (%s, %s, %s, %s, %s, %s, %ns, %ns, %s, %s, %d, %d, %d)',
$x_table->getTableName(),
$xaction_phid,
$row['authorPHID'],
$row['phid'],
'public',
$row['authorPHID'],
PhabricatorPasteTransaction::TYPE_CONTENT,
'null',
$row['filePHID'],
PhabricatorContentSource::newForSource(
PhabricatorOldWorldContentSource::SOURCECONST)->serialize(),
'[]',
$row['dateCreated'],
$row['dateCreated'],
0);
}
$conn_w->saveTransaction();
echo pht('Done.')."\n";
// Long ago, this migration populated initial "create" transactions for old
// pastes from before transactions came into existence. It was removed after
// about three years.

View file

@ -2160,6 +2160,9 @@ phutil_register_library_map(array(
'PhabricatorController' => 'applications/base/controller/PhabricatorController.php',
'PhabricatorCookies' => 'applications/auth/constants/PhabricatorCookies.php',
'PhabricatorCoreConfigOptions' => 'applications/config/option/PhabricatorCoreConfigOptions.php',
'PhabricatorCoreCreateTransaction' => 'applications/transactions/xaction/PhabricatorCoreCreateTransaction.php',
'PhabricatorCoreTransactionType' => 'applications/transactions/xaction/PhabricatorCoreTransactionType.php',
'PhabricatorCoreVoidTransaction' => 'applications/transactions/xaction/PhabricatorCoreVoidTransaction.php',
'PhabricatorCountdown' => 'applications/countdown/storage/PhabricatorCountdown.php',
'PhabricatorCountdownApplication' => 'applications/countdown/application/PhabricatorCountdownApplication.php',
'PhabricatorCountdownController' => 'applications/countdown/controller/PhabricatorCountdownController.php',
@ -2757,6 +2760,8 @@ phutil_register_library_map(array(
'PhabricatorMetaMTASendGridReceiveController' => 'applications/metamta/controller/PhabricatorMetaMTASendGridReceiveController.php',
'PhabricatorMetaMTAWorker' => 'applications/metamta/PhabricatorMetaMTAWorker.php',
'PhabricatorMetronomicTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorMetronomicTriggerClock.php',
'PhabricatorModularTransaction' => 'applications/transactions/storage/PhabricatorModularTransaction.php',
'PhabricatorModularTransactionType' => 'applications/transactions/storage/PhabricatorModularTransactionType.php',
'PhabricatorMonospacedFontSetting' => 'applications/settings/setting/PhabricatorMonospacedFontSetting.php',
'PhabricatorMonospacedTextareasSetting' => 'applications/settings/setting/PhabricatorMonospacedTextareasSetting.php',
'PhabricatorMotivatorProfilePanel' => 'applications/search/profilepanel/PhabricatorMotivatorProfilePanel.php',
@ -2916,12 +2921,14 @@ phutil_register_library_map(array(
'PhabricatorPasteArchiveController' => 'applications/paste/controller/PhabricatorPasteArchiveController.php',
'PhabricatorPasteConfigOptions' => 'applications/paste/config/PhabricatorPasteConfigOptions.php',
'PhabricatorPasteContentSearchEngineAttachment' => 'applications/paste/engineextension/PhabricatorPasteContentSearchEngineAttachment.php',
'PhabricatorPasteContentTransaction' => 'applications/paste/xaction/PhabricatorPasteContentTransaction.php',
'PhabricatorPasteController' => 'applications/paste/controller/PhabricatorPasteController.php',
'PhabricatorPasteDAO' => 'applications/paste/storage/PhabricatorPasteDAO.php',
'PhabricatorPasteEditController' => 'applications/paste/controller/PhabricatorPasteEditController.php',
'PhabricatorPasteEditEngine' => 'applications/paste/editor/PhabricatorPasteEditEngine.php',
'PhabricatorPasteEditor' => 'applications/paste/editor/PhabricatorPasteEditor.php',
'PhabricatorPasteFilenameContextFreeGrammar' => 'applications/paste/lipsum/PhabricatorPasteFilenameContextFreeGrammar.php',
'PhabricatorPasteLanguageTransaction' => 'applications/paste/xaction/PhabricatorPasteLanguageTransaction.php',
'PhabricatorPasteListController' => 'applications/paste/controller/PhabricatorPasteListController.php',
'PhabricatorPastePastePHIDType' => 'applications/paste/phid/PhabricatorPastePastePHIDType.php',
'PhabricatorPasteQuery' => 'applications/paste/query/PhabricatorPasteQuery.php',
@ -2930,10 +2937,13 @@ phutil_register_library_map(array(
'PhabricatorPasteSchemaSpec' => 'applications/paste/storage/PhabricatorPasteSchemaSpec.php',
'PhabricatorPasteSearchEngine' => 'applications/paste/query/PhabricatorPasteSearchEngine.php',
'PhabricatorPasteSnippet' => 'applications/paste/snippet/PhabricatorPasteSnippet.php',
'PhabricatorPasteStatusTransaction' => 'applications/paste/xaction/PhabricatorPasteStatusTransaction.php',
'PhabricatorPasteTestDataGenerator' => 'applications/paste/lipsum/PhabricatorPasteTestDataGenerator.php',
'PhabricatorPasteTitleTransaction' => 'applications/paste/xaction/PhabricatorPasteTitleTransaction.php',
'PhabricatorPasteTransaction' => 'applications/paste/storage/PhabricatorPasteTransaction.php',
'PhabricatorPasteTransactionComment' => 'applications/paste/storage/PhabricatorPasteTransactionComment.php',
'PhabricatorPasteTransactionQuery' => 'applications/paste/query/PhabricatorPasteTransactionQuery.php',
'PhabricatorPasteTransactionType' => 'applications/paste/xaction/PhabricatorPasteTransactionType.php',
'PhabricatorPasteViewController' => 'applications/paste/controller/PhabricatorPasteViewController.php',
'PhabricatorPathSetupCheck' => 'applications/config/check/PhabricatorPathSetupCheck.php',
'PhabricatorPeopleAnyOwnerDatasource' => 'applications/people/typeahead/PhabricatorPeopleAnyOwnerDatasource.php',
@ -6729,6 +6739,9 @@ phutil_register_library_map(array(
'PhabricatorController' => 'AphrontController',
'PhabricatorCookies' => 'Phobject',
'PhabricatorCoreConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorCoreCreateTransaction' => 'PhabricatorCoreTransactionType',
'PhabricatorCoreTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorCoreVoidTransaction' => 'PhabricatorModularTransactionType',
'PhabricatorCountdown' => array(
'PhabricatorCountdownDAO',
'PhabricatorPolicyInterface',
@ -7406,6 +7419,8 @@ phutil_register_library_map(array(
'PhabricatorMetaMTASendGridReceiveController' => 'PhabricatorMetaMTAController',
'PhabricatorMetaMTAWorker' => 'PhabricatorWorker',
'PhabricatorMetronomicTriggerClock' => 'PhabricatorTriggerClock',
'PhabricatorModularTransaction' => 'PhabricatorApplicationTransaction',
'PhabricatorModularTransactionType' => 'Phobject',
'PhabricatorMonospacedFontSetting' => 'PhabricatorStringSetting',
'PhabricatorMonospacedTextareasSetting' => 'PhabricatorSelectSetting',
'PhabricatorMotivatorProfilePanel' => 'PhabricatorProfilePanel',
@ -7601,12 +7616,14 @@ phutil_register_library_map(array(
'PhabricatorPasteArchiveController' => 'PhabricatorPasteController',
'PhabricatorPasteConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorPasteContentSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'PhabricatorPasteContentTransaction' => 'PhabricatorPasteTransactionType',
'PhabricatorPasteController' => 'PhabricatorController',
'PhabricatorPasteDAO' => 'PhabricatorLiskDAO',
'PhabricatorPasteEditController' => 'PhabricatorPasteController',
'PhabricatorPasteEditEngine' => 'PhabricatorEditEngine',
'PhabricatorPasteEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorPasteFilenameContextFreeGrammar' => 'PhutilContextFreeGrammar',
'PhabricatorPasteLanguageTransaction' => 'PhabricatorPasteTransactionType',
'PhabricatorPasteListController' => 'PhabricatorPasteController',
'PhabricatorPastePastePHIDType' => 'PhabricatorPHIDType',
'PhabricatorPasteQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
@ -7615,10 +7632,13 @@ phutil_register_library_map(array(
'PhabricatorPasteSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorPasteSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorPasteSnippet' => 'Phobject',
'PhabricatorPasteStatusTransaction' => 'PhabricatorPasteTransactionType',
'PhabricatorPasteTestDataGenerator' => 'PhabricatorTestDataGenerator',
'PhabricatorPasteTransaction' => 'PhabricatorApplicationTransaction',
'PhabricatorPasteTitleTransaction' => 'PhabricatorPasteTransactionType',
'PhabricatorPasteTransaction' => 'PhabricatorModularTransaction',
'PhabricatorPasteTransactionComment' => 'PhabricatorApplicationTransactionComment',
'PhabricatorPasteTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorPasteTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorPasteViewController' => 'PhabricatorPasteController',
'PhabricatorPathSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorPeopleAnyOwnerDatasource' => 'PhabricatorTypeaheadDatasource',

View file

@ -47,15 +47,15 @@ final class PasteCreateConduitAPIMethod extends PasteConduitAPIMethod {
$xactions = array();
$xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorPasteTransaction::TYPE_CONTENT)
->setTransactionType(PhabricatorPasteContentTransaction::TRANSACTIONTYPE)
->setNewValue($content);
$xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorPasteTransaction::TYPE_TITLE)
->setTransactionType(PhabricatorPasteTitleTransaction::TRANSACTIONTYPE)
->setNewValue($title);
$xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorPasteTransaction::TYPE_LANGUAGE)
->setTransactionType(PhabricatorPasteLanguageTransaction::TRANSACTIONTYPE)
->setNewValue($language);
$editor = id(new PhabricatorPasteEditor())

View file

@ -20,7 +20,7 @@ final class PhabricatorPasteArchiveController
return new Aphront404Response();
}
$view_uri = '/P'.$paste->getID();
$view_uri = $paste->getURI();
if ($request->isFormPost()) {
if ($paste->isArchived()) {
@ -32,7 +32,7 @@ final class PhabricatorPasteArchiveController
$xactions = array();
$xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorPasteTransaction::TYPE_STATUS)
->setTransactionType(PhabricatorPasteStatusTransaction::TRANSACTIONTYPE)
->setNewValue($new_status);
id(new PhabricatorPasteEditor())

View file

@ -71,7 +71,7 @@ final class PhabricatorPasteEditEngine
id(new PhabricatorTextEditField())
->setKey('title')
->setLabel(pht('Title'))
->setTransactionType(PhabricatorPasteTransaction::TYPE_TITLE)
->setTransactionType(PhabricatorPasteTitleTransaction::TRANSACTIONTYPE)
->setDescription(pht('The title of the paste.'))
->setConduitDescription(pht('Retitle the paste.'))
->setConduitTypeDescription(pht('New paste title.'))
@ -79,7 +79,8 @@ final class PhabricatorPasteEditEngine
id(new PhabricatorSelectEditField())
->setKey('language')
->setLabel(pht('Language'))
->setTransactionType(PhabricatorPasteTransaction::TYPE_LANGUAGE)
->setTransactionType(
PhabricatorPasteLanguageTransaction::TRANSACTIONTYPE)
->setAliases(array('lang'))
->setIsCopyable(true)
->setOptions($langs)
@ -94,7 +95,8 @@ final class PhabricatorPasteEditEngine
id(new PhabricatorTextAreaEditField())
->setKey('text')
->setLabel(pht('Text'))
->setTransactionType(PhabricatorPasteTransaction::TYPE_CONTENT)
->setTransactionType(
PhabricatorPasteContentTransaction::TRANSACTIONTYPE)
->setMonospaced(true)
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
->setDescription(pht('The main body text of the paste.'))
@ -104,7 +106,8 @@ final class PhabricatorPasteEditEngine
id(new PhabricatorSelectEditField())
->setKey('status')
->setLabel(pht('Status'))
->setTransactionType(PhabricatorPasteTransaction::TYPE_STATUS)
->setTransactionType(
PhabricatorPasteStatusTransaction::TRANSACTIONTYPE)
->setIsConduitOnly(true)
->setOptions(PhabricatorPaste::getStatusNameMap())
->setDescription(pht('Active or archived status.'))

View file

@ -3,8 +3,6 @@
final class PhabricatorPasteEditor
extends PhabricatorApplicationTransactionEditor {
private $fileName;
public function getEditorApplicationClass() {
return 'PhabricatorPasteApplication';
}
@ -13,29 +11,17 @@ final class PhabricatorPasteEditor
return pht('Pastes');
}
public static function initializeFileForPaste(
PhabricatorUser $actor,
$name,
$data) {
public function getCreateObjectTitle($author, $object) {
return pht('%s created this paste.', $author);
}
return PhabricatorFile::newFromFileData(
$data,
array(
'name' => $name,
'mime-type' => 'text/plain; charset=utf-8',
'authorPHID' => $actor->getPHID(),
'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
'editPolicy' => PhabricatorPolicies::POLICY_NOONE,
));
public function getCreateObjectTitleForFeed($author, $object) {
return pht('%s created %s.', $author, $object);
}
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = PhabricatorPasteTransaction::TYPE_CONTENT;
$types[] = PhabricatorPasteTransaction::TYPE_TITLE;
$types[] = PhabricatorPasteTransaction::TYPE_LANGUAGE;
$types[] = PhabricatorPasteTransaction::TYPE_STATUS;
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
$types[] = PhabricatorTransactions::TYPE_COMMENT;
@ -43,163 +29,14 @@ final class PhabricatorPasteEditor
return $types;
}
protected function shouldApplyInitialEffects(
PhabricatorLiskDAO $object,
array $xactions) {
return true;
}
protected function applyInitialEffects(
PhabricatorLiskDAO $object,
array $xactions) {
// Find the most user-friendly filename we can by examining the title of
// the paste and the pending transactions. We'll use this if we create a
// new file to store raw content later.
$name = $object->getTitle();
if (!strlen($name)) {
$name = 'paste.raw';
}
$type_title = PhabricatorPasteTransaction::TYPE_TITLE;
foreach ($xactions as $xaction) {
if ($xaction->getTransactionType() == $type_title) {
$name = $xaction->getNewValue();
}
}
$this->fileName = $name;
}
protected function validateTransaction(
PhabricatorLiskDAO $object,
$type,
array $xactions) {
$errors = parent::validateTransaction($object, $type, $xactions);
switch ($type) {
case PhabricatorPasteTransaction::TYPE_CONTENT:
if (!$object->getFilePHID() && !$xactions) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Required'),
pht('You must provide content to create a paste.'),
null);
$error->setIsMissingFieldError(true);
$errors[] = $error;
}
break;
}
return $errors;
}
protected function getCustomTransactionOldValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorPasteTransaction::TYPE_CONTENT:
return $object->getFilePHID();
case PhabricatorPasteTransaction::TYPE_TITLE:
return $object->getTitle();
case PhabricatorPasteTransaction::TYPE_LANGUAGE:
return $object->getLanguage();
case PhabricatorPasteTransaction::TYPE_STATUS:
return $object->getStatus();
}
}
protected function getCustomTransactionNewValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorPasteTransaction::TYPE_TITLE:
case PhabricatorPasteTransaction::TYPE_LANGUAGE:
case PhabricatorPasteTransaction::TYPE_STATUS:
return $xaction->getNewValue();
case PhabricatorPasteTransaction::TYPE_CONTENT:
// If this transaction does not really change the paste content, return
// the current file PHID so this transaction no-ops.
$new_content = $xaction->getNewValue();
$old_content = $object->getRawContent();
$file_phid = $object->getFilePHID();
if (($new_content === $old_content) && $file_phid) {
return $file_phid;
}
$file = self::initializeFileForPaste(
$this->getActor(),
$this->fileName,
$xaction->getNewValue());
return $file->getPHID();
}
}
protected function applyCustomInternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorPasteTransaction::TYPE_CONTENT:
$object->setFilePHID($xaction->getNewValue());
return;
case PhabricatorPasteTransaction::TYPE_TITLE:
$object->setTitle($xaction->getNewValue());
return;
case PhabricatorPasteTransaction::TYPE_LANGUAGE:
$object->setLanguage($xaction->getNewValue());
return;
case PhabricatorPasteTransaction::TYPE_STATUS:
$object->setStatus($xaction->getNewValue());
return;
}
return parent::applyCustomInternalTransaction($object, $xaction);
}
protected function applyCustomExternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorPasteTransaction::TYPE_CONTENT:
case PhabricatorPasteTransaction::TYPE_TITLE:
case PhabricatorPasteTransaction::TYPE_LANGUAGE:
case PhabricatorPasteTransaction::TYPE_STATUS:
return;
}
return parent::applyCustomExternalTransaction($object, $xaction);
}
protected function extractFilePHIDsFromCustomTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorPasteTransaction::TYPE_CONTENT:
return array($xaction->getNewValue());
}
return parent::extractFilePHIDsFromCustomTransaction($object, $xaction);
}
protected function shouldSendMail(
PhabricatorLiskDAO $object,
array $xactions) {
foreach ($xactions as $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorPasteTransaction::TYPE_CONTENT:
return false;
default:
break;
}
if ($this->getIsNewObject()) {
return false;
}
return true;
}
@ -258,8 +95,4 @@ final class PhabricatorPasteEditor
return true;
}
protected function supportsSearch() {
return false;
}
}

View file

@ -17,15 +17,15 @@ final class PhabricatorPasteTestDataGenerator
$xactions = array();
$xactions[] = $this->newTransaction(
PhabricatorPasteTransaction::TYPE_TITLE,
PhabricatorPasteTitleTransaction::TRANSACTIONTYPE,
$name);
$xactions[] = $this->newTransaction(
PhabricatorPasteTransaction::TYPE_LANGUAGE,
PhabricatorPasteLanguageTransaction::TRANSACTIONTYPE,
$language);
$xactions[] = $this->newTransaction(
PhabricatorPasteTransaction::TYPE_CONTENT,
PhabricatorPasteContentTransaction::TRANSACTIONTYPE,
$content);
$editor = id(new PhabricatorPasteEditor())

View file

@ -24,17 +24,13 @@ final class PasteCreateMailReceiver extends PhabricatorMailReceiver {
$xactions = array();
$xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorPasteTransaction::TYPE_CONTENT)
->setTransactionType(PhabricatorPasteContentTransaction::TRANSACTIONTYPE)
->setNewValue($mail->getCleanTextBody());
$xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorPasteTransaction::TYPE_TITLE)
->setTransactionType(PhabricatorPasteTitleTransaction::TRANSACTIONTYPE)
->setNewValue($title);
$xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorPasteTransaction::TYPE_LANGUAGE)
->setNewValue(''); // auto-detect
$paste = PhabricatorPaste::initializeNewPaste($sender);
$content_source = $mail->newContentSource();

View file

@ -1,12 +1,7 @@
<?php
final class PhabricatorPasteTransaction
extends PhabricatorApplicationTransaction {
const TYPE_CONTENT = 'paste.create';
const TYPE_TITLE = 'paste.title';
const TYPE_LANGUAGE = 'paste.language';
const TYPE_STATUS = 'paste.status';
extends PhabricatorModularTransaction {
const MAILTAG_CONTENT = 'paste-content';
const MAILTAG_OTHER = 'paste-other';
@ -24,226 +19,16 @@ final class PhabricatorPasteTransaction
return new PhabricatorPasteTransactionComment();
}
public function getRequiredHandlePHIDs() {
$phids = parent::getRequiredHandlePHIDs();
switch ($this->getTransactionType()) {
case self::TYPE_CONTENT:
$phids[] = $this->getObjectPHID();
break;
}
return $phids;
}
public function shouldHide() {
$old = $this->getOldValue();
switch ($this->getTransactionType()) {
case self::TYPE_TITLE:
case self::TYPE_LANGUAGE:
if ($old === null) {
return true;
}
break;
}
return parent::shouldHide();
}
public function getIcon() {
switch ($this->getTransactionType()) {
case self::TYPE_CONTENT:
return 'fa-plus';
break;
case self::TYPE_TITLE:
case self::TYPE_LANGUAGE:
return 'fa-pencil';
break;
case self::TYPE_STATUS:
$new = $this->getNewValue();
switch ($new) {
case PhabricatorPaste::STATUS_ACTIVE:
return 'fa-check';
case PhabricatorPaste::STATUS_ARCHIVED:
return 'fa-ban';
}
break;
}
return parent::getIcon();
}
public function getTitle() {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
$type = $this->getTransactionType();
switch ($type) {
case PhabricatorTransactions::TYPE_CREATE:
return pht(
'%s created this paste.',
$this->renderHandleLink($author_phid));
case self::TYPE_CONTENT:
if ($old === null) {
return pht(
'%s created this paste.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s edited the content of this paste.',
$this->renderHandleLink($author_phid));
}
break;
case self::TYPE_TITLE:
return pht(
'%s updated the paste\'s title to "%s".',
$this->renderHandleLink($author_phid),
$new);
break;
case self::TYPE_LANGUAGE:
return pht(
"%s updated the paste's language.",
$this->renderHandleLink($author_phid));
break;
case self::TYPE_STATUS:
switch ($new) {
case PhabricatorPaste::STATUS_ACTIVE:
return pht(
'%s activated this paste.',
$this->renderHandleLink($author_phid));
case PhabricatorPaste::STATUS_ARCHIVED:
return pht(
'%s archived this paste.',
$this->renderHandleLink($author_phid));
}
break;
}
return parent::getTitle();
}
public function getTitleForFeed() {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
$type = $this->getTransactionType();
switch ($type) {
case self::TYPE_CONTENT:
if ($old === null) {
return pht(
'%s created %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
} else {
return pht(
'%s edited %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
break;
case self::TYPE_TITLE:
return pht(
'%s updated the title for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
break;
case self::TYPE_LANGUAGE:
return pht(
'%s updated the language for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
break;
case self::TYPE_STATUS:
switch ($new) {
case PhabricatorPaste::STATUS_ACTIVE:
return pht(
'%s activated %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case PhabricatorPaste::STATUS_ARCHIVED:
return pht(
'%s archived %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
break;
}
return parent::getTitleForFeed();
}
public function getColor() {
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_CONTENT:
return PhabricatorTransactions::COLOR_GREEN;
case self::TYPE_STATUS:
switch ($new) {
case PhabricatorPaste::STATUS_ACTIVE:
return 'green';
case PhabricatorPaste::STATUS_ARCHIVED:
return 'indigo';
}
break;
}
return parent::getColor();
}
public function hasChangeDetails() {
switch ($this->getTransactionType()) {
case self::TYPE_CONTENT:
return ($this->getOldValue() !== null);
}
return parent::hasChangeDetails();
}
public function renderChangeDetails(PhabricatorUser $viewer) {
switch ($this->getTransactionType()) {
case self::TYPE_CONTENT:
$old = $this->getOldValue();
$new = $this->getNewValue();
$files = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs(array_filter(array($old, $new)))
->execute();
$files = mpull($files, null, 'getPHID');
$old_text = '';
if (idx($files, $old)) {
$old_text = $files[$old]->loadFileData();
}
$new_text = '';
if (idx($files, $new)) {
$new_text = $files[$new]->loadFileData();
}
return $this->renderTextCorpusChangeDetails(
$viewer,
$old_text,
$new_text);
}
return parent::renderChangeDetails($viewer);
public function getBaseTransactionClass() {
return 'PhabricatorPasteTransactionType';
}
public function getMailTags() {
$tags = array();
switch ($this->getTransactionType()) {
case self::TYPE_TITLE:
case self::TYPE_CONTENT:
case self::TYPE_LANGUAGE:
case PhabricatorPasteTitleTransaction::TRANSACTIONTYPE:
case PhabricatorPasteContentTransaction::TRANSACTIONTYPE:
case PhabricatorPasteLanguageTransaction::TRANSACTIONTYPE:
$tags[] = self::MAILTAG_CONTENT;
break;
case PhabricatorTransactions::TYPE_COMMENT:

View file

@ -0,0 +1,135 @@
<?php
final class PhabricatorPasteContentTransaction
extends PhabricatorPasteTransactionType {
const TRANSACTIONTYPE = 'paste.create';
private $fileName;
public function generateOldValue($object) {
return $object->getFilePHID();
}
public function applyInternalEffects($object, $value) {
$object->setFilePHID($value);
}
public function extractFilePHIDs($object, $value) {
return array($value);
}
public function validateTransactions($object, array $xactions) {
if ($object->getFilePHID() || $xactions) {
return array();
}
$error = $this->newError(
pht('Required'),
pht('You must provide content to create a paste.'));
$error->setIsMissingFieldError(true);
return array($error);
}
public function willApplyTransactions($object, array $xactions) {
// Find the most user-friendly filename we can by examining the title of
// the paste and the pending transactions. We'll use this if we create a
// new file to store raw content later.
$name = $object->getTitle();
if (!strlen($name)) {
$name = 'paste.raw';
}
$type_title = PhabricatorPasteTitleTransaction::TRANSACTIONTYPE;
foreach ($xactions as $xaction) {
if ($xaction->getTransactionType() == $type_title) {
$name = $xaction->getNewValue();
}
}
$this->fileName = $name;
}
public function generateNewValue($object, $value) {
// If this transaction does not really change the paste content, return
// the current file PHID so this transaction no-ops.
$old_content = $object->getRawContent();
if ($value === $old_content) {
$file_phid = $object->getFilePHID();
if ($file_phid) {
return $file_phid;
}
}
$editor = $this->getEditor();
$actor = $editor->getActor();
$file = $this->newFileForPaste($actor, $this->fileName, $value);
return $file->getPHID();
}
private function newFileForPaste(PhabricatorUser $actor, $name, $data) {
return PhabricatorFile::newFromFileData(
$data,
array(
'name' => $name,
'mime-type' => 'text/plain; charset=utf-8',
'authorPHID' => $actor->getPHID(),
'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
'editPolicy' => PhabricatorPolicies::POLICY_NOONE,
));
}
public function getIcon() {
return 'fa-plus';
}
public function getTitle() {
return pht(
'%s edited the content of this paste.',
$this->renderAuthor());
}
public function getTitleForFeed() {
return pht(
'%s edited %s.',
$this->renderAuthor(),
$this->renderObject());
}
public function hasChangeDetailView() {
return true;
}
public function newChangeDetailView() {
$viewer = $this->getViewer();
$old = $this->getOldValue();
$new = $this->getNewValue();
$files = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs(array($old, $new))
->execute();
$files = mpull($files, null, 'getPHID');
$old_text = '';
if (idx($files, $old)) {
$old_text = $files[$old]->loadFileData();
}
$new_text = '';
if (idx($files, $new)) {
$new_text = $files[$new]->loadFileData();
}
return id(new PhabricatorApplicationTransactionTextDiffDetailView())
->setViewer($viewer)
->setOldText($old_text)
->setNewText($new_text);
}
}

View file

@ -0,0 +1,29 @@
<?php
final class PhabricatorPasteLanguageTransaction
extends PhabricatorPasteTransactionType {
const TRANSACTIONTYPE = 'paste.language';
public function generateOldValue($object) {
return $object->getLanguage();
}
public function applyInternalEffects($object, $value) {
$object->setLanguage($value);
}
public function getTitle() {
return pht(
"%s updated the paste's language.",
$this->renderAuthor());
}
public function getTitleForFeed() {
return pht(
'%s updated the language for %s.',
$this->renderAuthor(),
$this->renderObject());
}
}

View file

@ -0,0 +1,62 @@
<?php
final class PhabricatorPasteStatusTransaction
extends PhabricatorPasteTransactionType {
const TRANSACTIONTYPE = 'paste.status';
public function generateOldValue($object) {
return $object->getStatus();
}
public function applyInternalEffects($object, $value) {
$object->setStatus($value);
}
private function isActivate() {
return ($this->getNewValue() == PhabricatorPaste::STATUS_ARCHIVED);
}
public function getIcon() {
if ($this->isActivate()) {
return 'fa-check';
} else {
return 'fa-ban';
}
}
public function getColor() {
if ($this->isActivate()) {
return 'green';
} else {
return 'indigo';
}
}
public function getTitle() {
if ($this->isActivate()) {
return pht(
'%s activated this paste.',
$this->renderAuthor());
} else {
return pht(
'%s archived this paste.',
$this->renderAuthor());
}
}
public function getTitleForFeed() {
if ($this->isActivate()) {
return pht(
'%s activated %s.',
$this->renderAuthor(),
$this->renderObject());
} else {
return pht(
'%s archived %s.',
$this->renderAuthor(),
$this->renderObject());
}
}
}

View file

@ -0,0 +1,33 @@
<?php
final class PhabricatorPasteTitleTransaction
extends PhabricatorPasteTransactionType {
const TRANSACTIONTYPE = 'paste.title';
public function generateOldValue($object) {
return $object->getTitle();
}
public function applyInternalEffects($object, $value) {
$object->setTitle($value);
}
public function getTitle() {
return pht(
'%s updated the paste\'s title from "%s" to "%s".',
$this->renderAuthor(),
$this->getOldValue(),
$this->getNewValue());
}
public function getTitleForFeed() {
return pht(
'%s updated the title for %s from "%s" to "%s".',
$this->renderAuthor(),
$this->renderObject(),
$this->getOldValue(),
$this->getNewValue());
}
}

View file

@ -0,0 +1,4 @@
<?php
abstract class PhabricatorPasteTransactionType
extends PhabricatorModularTransactionType {}

View file

@ -68,6 +68,7 @@ abstract class PhabricatorApplicationTransactionEditor
private $mailCCPHIDs = array();
private $feedNotifyPHIDs = array();
private $feedRelatedPHIDs = array();
private $modularTypes;
const STORAGE_ENCODING_BINARY = 'binary';
@ -285,6 +286,14 @@ abstract class PhabricatorApplicationTransactionEditor
$types[] = PhabricatorTransactions::TYPE_SPACE;
}
$template = $this->object->getApplicationTransactionTemplate();
if ($template instanceof PhabricatorModularTransaction) {
$xtypes = $template->newModularTransactionTypes();
foreach ($xtypes as $xtype) {
$types[] = $xtype->getTransactionTypeConstant();
}
}
return $types;
}
@ -304,7 +313,15 @@ abstract class PhabricatorApplicationTransactionEditor
private function getTransactionOldValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
$type = $xaction->getTransactionType();
$xtype = $this->getModularTransactionType($type);
if ($xtype) {
return $xtype->generateOldValue($object);
}
switch ($type) {
case PhabricatorTransactions::TYPE_CREATE:
return null;
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
@ -374,7 +391,15 @@ abstract class PhabricatorApplicationTransactionEditor
private function getTransactionNewValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
$type = $xaction->getTransactionType();
$xtype = $this->getModularTransactionType($type);
if ($xtype) {
return $xtype->generateNewValue($object, $xaction->getNewValue());
}
switch ($type) {
case PhabricatorTransactions::TYPE_CREATE:
return null;
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
@ -496,7 +521,14 @@ abstract class PhabricatorApplicationTransactionEditor
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
$type = $xaction->getTransactionType();
$xtype = $this->getModularTransactionType($type);
if ($xtype) {
return $xtype->applyInternalEffects($object, $xaction->getNewValue());
}
switch ($type) {
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getCustomFieldForTransaction($object, $xaction);
return $field->applyApplicationTransactionInternalEffects($xaction);
@ -520,7 +552,15 @@ abstract class PhabricatorApplicationTransactionEditor
private function applyExternalEffects(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
$type = $xaction->getTransactionType();
$xtype = $this->getModularTransactionType($type);
if ($xtype) {
return $xtype->applyExternalEffects($object, $xaction->getNewValue());
}
switch ($type) {
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
$subeditor = id(new PhabricatorSubscriptionsEditor())
->setObject($object)
@ -802,6 +842,8 @@ abstract class PhabricatorApplicationTransactionEditor
throw new PhabricatorApplicationTransactionValidationException($errors);
}
$this->willApplyTransactions($object, $xactions);
if ($object->getID()) {
foreach ($xactions as $xaction) {
@ -2006,6 +2048,12 @@ abstract class PhabricatorApplicationTransactionEditor
array $xactions) {
$errors = array();
$xtype = $this->getModularTransactionType($type);
if ($xtype) {
$errors[] = $xtype->validateTransactions($object, $xactions);
}
switch ($type) {
case PhabricatorTransactions::TYPE_VIEW_POLICY:
$errors[] = $this->validatePolicyTransaction(
@ -3129,9 +3177,16 @@ abstract class PhabricatorApplicationTransactionEditor
}
foreach ($xactions as $xaction) {
$phids[] = $this->extractFilePHIDsFromCustomTransaction(
$object,
$xaction);
$type = $xaction->getTransactionType();
$xtype = $this->getModularTransactionType($type);
if ($xtype) {
$phids[] = $xtype->extractFilePHIDs($object, $xaction->getNewValue());
} else {
$phids[] = $this->extractFilePHIDsFromCustomTransaction(
$object,
$xaction);
}
}
$phids = array_unique(array_filter(array_mergev($phids)));
@ -3692,5 +3747,50 @@ abstract class PhabricatorApplicationTransactionEditor
$proxy_phids);
}
private function getModularTransactionTypes() {
if ($this->modularTypes === null) {
$template = $this->object->getApplicationTransactionTemplate();
if ($template instanceof PhabricatorModularTransaction) {
$xtypes = $template->newModularTransactionTypes();
foreach ($xtypes as $key => $xtype) {
$xtype = clone $xtype;
$xtype->setEditor($this);
$xtypes[$key] = $xtype;
}
} else {
$xtypes = array();
}
$this->modularTypes = $xtypes;
}
return $this->modularTypes;
}
private function getModularTransactionType($type) {
$types = $this->getModularTransactionTypes();
return idx($types, $type);
}
private function willApplyTransactions($object, array $xactions) {
foreach ($xactions as $xaction) {
$type = $xaction->getTransactionType();
$xtype = $this->getModularTransactionType($type);
if (!$xtype) {
continue;
}
$xtype->willApplyTransactions($object, $xactions);
}
}
public function getCreateObjectTitle($author, $object) {
return pht('%s created this object.', $author);
}
public function getCreateObjectTitleForFeed($author, $object) {
return pht('%s created an object: %s.', $author, $object);
}
}

View file

@ -280,6 +280,7 @@ abstract class PhabricatorApplicationTransaction
$new = $this->getNewValue();
$phids[] = array($this->getAuthorPHID());
$phids[] = array($this->getObjectPHID());
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getTransactionCustomField();

View file

@ -0,0 +1,138 @@
<?php
abstract class PhabricatorModularTransaction
extends PhabricatorApplicationTransaction {
private $implementation;
abstract public function getBaseTransactionClass();
final protected function getTransactionImplementation() {
if (!$this->implementation) {
$this->implementation = $this->newTransactionImplementation();
}
return $this->implementation;
}
public function newModularTransactionTypes() {
$base_class = $this->getBaseTransactionClass();
$types = id(new PhutilClassMapQuery())
->setAncestorClass($base_class)
->setUniqueMethod('getTransactionTypeConstant')
->execute();
// Add core transaction types.
$types += id(new PhutilClassMapQuery())
->setAncestorClass('PhabricatorCoreTransactionType')
->setUniqueMethod('getTransactionTypeConstant')
->execute();
return $types;
}
private function newTransactionImplementation() {
$types = $this->newModularTransactionTypes();
$key = $this->getTransactionType();
if (empty($types[$key])) {
$type = new PhabricatorCoreVoidTransaction();
} else {
$type = clone $types[$key];
}
$type->setStorage($this);
return $type;
}
final public function generateOldValue($object) {
return $this->getTransactionImplementation()->generateOldValue($object);
}
final public function generateNewValue($object) {
return $this->getTransactionImplementation()
->generateNewValue($object, $this->getNewValue());
}
final public function willApplyTransactions($object, array $xactions) {
return $this->getTransactionImplementation()
->willApplyTransactions($object, $xactions);
}
final public function applyInternalEffects($object) {
return $this->getTransactionImplementation()
->applyInternalEffects($object);
}
final public function applyExternalEffects($object) {
return $this->getTransactionImplementation()
->applyExternalEffects($object);
}
final public function shouldHide() {
if ($this->getTransactionImplementation()->shouldHide()) {
return true;
}
return parent::shouldHide();
}
final public function getIcon() {
$icon = $this->getTransactionImplementation()->getIcon();
if ($icon !== null) {
return $icon;
}
return parent::getIcon();
}
final public function getTitle() {
$title = $this->getTransactionImplementation()->getTitle();
if ($title !== null) {
return $title;
}
return parent::getTitle();
}
final public function getTitleForFeed() {
$title = $this->getTransactionImplementation()->getTitleForFeed();
if ($title !== null) {
return $title;
}
return $this->getTitle();
}
final public function getColor() {
$color = $this->getTransactionImplementation()->getColor();
if ($color !== null) {
return $color;
}
return parent::getColor();
}
final public function hasChangeDetails() {
if ($this->getTransactionImplementation()->hasChangeDetailView()) {
return true;
}
return parent::hasChangeDetails();
}
final public function renderChangeDetails(PhabricatorUser $viewer) {
$impl = $this->getTransactionImplementation();
$impl->setViewer($viewer);
$view = $impl->newChangeDetailView();
if ($view !== null) {
return $view;
}
return parent::renderChangeDetails($viewer);
}
}

View file

@ -0,0 +1,140 @@
<?php
abstract class PhabricatorModularTransactionType
extends Phobject {
private $storage;
private $viewer;
private $editor;
final public function getTransactionTypeConstant() {
return $this->getPhobjectClassConstant('TRANSACTIONTYPE');
}
public function generateOldValue($object) {
throw new PhutilMethodNotImplementedException();
}
public function generateNewValue($object, $value) {
return $value;
}
public function validateTransactions($object, array $xactions) {
return array();
}
public function willApplyTransactions($object, array $xactions) {
return;
}
public function applyInternalEffects($object, $value) {
return;
}
public function applyExternalEffects($object, $value) {
return;
}
public function extractFilePHIDs($object, $value) {
return array();
}
public function shouldHide() {
return false;
}
public function getIcon() {
return null;
}
public function getTitle() {
return null;
}
public function getTitleForFeed() {
return null;
}
public function getColor() {
return null;
}
public function hasChangeDetailView() {
return false;
}
public function newChangeDetailView() {
throw new PhutilMethodNotImplementedException();
}
final public function setStorage(
PhabricatorApplicationTransaction $xaction) {
$this->storage = $xaction;
return $this;
}
private function getStorage() {
return $this->storage;
}
final public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
final protected function getViewer() {
return $this->viewer;
}
final public function setEditor(
PhabricatorApplicationTransactionEditor $editor) {
$this->editor = $editor;
return $this;
}
final protected function getEditor() {
if (!$this->editor) {
throw new PhutilInvalidStateException('setEditor');
}
return $this->editor;
}
final protected function getAuthorPHID() {
return $this->getStorage()->getAuthorPHID();
}
final protected function getObjectPHID() {
return $this->getStorage()->getObjectPHID();
}
final protected function getObject() {
return $this->getStorage()->getObject();
}
final protected function getOldValue() {
return $this->getStorage()->getOldValue();
}
final protected function getNewValue() {
return $this->getStorage()->getNewValue();
}
final protected function renderAuthor() {
$author_phid = $this->getAuthorPHID();
return $this->getStorage()->renderHandleLink($author_phid);
}
final protected function renderObject() {
$object_phid = $this->getObjectPHID();
return $this->getStorage()->renderHandleLink($object_phid);
}
final protected function newError($title, $message, $xaction = null) {
return new PhabricatorApplicationTransactionValidationError(
$this->getTransactionTypeConstant(),
$title,
$message,
$xaction);
}
}

View file

@ -0,0 +1,30 @@
<?php
final class PhabricatorCoreCreateTransaction
extends PhabricatorCoreTransactionType {
const TRANSACTIONTYPE = 'core:create';
public function generateOldValue($object) {
return null;
}
public function getTitle() {
$editor = $this->getObject()->getApplicationTransactionEditor();
$author = $this->renderAuthor();
$object = $this->renderObject();
return $editor->getCreateObjectTitle($author, $object);
}
public function getTitleForFeed() {
$editor = $this->getObject()->getApplicationTransactionEditor();
$author = $this->renderAuthor();
$object = $this->renderObject();
return $editor->getCreateObjectTitleForFeed($author, $object);
}
}

View file

@ -0,0 +1,4 @@
<?php
abstract class PhabricatorCoreTransactionType
extends PhabricatorModularTransactionType {}

View file

@ -0,0 +1,8 @@
<?php
final class PhabricatorCoreVoidTransaction
extends PhabricatorModularTransactionType {
const TRANSACTIONTYPE = 'core.void';
}