diff --git a/resources/sql/autopatches/20181219.pholio.01.imagephid.sql b/resources/sql/autopatches/20181219.pholio.01.imagephid.sql new file mode 100644 index 0000000000..870cddd950 --- /dev/null +++ b/resources/sql/autopatches/20181219.pholio.01.imagephid.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_pholio.pholio_image + ADD mockPHID VARBINARY(64); diff --git a/resources/sql/autopatches/20181219.pholio.02.imagemigrate.php b/resources/sql/autopatches/20181219.pholio.02.imagemigrate.php new file mode 100644 index 0000000000..f1fc1b3c37 --- /dev/null +++ b/resources/sql/autopatches/20181219.pholio.02.imagemigrate.php @@ -0,0 +1,35 @@ +establishConnection('w'); +$iterator = new LiskRawMigrationIterator($conn, $image->getTableName()); + +foreach ($iterator as $image_row) { + if ($image_row['mockPHID']) { + continue; + } + + $mock_id = $image_row['mockID']; + + $mock_row = queryfx_one( + $conn, + 'SELECT phid FROM %R WHERE id = %d', + $mock, + $mock_id); + + if (!$mock_row) { + continue; + } + + queryfx( + $conn, + 'UPDATE %R SET mockPHID = %s WHERE id = %d', + $image, + $mock_row['phid'], + $image_row['id']); +} diff --git a/resources/sql/autopatches/20181219.pholio.03.imageid.sql b/resources/sql/autopatches/20181219.pholio.03.imageid.sql new file mode 100644 index 0000000000..3a3cb029ac --- /dev/null +++ b/resources/sql/autopatches/20181219.pholio.03.imageid.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_pholio.pholio_image + DROP mockID; diff --git a/src/applications/pholio/editor/PholioMockEditor.php b/src/applications/pholio/editor/PholioMockEditor.php index d1f7daf3a5..a653272fba 100644 --- a/src/applications/pholio/editor/PholioMockEditor.php +++ b/src/applications/pholio/editor/PholioMockEditor.php @@ -1,278 +1,278 @@ newImages = $new_images; return $this; } public function getNewImages() { return $this->newImages; } public function getCreateObjectTitle($author, $object) { return pht('%s created this mock.', $author); } public function getCreateObjectTitleForFeed($author, $object) { return pht('%s created %s.', $author, $object); } public function getTransactionTypes() { $types = parent::getTransactionTypes(); $types[] = PhabricatorTransactions::TYPE_EDGE; $types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; return $types; } protected function shouldApplyInitialEffects( PhabricatorLiskDAO $object, array $xactions) { foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { case PholioImageFileTransaction::TRANSACTIONTYPE: case PholioImageReplaceTransaction::TRANSACTIONTYPE: return true; break; } } return false; } protected function applyInitialEffects( PhabricatorLiskDAO $object, array $xactions) { $new_images = array(); foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { case PholioImageFileTransaction::TRANSACTIONTYPE: $new_value = $xaction->getNewValue(); foreach ($new_value as $key => $txn_images) { if ($key != '+') { continue; } foreach ($txn_images as $image) { $image->save(); $new_images[] = $image; } } break; case PholioImageReplaceTransaction::TRANSACTIONTYPE: $image = $xaction->getNewValue(); $image->save(); $new_images[] = $image; break; } } $this->setNewImages($new_images); } protected function applyFinalEffects( PhabricatorLiskDAO $object, array $xactions) { $images = $this->getNewImages(); foreach ($images as $image) { - $image->setMockID($object->getID()); + $image->setMockPHID($object->getPHID()); $image->save(); } return $xactions; } protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { return true; } protected function buildReplyHandler(PhabricatorLiskDAO $object) { return id(new PholioReplyHandler()) ->setMailReceiver($object); } protected function buildMailTemplate(PhabricatorLiskDAO $object) { $id = $object->getID(); $name = $object->getName(); return id(new PhabricatorMetaMTAMail()) ->setSubject("M{$id}: {$name}"); } protected function getMailTo(PhabricatorLiskDAO $object) { return array( $object->getAuthorPHID(), $this->requireActor()->getPHID(), ); } protected function buildMailBody( PhabricatorLiskDAO $object, array $xactions) { $viewer = $this->requireActor(); $body = id(new PhabricatorMetaMTAMailBody()) ->setViewer($viewer); $mock_uri = $object->getURI(); $mock_uri = PhabricatorEnv::getProductionURI($mock_uri); $this->addHeadersAndCommentsToMailBody( $body, $xactions, pht('View Mock'), $mock_uri); $type_inline = PholioMockInlineTransaction::TRANSACTIONTYPE; $inlines = array(); foreach ($xactions as $xaction) { if ($xaction->getTransactionType() == $type_inline) { $inlines[] = $xaction; } } $this->appendInlineCommentsForMail($object, $inlines, $body); $body->addLinkSection( pht('MOCK DETAIL'), PhabricatorEnv::getProductionURI('/M'.$object->getID())); return $body; } private function appendInlineCommentsForMail( $object, array $inlines, PhabricatorMetaMTAMailBody $body) { if (!$inlines) { return; } $viewer = $this->requireActor(); $header = pht('INLINE COMMENTS'); $body->addRawPlaintextSection($header); $body->addRawHTMLSection(phutil_tag('strong', array(), $header)); $image_ids = array(); foreach ($inlines as $inline) { $comment = $inline->getComment(); $image_id = $comment->getImageID(); $image_ids[$image_id] = $image_id; } $images = id(new PholioImageQuery()) ->setViewer($viewer) ->withIDs($image_ids) ->execute(); $images = mpull($images, null, 'getID'); foreach ($inlines as $inline) { $comment = $inline->getComment(); $content = $comment->getContent(); $image_id = $comment->getImageID(); $image = idx($images, $image_id); if ($image) { $image_name = $image->getName(); } else { $image_name = pht('Unknown (ID %d)', $image_id); } $body->addRemarkupSection( pht('Image "%s":', $image_name), $content); } } protected function getMailSubjectPrefix() { return PhabricatorEnv::getEnvConfig('metamta.pholio.subject-prefix'); } public function getMailTagsMap() { return array( PholioTransaction::MAILTAG_STATUS => pht("A mock's status changes."), PholioTransaction::MAILTAG_COMMENT => pht('Someone comments on a mock.'), PholioTransaction::MAILTAG_UPDATED => pht('Mock images or descriptions change.'), PholioTransaction::MAILTAG_OTHER => pht('Other mock activity not listed above occurs.'), ); } protected function shouldPublishFeedStory( PhabricatorLiskDAO $object, array $xactions) { return true; } protected function supportsSearch() { return true; } protected function shouldApplyHeraldRules( PhabricatorLiskDAO $object, array $xactions) { return true; } protected function buildHeraldAdapter( PhabricatorLiskDAO $object, array $xactions) { return id(new HeraldPholioMockAdapter()) ->setMock($object); } protected function sortTransactions(array $xactions) { $head = array(); $tail = array(); // Move inline comments to the end, so the comments precede them. foreach ($xactions as $xaction) { $type = $xaction->getTransactionType(); if ($type == PholioMockInlineTransaction::TRANSACTIONTYPE) { $tail[] = $xaction; } else { $head[] = $xaction; } } return array_values(array_merge($head, $tail)); } protected function shouldImplyCC( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PholioMockInlineTransaction::TRANSACTIONTYPE: return true; } return parent::shouldImplyCC($object, $xaction); } } diff --git a/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php b/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php index 041e14fcbf..b97a5fc3c7 100644 --- a/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php +++ b/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php @@ -1,121 +1,121 @@ loadPhabricatorUserPHID(); $author = id(new PhabricatorUser()) ->loadOneWhere('phid = %s', $author_phid); $mock = PholioMock::initializeNewMock($author); $content_source = $this->getLipsumContentSource(); $template = id(new PholioTransaction()) ->setContentSource($content_source); // Accumulate Transactions $changes = array(); $changes[PholioMockNameTransaction::TRANSACTIONTYPE] = $this->generateTitle(); $changes[PholioMockDescriptionTransaction::TRANSACTIONTYPE] = $this->generateDescription(); $changes[PhabricatorTransactions::TYPE_VIEW_POLICY] = PhabricatorPolicies::POLICY_PUBLIC; $changes[PhabricatorTransactions::TYPE_SUBSCRIBERS] = array('=' => $this->getCCPHIDs()); // Get Files and make Images $file_phids = $this->generateImages(); $files = id(new PhabricatorFileQuery()) ->setViewer($author) ->withPHIDs($file_phids) ->execute(); $mock->setCoverPHID(head($files)->getPHID()); $sequence = 0; $images = array(); foreach ($files as $file) { $image = PholioImage::initializeNewImage() ->setAuthorPHID($author_phid) ->setFilePHID($file->getPHID()) ->setSequence($sequence++) ->attachMock($mock); $images[] = $image; } // Apply Transactions $transactions = array(); foreach ($changes as $type => $value) { $transaction = clone $template; $transaction->setTransactionType($type); $transaction->setNewValue($value); $transactions[] = $transaction; } $mock->openTransaction(); $editor = id(new PholioMockEditor()) ->setContentSource($content_source) ->setContinueOnNoEffect(true) ->setActor($author) ->applyTransactions($mock, $transactions); foreach ($images as $image) { - $image->setMockID($mock->getID()); + $image->setMockPHID($mock->getPHID()); $image->save(); } $mock->saveTransaction(); return $mock->save(); } public function generateTitle() { return id(new PhutilLipsumContextFreeGrammar()) ->generate(); } public function generateDescription() { return id(new PhutilLipsumContextFreeGrammar()) ->generateSeveral(rand(30, 40)); } public function getCCPHIDs() { $ccs = array(); for ($i = 0; $i < rand(1, 4);$i++) { $ccs[] = $this->loadPhabricatorUserPHID(); } return $ccs; } public function generateImages() { $images = newv('PhabricatorFile', array()) ->loadAllWhere('mimeType = %s', 'image/jpeg'); $rand_images = array(); $quantity = rand(2, 10); $quantity = min($quantity, count($images)); if ($quantity) { $random_images = $quantity === 1 ? array(array_rand($images, $quantity)) : array_rand($images, $quantity); foreach ($random_images as $random) { $rand_images[] = $images[$random]->getPHID(); } } // This means you don't have any JPEGs yet. We'll just use a built-in image. if (empty($rand_images)) { $default = PhabricatorFile::loadBuiltin( PhabricatorUser::getOmnipotentUser(), 'profile.png'); $rand_images[] = $default->getPHID(); } return $rand_images; } } diff --git a/src/applications/pholio/phid/PholioImagePHIDType.php b/src/applications/pholio/phid/PholioImagePHIDType.php index b28dbd64d5..d7a9984fd5 100644 --- a/src/applications/pholio/phid/PholioImagePHIDType.php +++ b/src/applications/pholio/phid/PholioImagePHIDType.php @@ -1,45 +1,41 @@ withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $image = $objects[$phid]; - $id = $image->getID(); - $mock_id = $image->getMockID(); - $name = $image->getName(); - - $handle->setURI("/M{$mock_id}/{$id}/"); - $handle->setName($name); - $handle->setFullName($name); + $handle + ->setName($image->getName()) + ->setURI($image->getURI()); } } } diff --git a/src/applications/pholio/query/PholioImageQuery.php b/src/applications/pholio/query/PholioImageQuery.php index 5f541f1768..0fa7bfb1a0 100644 --- a/src/applications/pholio/query/PholioImageQuery.php +++ b/src/applications/pholio/query/PholioImageQuery.php @@ -1,157 +1,146 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } - public function withMockIDs(array $mock_ids) { - $this->mockIDs = $mock_ids; - return $this; - } - - public function withObsolete($obsolete) { - $this->obsolete = $obsolete; + public function withMockPHIDs(array $mock_phids) { + $this->mockPHIDs = $mock_phids; return $this; } public function needInlineComments($need_inline_comments) { $this->needInlineComments = $need_inline_comments; return $this; } public function setMockCache($mock_cache) { $this->mockCache = $mock_cache; return $this; } public function getMockCache() { return $this->mockCache; } public function newResultObject() { return new PholioImage(); } protected function loadPage() { return $this->loadStandardPage($this->newResultObject()); } protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where = parent::buildWhereClauseParts($conn); if ($this->ids !== null) { $where[] = qsprintf( $conn, 'id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, 'phid IN (%Ls)', $this->phids); } - if ($this->mockIDs !== null) { + if ($this->mockPHIDs !== null) { $where[] = qsprintf( $conn, - 'mockID IN (%Ld)', - $this->mockIDs); - } - - if ($this->obsolete !== null) { - $where[] = qsprintf( - $conn, - 'isObsolete = %d', - $this->obsolete); + 'mockPHID IN (%Ls)', + $this->mockPHIDs); } return $where; } protected function willFilterPage(array $images) { assert_instances_of($images, 'PholioImage'); if ($this->getMockCache()) { $mocks = $this->getMockCache(); } else { - $mock_ids = mpull($images, 'getMockID'); + $mock_phids = mpull($images, 'getMockPHID'); + // DO NOT set needImages to true; recursion results! $mocks = id(new PholioMockQuery()) ->setViewer($this->getViewer()) - ->withIDs($mock_ids) + ->withPHIDs($mock_phids) ->execute(); - $mocks = mpull($mocks, null, 'getID'); + $mocks = mpull($mocks, null, 'getPHID'); } + foreach ($images as $index => $image) { - $mock = idx($mocks, $image->getMockID()); + $mock = idx($mocks, $image->getMockPHID()); if ($mock) { $image->attachMock($mock); } else { // mock is missing or we can't see it unset($images[$index]); } } return $images; } protected function didFilterPage(array $images) { assert_instances_of($images, 'PholioImage'); $file_phids = mpull($images, 'getFilePHID'); $all_files = id(new PhabricatorFileQuery()) ->setParentQuery($this) ->setViewer($this->getViewer()) ->withPHIDs($file_phids) ->execute(); $all_files = mpull($all_files, null, 'getPHID'); if ($this->needInlineComments) { // Only load inline comments the viewer has permission to see. $all_inline_comments = id(new PholioTransactionComment())->loadAllWhere( 'imageID IN (%Ld) AND (transactionPHID IS NOT NULL OR authorPHID = %s)', mpull($images, 'getID'), $this->getViewer()->getPHID()); $all_inline_comments = mgroup($all_inline_comments, 'getImageID'); } foreach ($images as $image) { $file = idx($all_files, $image->getFilePHID()); if (!$file) { $file = PhabricatorFile::loadBuiltin($this->getViewer(), 'missing.png'); } $image->attachFile($file); if ($this->needInlineComments) { $inlines = idx($all_inline_comments, $image->getID(), array()); $image->attachInlineComments($inlines); } } return $images; } public function getQueryApplicationClass() { return 'PhabricatorPholioApplication'; } } diff --git a/src/applications/pholio/query/PholioMockQuery.php b/src/applications/pholio/query/PholioMockQuery.php index 5f1711def6..9e3154ec5c 100644 --- a/src/applications/pholio/query/PholioMockQuery.php +++ b/src/applications/pholio/query/PholioMockQuery.php @@ -1,176 +1,176 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withAuthorPHIDs(array $author_phids) { $this->authorPHIDs = $author_phids; return $this; } public function withStatuses(array $statuses) { $this->statuses = $statuses; return $this; } public function needCoverFiles($need_cover_files) { $this->needCoverFiles = $need_cover_files; return $this; } public function needImages($need_images) { $this->needImages = $need_images; return $this; } public function needInlineComments($need_inline_comments) { $this->needInlineComments = $need_inline_comments; return $this; } public function needTokenCounts($need) { $this->needTokenCounts = $need; return $this; } public function newResultObject() { return new PholioMock(); } protected function loadPage() { $mocks = $this->loadStandardPage(new PholioMock()); if ($mocks && $this->needImages) { self::loadImages($this->getViewer(), $mocks, $this->needInlineComments); } if ($mocks && $this->needCoverFiles) { $this->loadCoverFiles($mocks); } if ($mocks && $this->needTokenCounts) { $this->loadTokenCounts($mocks); } return $mocks; } protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where = parent::buildWhereClauseParts($conn); if ($this->ids !== null) { $where[] = qsprintf( $conn, 'mock.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, 'mock.phid IN (%Ls)', $this->phids); } if ($this->authorPHIDs !== null) { $where[] = qsprintf( $conn, 'mock.authorPHID in (%Ls)', $this->authorPHIDs); } if ($this->statuses !== null) { $where[] = qsprintf( $conn, 'mock.status IN (%Ls)', $this->statuses); } return $where; } public static function loadImages( PhabricatorUser $viewer, array $mocks, $need_inline_comments) { assert_instances_of($mocks, 'PholioMock'); - $mock_map = mpull($mocks, null, 'getID'); + $mock_map = mpull($mocks, null, 'getPHID'); $all_images = id(new PholioImageQuery()) ->setViewer($viewer) ->setMockCache($mock_map) - ->withMockIDs(array_keys($mock_map)) + ->withMockPHIDs(array_keys($mock_map)) ->needInlineComments($need_inline_comments) ->execute(); - $image_groups = mgroup($all_images, 'getMockID'); + $image_groups = mgroup($all_images, 'getMockPHID'); foreach ($mocks as $mock) { - $mock_images = idx($image_groups, $mock->getID(), array()); + $mock_images = idx($image_groups, $mock->getPHID(), array()); $mock->attachAllImages($mock_images); $active_images = mfilter($mock_images, 'getIsObsolete', true); $mock->attachImages(msort($active_images, 'getSequence')); } } private function loadCoverFiles(array $mocks) { assert_instances_of($mocks, 'PholioMock'); $cover_file_phids = mpull($mocks, 'getCoverPHID'); $cover_files = id(new PhabricatorFileQuery()) ->setViewer($this->getViewer()) ->withPHIDs($cover_file_phids) ->execute(); $cover_files = mpull($cover_files, null, 'getPHID'); foreach ($mocks as $mock) { $file = idx($cover_files, $mock->getCoverPHID()); if (!$file) { $file = PhabricatorFile::loadBuiltin($this->getViewer(), 'missing.png'); } $mock->attachCoverFile($file); } } private function loadTokenCounts(array $mocks) { assert_instances_of($mocks, 'PholioMock'); $phids = mpull($mocks, 'getPHID'); $counts = id(new PhabricatorTokenCountQuery()) ->withObjectPHIDs($phids) ->execute(); foreach ($mocks as $mock) { $mock->attachTokenCount(idx($counts, $mock->getPHID(), 0)); } } public function getQueryApplicationClass() { return 'PhabricatorPholioApplication'; } protected function getPrimaryTableAlias() { return 'mock'; } } diff --git a/src/applications/pholio/storage/PholioImage.php b/src/applications/pholio/storage/PholioImage.php index 0f800bbb0f..1440773722 100644 --- a/src/applications/pholio/storage/PholioImage.php +++ b/src/applications/pholio/storage/PholioImage.php @@ -1,128 +1,143 @@ setName('') ->setDescription('') ->setIsObsolete(0); } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_COLUMN_SCHEMA => array( - 'mockID' => 'id?', + 'mockPHID' => 'phid?', 'name' => 'text128', 'description' => 'text', 'sequence' => 'uint32', 'isObsolete' => 'bool', 'replacesImagePHID' => 'phid?', ), self::CONFIG_KEY_SCHEMA => array( - 'key_phid' => null, - 'keyPHID' => array( - 'columns' => array('phid'), - 'unique' => true, - ), - 'mockID' => array( - 'columns' => array('mockID', 'isObsolete', 'sequence'), - ), + // TODO: There should be a key starting with "mockPHID" here at a + // minimum, but it's not entirely clear what other columns we should + // have as part of the key. ), ) + parent::getConfiguration(); } public function getPHIDType() { return PholioImagePHIDType::TYPECONST; } public function attachFile(PhabricatorFile $file) { $this->file = $file; return $this; } public function getFile() { return $this->assertAttached($this->file); } public function attachMock(PholioMock $mock) { $this->mock = $mock; return $this; } public function getMock() { return $this->assertAttached($this->mock); } + public function hasMock() { + return (bool)$this->getMockPHID(); + } + public function attachInlineComments(array $inline_comments) { assert_instances_of($inline_comments, 'PholioTransactionComment'); $this->inlineComments = $inline_comments; return $this; } public function getInlineComments() { $this->assertAttached($this->inlineComments); return $this->inlineComments; } + public function getURI() { + if ($this->hasMock()) { + $mock = $this->getMock(); + + $mock_uri = $mock->getURI(); + $image_id = $this->getID(); + + return "{$mock_uri}/{$image_id}/"; + } + + // For now, standalone images have no URI. We could provide one at some + // point, although it's not clear that there's any motivation to do so. + + return null; + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { // If the image is attached to a mock, we use an extended policy to match // the mock's permissions. - if ($this->getMockID()) { + if ($this->hasMock()) { return PhabricatorPolicies::getMostOpenPolicy(); } // If the image is not attached to a mock, only the author can see it. return $this->getAuthorPHID(); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return false; } /* -( PhabricatorExtendedPolicyInterface )--------------------------------- */ public function getExtendedPolicy($capability, PhabricatorUser $viewer) { - if ($this->getMockID()) { + if ($this->hasMock()) { return array( array( $this->getMock(), $capability, ), ); } return array(); } } diff --git a/src/applications/pholio/storage/PholioMock.php b/src/applications/pholio/storage/PholioMock.php index cf32a34131..62285d0a59 100644 --- a/src/applications/pholio/storage/PholioMock.php +++ b/src/applications/pholio/storage/PholioMock.php @@ -1,296 +1,296 @@ setViewer($actor) ->withClasses(array('PhabricatorPholioApplication')) ->executeOne(); $view_policy = $app->getPolicy(PholioDefaultViewCapability::CAPABILITY); $edit_policy = $app->getPolicy(PholioDefaultEditCapability::CAPABILITY); return id(new PholioMock()) ->setAuthorPHID($actor->getPHID()) ->attachImages(array()) ->setStatus(self::STATUS_OPEN) ->setViewPolicy($view_policy) ->setEditPolicy($edit_policy) ->setSpacePHID($actor->getDefaultSpacePHID()); } public function getMonogram() { return 'M'.$this->getID(); } public function getURI() { return '/'.$this->getMonogram(); } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_COLUMN_SCHEMA => array( 'name' => 'text128', 'description' => 'text', 'mailKey' => 'bytes20', 'status' => 'text12', ), self::CONFIG_KEY_SCHEMA => array( 'key_phid' => null, 'phid' => array( 'columns' => array('phid'), 'unique' => true, ), 'authorPHID' => array( 'columns' => array('authorPHID'), ), ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID('MOCK'); } public function save() { if (!$this->getMailKey()) { $this->setMailKey(Filesystem::readRandomCharacters(20)); } return parent::save(); } /** * These should be the images currently associated with the Mock. */ public function attachImages(array $images) { assert_instances_of($images, 'PholioImage'); $this->images = $images; return $this; } public function getImages() { $this->assertAttached($this->images); return $this->images; } /** * These should be *all* images associated with the Mock. This includes * images which have been removed and / or replaced from the Mock. */ public function attachAllImages(array $images) { assert_instances_of($images, 'PholioImage'); $this->allImages = $images; return $this; } public function getAllImages() { $this->assertAttached($this->images); return $this->allImages; } public function attachCoverFile(PhabricatorFile $file) { $this->coverFile = $file; return $this; } public function getCoverFile() { $this->assertAttached($this->coverFile); return $this->coverFile; } public function getTokenCount() { $this->assertAttached($this->tokenCount); return $this->tokenCount; } public function attachTokenCount($count) { $this->tokenCount = $count; return $this; } public function getImageHistorySet($image_id) { $images = $this->getAllImages(); $images = mpull($images, null, 'getID'); $selected_image = $images[$image_id]; $replace_map = mpull($images, null, 'getReplacesImagePHID'); $phid_map = mpull($images, null, 'getPHID'); // find the earliest image $image = $selected_image; while (isset($phid_map[$image->getReplacesImagePHID()])) { $image = $phid_map[$image->getReplacesImagePHID()]; } // now build history moving forward $history = array($image->getID() => $image); while (isset($replace_map[$image->getPHID()])) { $image = $replace_map[$image->getPHID()]; $history[$image->getID()] = $image; } return $history; } public function getStatuses() { $options = array(); $options[self::STATUS_OPEN] = pht('Open'); $options[self::STATUS_CLOSED] = pht('Closed'); return $options; } public function isClosed() { return ($this->getStatus() == 'closed'); } /* -( PhabricatorSubscribableInterface Implementation )-------------------- */ public function isAutomaticallySubscribed($phid) { return ($this->authorPHID == $phid); } /* -( PhabricatorPolicyInterface Implementation )-------------------------- */ 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 ($viewer->getPHID() == $this->getAuthorPHID()); } public function describeAutomaticCapability($capability) { return pht("A mock's owner can always view and edit it."); } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new PholioMockEditor(); } public function getApplicationTransactionObject() { return $this; } public function getApplicationTransactionTemplate() { return new PholioTransaction(); } public function willRenderTimeline( PhabricatorApplicationTransactionView $timeline, AphrontRequest $request) { PholioMockQuery::loadImages( $request->getUser(), array($this), $need_inline_comments = true); $timeline->setMock($this); return $timeline; } /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ public function getUsersToNotifyOfTokenGiven() { return array( $this->getAuthorPHID(), ); } /* -( PhabricatorDestructibleInterface )----------------------------------- */ public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { $this->openTransaction(); $images = id(new PholioImageQuery()) ->setViewer($engine->getViewer()) - ->withMockIDs(array($this->getID())) + ->withMockIDs(array($this->getPHID())) ->execute(); foreach ($images as $image) { $image->delete(); } $this->delete(); $this->saveTransaction(); } /* -( PhabricatorSpacesInterface )----------------------------------------- */ public function getSpacePHID() { return $this->spacePHID; } /* -( PhabricatorFulltextInterface )--------------------------------------- */ public function newFulltextEngine() { return new PholioMockFulltextEngine(); } /* -( PhabricatorFerretInterface )----------------------------------------- */ public function newFerretEngine() { return new PholioMockFerretEngine(); } }