diff --git a/resources/sql/autopatches/20160206.cover.1.sql b/resources/sql/autopatches/20160206.cover.1.sql new file mode 100644 index 0000000000..1e8b473a5e --- /dev/null +++ b/resources/sql/autopatches/20160206.cover.1.sql @@ -0,0 +1,5 @@ +ALTER TABLE {$NAMESPACE}_maniphest.maniphest_task + ADD properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}; + +UPDATE {$NAMESPACE}_maniphest.maniphest_task + SET properties = '{}' WHERE properties = ''; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 763b41e99b..9e65dda01a 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1815,6 +1815,7 @@ phutil_register_library_map(array( 'PhabricatorBinariesSetupCheck' => 'applications/config/check/PhabricatorBinariesSetupCheck.php', 'PhabricatorBitbucketAuthProvider' => 'applications/auth/provider/PhabricatorBitbucketAuthProvider.php', 'PhabricatorBoardLayoutEngine' => 'applications/project/engine/PhabricatorBoardLayoutEngine.php', + 'PhabricatorBoardRenderingEngine' => 'applications/project/engine/PhabricatorBoardRenderingEngine.php', 'PhabricatorBot' => 'infrastructure/daemon/bot/PhabricatorBot.php', 'PhabricatorBotChannel' => 'infrastructure/daemon/bot/target/PhabricatorBotChannel.php', 'PhabricatorBotDebugLogHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotDebugLogHandler.php', @@ -6043,6 +6044,7 @@ phutil_register_library_map(array( 'PhabricatorBinariesSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorBitbucketAuthProvider' => 'PhabricatorOAuth1AuthProvider', 'PhabricatorBoardLayoutEngine' => 'Phobject', + 'PhabricatorBoardRenderingEngine' => 'Phobject', 'PhabricatorBot' => 'PhabricatorDaemon', 'PhabricatorBotChannel' => 'PhabricatorBotTarget', 'PhabricatorBotDebugLogHandler' => 'PhabricatorBotHandler', diff --git a/src/applications/files/transform/PhabricatorFileThumbnailTransform.php b/src/applications/files/transform/PhabricatorFileThumbnailTransform.php index 8d55109eda..2c1f1149f3 100644 --- a/src/applications/files/transform/PhabricatorFileThumbnailTransform.php +++ b/src/applications/files/transform/PhabricatorFileThumbnailTransform.php @@ -7,6 +7,7 @@ final class PhabricatorFileThumbnailTransform const TRANSFORM_PINBOARD = 'pinboard'; const TRANSFORM_THUMBGRID = 'thumbgrid'; const TRANSFORM_PREVIEW = 'preview'; + const TRANSFORM_WORKCARD = 'workcard'; private $name; private $key; @@ -73,6 +74,10 @@ final class PhabricatorFileThumbnailTransform ->setName(pht('Preview (220px)')) ->setKey(self::TRANSFORM_PREVIEW) ->setDimensions(220, null), + id(new self()) + ->setName(pht('Workcard (526px)')) + ->setKey(self::TRANSFORM_WORKCARD) + ->setDimensions(526, null), ); } diff --git a/src/applications/maniphest/editor/ManiphestEditEngine.php b/src/applications/maniphest/editor/ManiphestEditEngine.php index 3f88a9abe3..efc337d729 100644 --- a/src/applications/maniphest/editor/ManiphestEditEngine.php +++ b/src/applications/maniphest/editor/ManiphestEditEngine.php @@ -334,39 +334,27 @@ final class ManiphestEditEngine 'sortMap' => $sort_map, ); - // TODO: This should just use HandlePool once we get through the EditEngine - // transition. - $owner = null; - if ($task->getOwnerPHID()) { - $owner = id(new PhabricatorHandleQuery()) - ->setViewer($viewer) - ->withPHIDs(array($task->getOwnerPHID())) - ->executeOne(); - } - - $handle_phids = $task->getProjectPHIDs(); - $handle_phids = array_fuse($handle_phids); - $handle_phids = array_diff_key($handle_phids, $board_phids); - - $project_handles = $viewer->loadHandles($handle_phids); - $project_handles = iterator_to_array($project_handles); - - $tasks = id(new ProjectBoardTaskCard()) + $rendering_engine = id(new PhabricatorBoardRenderingEngine()) ->setViewer($viewer) - ->setTask($task) - ->setOwner($owner) - ->setProjectHandles($project_handles) - ->setCanEdit(true) - ->getItem(); + ->setObjects(array($task)) + ->setExcludedProjectPHIDs($board_phids); - $tasks->addClass('phui-workcard'); + $card = $rendering_engine->renderCard($task->getPHID()); + + $item = $card->getItem(); + $item->addClass('phui-workcard'); $payload = array( - 'tasks' => $tasks, + 'tasks' => $item, 'data' => $data, ); - return id(new AphrontAjaxResponse())->setContent($payload); + return id(new AphrontAjaxResponse()) + ->setContent( + array( + 'tasks' => $item, + 'data' => $data, + )); } diff --git a/src/applications/maniphest/storage/ManiphestTask.php b/src/applications/maniphest/storage/ManiphestTask.php index cc9a5bd80a..5b6c4129dd 100644 --- a/src/applications/maniphest/storage/ManiphestTask.php +++ b/src/applications/maniphest/storage/ManiphestTask.php @@ -38,6 +38,7 @@ final class ManiphestTask extends ManiphestDAO protected $ownerOrdering; protected $spacePHID; + protected $properties = array(); private $subscriberPHIDs = self::ATTACHABLE; private $groupByProjectPHID = self::ATTACHABLE; @@ -74,6 +75,7 @@ final class ManiphestTask extends ManiphestDAO 'ccPHIDs' => self::SERIALIZATION_JSON, 'attached' => self::SERIALIZATION_JSON, 'projectPHIDs' => self::SERIALIZATION_JSON, + 'properties' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( 'ownerPHID' => 'phid?', @@ -215,6 +217,19 @@ final class ManiphestTask extends ManiphestDAO ); } + public function setProperty($key, $value) { + $this->properties[$key] = $value; + return $this; + } + + public function getProperty($key, $default = null) { + return idx($this->properties, $key, $default); + } + + public function getCoverImageThumbnailPHID() { + return idx($this->properties, 'cover.thumbnailPHID'); + } + /* -( PhabricatorSubscribableInterface )----------------------------------- */ diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index d0580a95ad..1f00a8a7a6 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -7,7 +7,6 @@ final class PhabricatorProjectBoardViewController private $id; private $slug; - private $handles; private $queryKey; private $filter; private $sortKey; @@ -226,22 +225,9 @@ final class PhabricatorProjectBoardViewController 'project-boards', $behavior_config); - $this->handles = ManiphestTaskListView::loadTaskHandles($viewer, $tasks); - - $all_project_phids = array(); - foreach ($tasks as $task) { - foreach ($task->getProjectPHIDs() as $project_phid) { - $all_project_phids[$project_phid] = $project_phid; - } - } - - foreach ($select_phids as $phid) { - unset($all_project_phids[$phid]); - } - - $all_handles = $viewer->loadHandles($all_project_phids); - $all_handles = iterator_to_array($all_handles); - + $visible_columns = array(); + $column_phids = array(); + $visible_phids = array(); foreach ($columns as $column) { if (!$this->showHidden) { if ($column->isHidden()) { @@ -268,6 +254,25 @@ final class PhabricatorProjectBoardViewController $column_tasks = array_select_keys($column_tasks, array_keys($tasks)); } + $column_phid = $column->getPHID(); + + $visible_columns[$column_phid] = $column; + $column_phids[$column_phid] = $column_tasks; + + foreach ($column_tasks as $phid => $task) { + $visible_phids[$phid] = $phid; + } + } + + $rendering_engine = id(new PhabricatorBoardRenderingEngine()) + ->setViewer($viewer) + ->setObjects(array_select_keys($tasks, $visible_phids)) + ->setEditMap($task_can_edit_map) + ->setExcludedProjectPHIDs($select_phids); + + foreach ($visible_columns as $column_phid => $column) { + $column_tasks = $column_phids[$column_phid]; + $panel = id(new PHUIWorkpanelView()) ->setHeader($column->getDisplayName()) ->setSubHeader($column->getDisplayType()) @@ -317,22 +322,10 @@ final class PhabricatorProjectBoardViewController )); foreach ($column_tasks as $task) { - $owner = null; - if ($task->getOwnerPHID()) { - $owner = $this->handles[$task->getOwnerPHID()]; - } - $can_edit = idx($task_can_edit_map, $task->getPHID(), false); - - $handles = array_select_keys($all_handles, $task->getProjectPHIDs()); - - $cards->addItem(id(new ProjectBoardTaskCard()) - ->setViewer($viewer) - ->setProjectHandles($handles) - ->setTask($task) - ->setOwner($owner) - ->setCanEdit($can_edit) - ->getItem()); + $card = $rendering_engine->renderCard($task->getPHID()); + $cards->addItem($card->getItem()); } + $panel->setCards($cards); $board->addPanel($panel); } diff --git a/src/applications/project/controller/PhabricatorProjectMoveController.php b/src/applications/project/controller/PhabricatorProjectMoveController.php index 7cbbf3d0ae..8cc8a355af 100644 --- a/src/applications/project/controller/PhabricatorProjectMoveController.php +++ b/src/applications/project/controller/PhabricatorProjectMoveController.php @@ -175,24 +175,11 @@ final class PhabricatorProjectMoveController $editor->applyTransactions($object, $xactions); - $owner = null; - if ($object->getOwnerPHID()) { - $owner = id(new PhabricatorHandleQuery()) - ->setViewer($viewer) - ->withPHIDs(array($object->getOwnerPHID())) - ->executeOne(); - } - // Reload the object so it reflects edits which have been applied. $object = id(new ManiphestTaskQuery()) ->setViewer($viewer) ->withPHIDs(array($object_phid)) ->needProjectPHIDs(true) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) ->executeOne(); $except_phids = array($board_phid); @@ -206,25 +193,21 @@ final class PhabricatorProjectMoveController } } - $except_phids = array_fuse($except_phids); - $handle_phids = array_fuse($object->getProjectPHIDs()); - $handle_phids = array_diff_key($handle_phids, $except_phids); - - $project_handles = $viewer->loadHandles($handle_phids); - $project_handles = iterator_to_array($project_handles); - - $card = id(new ProjectBoardTaskCard()) + $rendering_engine = id(new PhabricatorBoardRenderingEngine()) ->setViewer($viewer) - ->setTask($object) - ->setOwner($owner) - ->setCanEdit(true) - ->setProjectHandles($project_handles) - ->getItem(); + ->setObjects(array($object)) + ->setExcludedProjectPHIDs($except_phids); - $card->addClass('phui-workcard'); + $card = $rendering_engine->renderCard($object->getPHID()); - return id(new AphrontAjaxResponse())->setContent( - array('task' => $card)); + $item = $card->getItem(); + $item->addClass('phui-workcard'); + + return id(new AphrontAjaxResponse()) + ->setContent( + array( + 'task' => $item, + )); } } diff --git a/src/applications/project/engine/PhabricatorBoardRenderingEngine.php b/src/applications/project/engine/PhabricatorBoardRenderingEngine.php new file mode 100644 index 0000000000..ca8b0633da --- /dev/null +++ b/src/applications/project/engine/PhabricatorBoardRenderingEngine.php @@ -0,0 +1,144 @@ +viewer = $viewer; + return $this; + } + + public function getViewer() { + return $this->viewer; + } + + public function setObjects(array $objects) { + $this->objects = mpull($objects, null, 'getPHID'); + return $this; + } + + public function getObjects() { + return $this->objects; + } + + public function setExcludedProjectPHIDs(array $phids) { + $this->excludedProjectPHIDs = $phids; + return $this; + } + + public function getExcludedProjectPHIDs() { + return $this->excludedProjectPHIDs; + } + + public function setEditMap(array $edit_map) { + $this->editMap = $edit_map; + return $this; + } + + public function getEditMap() { + return $this->editMap; + } + + public function renderCard($phid) { + $this->willRender(); + + $viewer = $this->getViewer(); + $object = idx($this->getObjects(), $phid); + + $card = id(new ProjectBoardTaskCard()) + ->setViewer($viewer) + ->setTask($object) + ->setCanEdit($this->getCanEdit($phid)); + + $owner_phid = $object->getOwnerPHID(); + if ($owner_phid) { + $owner_handle = $this->handles[$owner_phid]; + $card->setOwner($owner_handle); + } + + $project_phids = $object->getProjectPHIDs(); + $project_handles = array_select_keys($this->handles, $project_phids); + if ($project_handles) { + $card->setProjectHandles($project_handles); + } + + $cover_phid = $object->getCoverImageThumbnailPHID(); + if ($cover_phid) { + $cover_file = idx($this->coverFiles, $cover_phid); + if ($cover_file) { + $card->setCoverImageFile($cover_file); + } + } + + return $card; + } + + private function willRender() { + if ($this->loaded) { + return; + } + + $phids = array(); + foreach ($this->objects as $object) { + $owner_phid = $object->getOwnerPHID(); + if ($owner_phid) { + $phids[$owner_phid] = $owner_phid; + } + + foreach ($object->getProjectPHIDs() as $phid) { + $phids[$phid] = $phid; + } + } + + if ($this->excludedProjectPHIDs) { + foreach ($this->excludedProjectPHIDs as $excluded_phid) { + unset($phids[$excluded_phid]); + } + } + + $viewer = $this->getViewer(); + + $handles = $viewer->loadHandles($phids); + $handles = iterator_to_array($handles); + $this->handles = $handles; + + $cover_phids = array(); + foreach ($this->objects as $object) { + $cover_phid = $object->getCoverImageThumbnailPHID(); + if ($cover_phid) { + $cover_phids[$cover_phid] = $cover_phid; + } + } + + if ($cover_phids) { + $cover_files = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs($cover_phids) + ->execute(); + $cover_files = mpull($cover_files, null, 'getPHID'); + } else { + $cover_files = array(); + } + + $this->coverFiles = $cover_files; + + $this->loaded = true; + } + + private function getCanEdit($phid) { + if ($this->editMap === null) { + return true; + } + + return idx($this->editMap, $phid); + } + +} diff --git a/src/applications/project/view/ProjectBoardTaskCard.php b/src/applications/project/view/ProjectBoardTaskCard.php index 2c69e0f37b..065acc6c13 100644 --- a/src/applications/project/view/ProjectBoardTaskCard.php +++ b/src/applications/project/view/ProjectBoardTaskCard.php @@ -7,6 +7,7 @@ final class ProjectBoardTaskCard extends Phobject { private $task; private $owner; private $canEdit; + private $coverImageFile; public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; @@ -25,6 +26,15 @@ final class ProjectBoardTaskCard extends Phobject { return $this->projectHandles; } + public function setCoverImageFile(PhabricatorFile $cover_image_file) { + $this->coverImageFile = $cover_image_file; + return $this; + } + + public function getCoverImageFile() { + return $this->coverImageFile; + } + public function setTask(ManiphestTask $task) { $this->task = $task; return $this; @@ -84,6 +94,11 @@ final class ProjectBoardTaskCard extends Phobject { $card->addHandleIcon($owner, $owner->getName()); } + $cover_file = $this->getCoverImageFile(); + if ($cover_file) { + $card->setCoverImage($cover_file->getBestURI()); + } + if ($task->isClosed()) { $icon = ManiphestTaskStatus::getStatusIcon($task->getStatus()); $icon = id(new PHUIIconView())