diff --git a/src/applications/pholio/engine/PholioMockTimelineEngine.php b/src/applications/pholio/engine/PholioMockTimelineEngine.php index 80b64e73b7..8543649e68 100644 --- a/src/applications/pholio/engine/PholioMockTimelineEngine.php +++ b/src/applications/pholio/engine/PholioMockTimelineEngine.php @@ -1,19 +1,22 @@ getViewer(); $object = $this->getObject(); - PholioMockQuery::loadImages( - $viewer, - array($object), - $need_inline_comments = true); + $images = id(new PholioImageQuery()) + ->setViewer($viewer) + ->withMocks(array($object)) + ->needInlineComments(true) + ->execute(); + + $object->attachImages($images); return id(new PholioTransactionView()) ->setMock($object); } } diff --git a/src/applications/pholio/query/PholioImageQuery.php b/src/applications/pholio/query/PholioImageQuery.php index 0fa7bfb1a0..0d64540f91 100644 --- a/src/applications/pholio/query/PholioImageQuery.php +++ b/src/applications/pholio/query/PholioImageQuery.php @@ -1,146 +1,162 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } + public function withMocks(array $mocks) { + assert_instances_of($mocks, 'PholioMock'); + + $mocks = mpull($mocks, null, 'getPHID'); + $this->mocks = $mocks; + $this->mockPHIDs = array_keys($mocks); + + return $this; + } + 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->mockPHIDs !== null) { $where[] = qsprintf( $conn, '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_phids = mpull($images, 'getMockPHID'); + $mock_phids = array(); + foreach ($images as $image) { + if (!$image->hasMock()) { + continue; + } + + $mock_phids[] = $image->getMockPHID(); + } + + if ($mock_phids) { + if ($this->mocks) { + $mocks = $this->mocks; + } else { + $mocks = id(new PholioMockQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs($mock_phids) + ->execute(); + } - // DO NOT set needImages to true; recursion results! - $mocks = id(new PholioMockQuery()) - ->setViewer($this->getViewer()) - ->withPHIDs($mock_phids) - ->execute(); $mocks = mpull($mocks, null, 'getPHID'); - } - foreach ($images as $index => $image) { - $mock = idx($mocks, $image->getMockPHID()); - if ($mock) { + foreach ($images as $key => $image) { + if (!$image->hasMock()) { + continue; + } + + $mock = idx($mocks, $image->getMockPHID()); + if (!$mock) { + unset($images[$key]); + $this->didRejectResult($image); + continue; + } + $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 465f23d34b..4820ffd8eb 100644 --- a/src/applications/pholio/query/PholioMockQuery.php +++ b/src/applications/pholio/query/PholioMockQuery.php @@ -1,174 +1,162 @@ 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($this->newResultObject()); - - 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); + if ($this->needInlineComments && !$this->needImages) { + throw new Exception( + pht( + 'You can not query for inline comments without also querying for '. + 'images.')); } - return $mocks; + return $this->loadStandardPage(new PholioMock()); } 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, 'getPHID'); - $all_images = id(new PholioImageQuery()) - ->setViewer($viewer) - ->setMockCache($mock_map) - ->withMockPHIDs(array_keys($mock_map)) - ->needInlineComments($need_inline_comments) - ->execute(); - - $image_groups = mgroup($all_images, 'getMockPHID'); - - foreach ($mocks as $mock) { - $mock_images = idx($image_groups, $mock->getPHID(), array()); - $mock->attachImages($mock_images); - } - } + protected function didFilterPage(array $mocks) { + $viewer = $this->getViewer(); - 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(); + if ($this->needImages) { + $images = id(new PholioImageQuery()) + ->setViewer($viewer) + ->withMocks($mocks) + ->needInlineComments($this->needInlineComments) + ->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'); + $image_groups = mgroup($images, 'getMockPHID'); + foreach ($mocks as $mock) { + $images = idx($image_groups, $mock->getPHID(), array()); + $mock->attachImages($images); } - $mock->attachCoverFile($file); } - } - private function loadTokenCounts(array $mocks) { - assert_instances_of($mocks, 'PholioMock'); + if ($this->needCoverFiles) { + $cover_files = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(mpull($mocks, 'getCoverPHID')) + ->execute(); + $cover_files = mpull($cover_files, null, 'getPHID'); + + foreach ($mocks as $mock) { + $file = idx($cover_files, $mock->getCoverPHID()); + if (!$file) { + $file = PhabricatorFile::loadBuiltin( + $viewer, + 'missing.png'); + } + $mock->attachCoverFile($file); + } + } - $phids = mpull($mocks, 'getPHID'); - $counts = id(new PhabricatorTokenCountQuery()) - ->withObjectPHIDs($phids) - ->execute(); + if ($this->needTokenCounts) { + $counts = id(new PhabricatorTokenCountQuery()) + ->withObjectPHIDs(mpull($mocks, 'getPHID')) + ->execute(); - foreach ($mocks as $mock) { - $mock->attachTokenCount(idx($counts, $mock->getPHID(), 0)); + foreach ($mocks as $mock) { + $token_count = idx($counts, $mock->getPHID(), 0); + $mock->attachTokenCount($token_count); + } } + + return $mocks; } public function getQueryApplicationClass() { return 'PhabricatorPholioApplication'; } protected function getPrimaryTableAlias() { return 'mock'; } } diff --git a/src/applications/pholio/view/PholioMockImagesView.php b/src/applications/pholio/view/PholioMockImagesView.php index 70f5fe8bb3..99645c4a91 100644 --- a/src/applications/pholio/view/PholioMockImagesView.php +++ b/src/applications/pholio/view/PholioMockImagesView.php @@ -1,223 +1,223 @@ commentFormID = $comment_form_id; return $this; } public function getCommentFormID() { return $this->commentFormID; } public function setRequestURI(PhutilURI $request_uri) { $this->requestURI = $request_uri; return $this; } public function getRequestURI() { return $this->requestURI; } public function setImageID($image_id) { $this->imageID = $image_id; return $this; } public function getImageID() { return $this->imageID; } public function setMock(PholioMock $mock) { $this->mock = $mock; return $this; } public function getMock() { return $this->mock; } public function __construct() { $this->panelID = celerity_generate_unique_node_id(); $this->viewportID = celerity_generate_unique_node_id(); } public function getBehaviorConfig() { if (!$this->getMock()) { throw new PhutilInvalidStateException('setMock'); } if ($this->behaviorConfig === null) { $this->behaviorConfig = $this->calculateBehaviorConfig(); } return $this->behaviorConfig; } private function calculateBehaviorConfig() { $mock = $this->getMock(); // TODO: We could maybe do a better job with tailoring this, which is the // image shown on the review stage. $viewer = $this->getUser(); $default = PhabricatorFile::loadBuiltin($viewer, 'image-100x100.png'); $images = array(); $current_set = 0; foreach ($mock->getImages() as $image) { $file = $image->getFile(); $metadata = $file->getMetadata(); $x = idx($metadata, PhabricatorFile::METADATA_IMAGE_WIDTH); $y = idx($metadata, PhabricatorFile::METADATA_IMAGE_HEIGHT); $is_obs = (bool)$image->getIsObsolete(); if (!$is_obs) { $current_set++; } $description = $image->getDescription(); if (strlen($description)) { $description = new PHUIRemarkupView($viewer, $description); } $history_uri = '/pholio/image/history/'.$image->getID().'/'; $images[] = array( 'id' => $image->getID(), 'fullURI' => $file->getBestURI(), 'stageURI' => ($file->isViewableImage() ? $file->getBestURI() : $default->getBestURI()), 'pageURI' => $this->getImagePageURI($image, $mock), 'downloadURI' => $file->getDownloadURI(), 'historyURI' => $history_uri, 'width' => $x, 'height' => $y, 'title' => $image->getName(), 'descriptionMarkup' => $description, 'isObsolete' => (bool)$image->getIsObsolete(), 'isImage' => $file->isViewableImage(), 'isViewable' => $file->isViewableInBrowser(), ); } - $ids = mpull($mock->getActiveImages(), 'getID'); + $ids = mpull($mock->getActiveImages(), null, 'getID'); if ($this->imageID && isset($ids[$this->imageID])) { $selected_id = $this->imageID; } else { $selected_id = head_key($ids); } $navsequence = array(); foreach ($mock->getActiveImages() as $image) { $navsequence[] = $image->getID(); } $full_icon = array( javelin_tag('span', array('aural' => true), pht('View Raw File')), id(new PHUIIconView())->setIcon('fa-file-image-o'), ); $download_icon = array( javelin_tag('span', array('aural' => true), pht('Download File')), id(new PHUIIconView())->setIcon('fa-download'), ); $login_uri = id(new PhutilURI('/login/')) ->setQueryParam('next', (string)$this->getRequestURI()); $config = array( 'mockID' => $mock->getID(), 'panelID' => $this->panelID, 'viewportID' => $this->viewportID, 'commentFormID' => $this->getCommentFormID(), 'images' => $images, 'selectedID' => $selected_id, 'loggedIn' => $this->getUser()->isLoggedIn(), 'logInLink' => (string)$login_uri, 'navsequence' => $navsequence, 'fullIcon' => hsprintf('%s', $full_icon), 'downloadIcon' => hsprintf('%s', $download_icon), 'currentSetSize' => $current_set, ); return $config; } public function render() { if (!$this->getMock()) { throw new PhutilInvalidStateException('setMock'); } $mock = $this->getMock(); require_celerity_resource('javelin-behavior-pholio-mock-view'); $panel_id = $this->panelID; $viewport_id = $this->viewportID; $config = $this->getBehaviorConfig(); Javelin::initBehavior( 'pholio-mock-view', $this->getBehaviorConfig()); $mock_wrapper = javelin_tag( 'div', array( 'id' => $this->viewportID, 'sigil' => 'mock-viewport', 'class' => 'pholio-mock-image-viewport', ), ''); $image_header = javelin_tag( 'div', array( 'id' => 'mock-image-header', 'class' => 'pholio-mock-image-header', ), ''); $mock_wrapper = javelin_tag( 'div', array( 'id' => $this->panelID, 'sigil' => 'mock-panel touchable', 'class' => 'pholio-mock-image-panel', ), array( $image_header, $mock_wrapper, )); $inline_comments_holder = javelin_tag( 'div', array( 'id' => 'mock-image-description', 'sigil' => 'mock-image-description', 'class' => 'mock-image-description', ), ''); return phutil_tag( 'div', array( 'class' => 'pholio-mock-image-container', 'id' => 'pholio-mock-image-container', ), array($mock_wrapper, $inline_comments_holder)); } private function getImagePageURI(PholioImage $image, PholioMock $mock) { $uri = '/M'.$mock->getID().'/'.$image->getID().'/'; return $uri; } }