Add ability to set a header image per Phame blog
Summary: This is the backend half of uploading an image as a header for Phame Blogs. Allows you to upload image, or delete it. Ref T10901
Test Plan:
Go to Manage Blog, visit Edit Header Image, Upload snarky file. See snarky file on Manage page. Edit Header Image, click delete, save, see file goes away.
{F1690966}
Reviewers: epriestley
Reviewed By: epriestley
Subscribers: Korvin
Maniphest Tasks: T10901
Differential Revision: https://secure.phabricator.com/D16140
			
			
This commit is contained in:
		| @@ -7,7 +7,7 @@ | ||||
|  */ | ||||
| return array( | ||||
|   'names' => array( | ||||
|     'core.pkg.css' => '6913fe66', | ||||
|     'core.pkg.css' => 'c7fc5aec', | ||||
|     'core.pkg.js' => '10275c16', | ||||
|     'darkconsole.pkg.js' => 'e7393ebb', | ||||
|     'differential.pkg.css' => 'b3eea3f5', | ||||
| @@ -155,7 +155,7 @@ return array( | ||||
|     'rsrc/css/phui/phui-spacing.css' => '042804d6', | ||||
|     'rsrc/css/phui/phui-status.css' => 'd5263e49', | ||||
|     'rsrc/css/phui/phui-tag-view.css' => '6bbd83e2', | ||||
|     'rsrc/css/phui/phui-timeline-view.css' => '6e342216', | ||||
|     'rsrc/css/phui/phui-timeline-view.css' => '8ea41b25', | ||||
|     'rsrc/css/phui/phui-two-column-view.css' => '9fb86c85', | ||||
|     'rsrc/css/phui/workboards/phui-workboard-color.css' => 'ac6fe6a7', | ||||
|     'rsrc/css/phui/workboards/phui-workboard.css' => 'e6d89647', | ||||
| @@ -860,7 +860,7 @@ return array( | ||||
|     'phui-status-list-view-css' => 'd5263e49', | ||||
|     'phui-tag-view-css' => '6bbd83e2', | ||||
|     'phui-theme-css' => '027ba77e', | ||||
|     'phui-timeline-view-css' => '6e342216', | ||||
|     'phui-timeline-view-css' => '8ea41b25', | ||||
|     'phui-two-column-view-css' => '9fb86c85', | ||||
|     'phui-workboard-color-css' => 'ac6fe6a7', | ||||
|     'phui-workboard-view-css' => 'e6d89647', | ||||
|   | ||||
| @@ -0,0 +1,2 @@ | ||||
| ALTER TABLE {$NAMESPACE}_phame.phame_blog | ||||
|   ADD headerImagePHID VARBINARY(64); | ||||
| @@ -3777,6 +3777,7 @@ phutil_register_library_map(array( | ||||
|     'PhameBlogEditor' => 'applications/phame/editor/PhameBlogEditor.php', | ||||
|     'PhameBlogFeedController' => 'applications/phame/controller/blog/PhameBlogFeedController.php', | ||||
|     'PhameBlogFulltextEngine' => 'applications/phame/search/PhameBlogFulltextEngine.php', | ||||
|     'PhameBlogHeaderPictureController' => 'applications/phame/controller/blog/PhameBlogHeaderPictureController.php', | ||||
|     'PhameBlogListController' => 'applications/phame/controller/blog/PhameBlogListController.php', | ||||
|     'PhameBlogListView' => 'applications/phame/view/PhameBlogListView.php', | ||||
|     'PhameBlogManageController' => 'applications/phame/controller/blog/PhameBlogManageController.php', | ||||
| @@ -8655,6 +8656,7 @@ phutil_register_library_map(array( | ||||
|     'PhameBlogEditor' => 'PhabricatorApplicationTransactionEditor', | ||||
|     'PhameBlogFeedController' => 'PhameBlogController', | ||||
|     'PhameBlogFulltextEngine' => 'PhabricatorFulltextEngine', | ||||
|     'PhameBlogHeaderPictureController' => 'PhameBlogController', | ||||
|     'PhameBlogListController' => 'PhameBlogController', | ||||
|     'PhameBlogListView' => 'AphrontTagView', | ||||
|     'PhameBlogManageController' => 'PhameBlogController', | ||||
|   | ||||
| @@ -67,6 +67,7 @@ final class PhabricatorPhameApplication extends PhabricatorApplication { | ||||
|           'manage/(?P<id>[^/]+)/' => 'PhameBlogManageController', | ||||
|           'feed/(?P<id>[^/]+)/' => 'PhameBlogFeedController', | ||||
|           'picture/(?P<id>[1-9]\d*)/' => 'PhameBlogProfilePictureController', | ||||
|           'header/(?P<id>[1-9]\d*)/' => 'PhameBlogHeaderPictureController', | ||||
|         ), | ||||
|       ) + $this->getResourceSubroutes(), | ||||
|     ); | ||||
|   | ||||
| @@ -0,0 +1,126 @@ | ||||
| <?php | ||||
|  | ||||
| final class PhameBlogHeaderPictureController | ||||
|   extends PhameBlogController { | ||||
|  | ||||
|   public function handleRequest(AphrontRequest $request) { | ||||
|     $viewer = $request->getViewer(); | ||||
|     $id = $request->getURIData('id'); | ||||
|  | ||||
|     $blog = id(new PhameBlogQuery()) | ||||
|       ->setViewer($viewer) | ||||
|       ->withIDs(array($id)) | ||||
|       ->needHeaderImage(true) | ||||
|       ->requireCapabilities( | ||||
|         array( | ||||
|           PhabricatorPolicyCapability::CAN_VIEW, | ||||
|           PhabricatorPolicyCapability::CAN_EDIT, | ||||
|         )) | ||||
|       ->executeOne(); | ||||
|     if (!$blog) { | ||||
|       return new Aphront404Response(); | ||||
|     } | ||||
|  | ||||
|     $blog_uri = '/phame/blog/manage/'.$id; | ||||
|  | ||||
|     $supported_formats = PhabricatorFile::getTransformableImageFormats(); | ||||
|     $e_file = true; | ||||
|     $errors = array(); | ||||
|     $delete_header = ($request->getInt('delete') == 1); | ||||
|  | ||||
|     if ($request->isFormPost()) { | ||||
|       if ($request->getFileExists('header')) { | ||||
|         $file = PhabricatorFile::newFromPHPUpload( | ||||
|           $_FILES['header'], | ||||
|           array( | ||||
|             'authorPHID' => $viewer->getPHID(), | ||||
|             'canCDN' => true, | ||||
|           )); | ||||
|       } else if (!$delete_header) { | ||||
|         $e_file = pht('Required'); | ||||
|         $errors[] = pht( | ||||
|           'You must choose a file when uploading a new blog header.'); | ||||
|       } | ||||
|  | ||||
|       if (!$errors && !$delete_header) { | ||||
|         if (!$file->isTransformableImage()) { | ||||
|           $e_file = pht('Not Supported'); | ||||
|           $errors[] = pht( | ||||
|             'This server only supports these image formats: %s.', | ||||
|             implode(', ', $supported_formats)); | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       if (!$errors) { | ||||
|         if ($delete_header) { | ||||
|           $blog->setHeaderImagePHID(null); | ||||
|         } else { | ||||
|           $blog->setHeaderImagePHID($file->getPHID()); | ||||
|           $file->attachToObject($blog->getPHID()); | ||||
|         } | ||||
|         $blog->save(); | ||||
|         return id(new AphrontRedirectResponse())->setURI($blog_uri); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     $title = pht('Edit Blog Header'); | ||||
|  | ||||
|     $upload_form = id(new AphrontFormView()) | ||||
|       ->setUser($viewer) | ||||
|       ->setEncType('multipart/form-data') | ||||
|       ->appendChild( | ||||
|         id(new AphrontFormFileControl()) | ||||
|           ->setName('header') | ||||
|           ->setLabel(pht('Upload Header')) | ||||
|           ->setError($e_file) | ||||
|           ->setCaption( | ||||
|             pht('Supported formats: %s', implode(', ', $supported_formats)))) | ||||
|       ->appendChild( | ||||
|         id(new AphrontFormCheckboxControl()) | ||||
|           ->setName('delete') | ||||
|           ->setLabel(pht('Delete Header')) | ||||
|           ->addCheckbox( | ||||
|             'delete', | ||||
|             1, | ||||
|             null, | ||||
|             null)) | ||||
|       ->appendChild( | ||||
|         id(new AphrontFormSubmitControl()) | ||||
|           ->addCancelButton($blog_uri) | ||||
|           ->setValue(pht('Upload Header'))); | ||||
|  | ||||
|     $upload_box = id(new PHUIObjectBoxView()) | ||||
|       ->setHeaderText(pht('Upload New Header')) | ||||
|       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) | ||||
|       ->setForm($upload_form); | ||||
|  | ||||
|     $crumbs = $this->buildApplicationCrumbs(); | ||||
|     $crumbs->addTextCrumb( | ||||
|       pht('Blogs'), | ||||
|       $this->getApplicationURI('blog/')); | ||||
|     $crumbs->addTextCrumb( | ||||
|       $blog->getName(), | ||||
|       $this->getApplicationURI('blog/view/'.$id)); | ||||
|     $crumbs->addTextCrumb(pht('Blog Header')); | ||||
|     $crumbs->setBorder(true); | ||||
|  | ||||
|     $header = id(new PHUIHeaderView()) | ||||
|       ->setHeader(pht('Edit Blog Header')) | ||||
|       ->setHeaderIcon('fa-camera'); | ||||
|  | ||||
|     $view = id(new PHUITwoColumnView()) | ||||
|       ->setHeader($header) | ||||
|       ->setFooter(array( | ||||
|         $upload_box, | ||||
|       )); | ||||
|  | ||||
|     return $this->newPage() | ||||
|       ->setTitle($title) | ||||
|       ->setCrumbs($crumbs) | ||||
|       ->appendChild( | ||||
|         array( | ||||
|           $view, | ||||
|       )); | ||||
|  | ||||
|   } | ||||
| } | ||||
| @@ -14,6 +14,7 @@ final class PhameBlogManageController extends PhameBlogController { | ||||
|       ->setViewer($viewer) | ||||
|       ->withIDs(array($id)) | ||||
|       ->needProfileImage(true) | ||||
|       ->needHeaderImage(true) | ||||
|       ->executeOne(); | ||||
|     if (!$blog) { | ||||
|       return new Aphront404Response(); | ||||
| @@ -40,6 +41,7 @@ final class PhameBlogManageController extends PhameBlogController { | ||||
|  | ||||
|     $curtain = $this->buildCurtain($blog); | ||||
|     $properties = $this->buildPropertyView($blog); | ||||
|     $file = $this->buildFileView($blog); | ||||
|  | ||||
|     $crumbs = $this->buildApplicationCrumbs(); | ||||
|     $crumbs->addTextCrumb( | ||||
| @@ -62,6 +64,7 @@ final class PhameBlogManageController extends PhameBlogController { | ||||
|       ->setHeader($header) | ||||
|       ->setCurtain($curtain) | ||||
|       ->addPropertySection(pht('Details'), $properties) | ||||
|       ->addPropertySection(pht('Header'), $file) | ||||
|       ->setMainColumn( | ||||
|         array( | ||||
|           $timeline, | ||||
| @@ -157,6 +160,14 @@ final class PhameBlogManageController extends PhameBlogController { | ||||
|         ->setDisabled(!$can_edit) | ||||
|         ->setWorkflow(!$can_edit)); | ||||
|  | ||||
|     $curtain->addAction( | ||||
|       id(new PhabricatorActionView()) | ||||
|         ->setIcon('fa-camera') | ||||
|         ->setHref($this->getApplicationURI('blog/header/'.$blog->getID().'/')) | ||||
|         ->setName(pht('Edit Blog Header')) | ||||
|         ->setDisabled(!$can_edit) | ||||
|         ->setWorkflow(!$can_edit)); | ||||
|  | ||||
|     $curtain->addAction( | ||||
|       id(new PhabricatorActionView()) | ||||
|         ->setIcon('fa-picture-o') | ||||
| @@ -188,4 +199,24 @@ final class PhameBlogManageController extends PhameBlogController { | ||||
|     return $curtain; | ||||
|   } | ||||
|  | ||||
|   private function buildFileView( | ||||
|     PhameBlog $blog) { | ||||
|     $viewer = $this->getViewer(); | ||||
|  | ||||
|     $view = id(new PHUIPropertyListView()) | ||||
|       ->setUser($viewer); | ||||
|  | ||||
|     if ($blog->getHeaderImagePHID()) { | ||||
|       $view->addImageContent( | ||||
|         phutil_tag( | ||||
|           'img', | ||||
|           array( | ||||
|             'src'     => $blog->getHeaderImageURI(), | ||||
|             'class'   => 'phabricator-image-macro-hero', | ||||
|           ))); | ||||
|       return $view; | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -3,10 +3,6 @@ | ||||
| final class PhameBlogProfilePictureController | ||||
|   extends PhameBlogController { | ||||
|  | ||||
|   public function shouldRequireAdmin() { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   public function handleRequest(AphrontRequest $request) { | ||||
|     $viewer = $request->getViewer(); | ||||
|     $id = $request->getURIData('id'); | ||||
|   | ||||
| @@ -9,6 +9,7 @@ final class PhameBlogQuery extends PhabricatorCursorPagedPolicyAwareQuery { | ||||
|  | ||||
|   private $needBloggers; | ||||
|   private $needProfileImage; | ||||
|   private $needHeaderImage; | ||||
|  | ||||
|   public function withIDs(array $ids) { | ||||
|     $this->ids = $ids; | ||||
| @@ -35,6 +36,11 @@ final class PhameBlogQuery extends PhabricatorCursorPagedPolicyAwareQuery { | ||||
|     return $this; | ||||
|   } | ||||
|  | ||||
|   public function needHeaderImage($need) { | ||||
|     $this->needHeaderImage = $need; | ||||
|     return $this; | ||||
|   } | ||||
|  | ||||
|   public function newResultObject() { | ||||
|     return new PhameBlog(); | ||||
|   } | ||||
| @@ -107,6 +113,28 @@ final class PhameBlogQuery extends PhabricatorCursorPagedPolicyAwareQuery { | ||||
|         $blog->attachProfileImageFile($file); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     if ($this->needHeaderImage) { | ||||
|       $file_phids = mpull($blogs, 'getHeaderImagePHID'); | ||||
|       $file_phids = array_filter($file_phids); | ||||
|       if ($file_phids) { | ||||
|         $files = id(new PhabricatorFileQuery()) | ||||
|           ->setParentQuery($this) | ||||
|           ->setViewer($this->getViewer()) | ||||
|           ->withPHIDs($file_phids) | ||||
|           ->execute(); | ||||
|         $files = mpull($files, null, 'getPHID'); | ||||
|       } else { | ||||
|         $files = array(); | ||||
|       } | ||||
|  | ||||
|       foreach ($blogs as $blog) { | ||||
|         $file = idx($files, $blog->getHeaderImagePHID()); | ||||
|         if ($file) { | ||||
|           $blog->attachHeaderImageFile($file); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return $blogs; | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -24,8 +24,10 @@ final class PhameBlog extends PhameDAO | ||||
|   protected $status; | ||||
|   protected $mailKey; | ||||
|   protected $profileImagePHID; | ||||
|   protected $headerImagePHID; | ||||
|  | ||||
|   private $profileImageFile = self::ATTACHABLE; | ||||
|   private $headerImageFile = self::ATTACHABLE; | ||||
|  | ||||
|   const STATUS_ACTIVE = 'active'; | ||||
|   const STATUS_ARCHIVED = 'archived'; | ||||
| @@ -43,6 +45,7 @@ final class PhameBlog extends PhameDAO | ||||
|         'status' => 'text32', | ||||
|         'mailKey' => 'bytes20', | ||||
|         'profileImagePHID' => 'phid?', | ||||
|         'headerImagePHID' => 'phid?', | ||||
|  | ||||
|         // T6203/NULLABILITY | ||||
|         // These policies should always be non-null. | ||||
| @@ -212,6 +215,19 @@ final class PhameBlog extends PhameDAO | ||||
|     return $this->assertAttached($this->profileImageFile); | ||||
|   } | ||||
|  | ||||
|   public function getHeaderImageURI() { | ||||
|     return $this->getHeaderImageFile()->getBestURI(); | ||||
|   } | ||||
|  | ||||
|   public function attachHeaderImageFile(PhabricatorFile $file) { | ||||
|     $this->headerImageFile = $file; | ||||
|     return $this; | ||||
|   } | ||||
|  | ||||
|   public function getHeaderImageFile() { | ||||
|     return $this->assertAttached($this->headerImageFile); | ||||
|   } | ||||
|  | ||||
|  | ||||
| /* -(  PhabricatorPolicyInterface Implementation  )-------------------------- */ | ||||
|  | ||||
|   | ||||
| @@ -47,6 +47,7 @@ | ||||
|   height: 9px; | ||||
|   border-radius: 2px; | ||||
|   margin-left: 76px; | ||||
|   margin-bottom: 20px; | ||||
| } | ||||
|  | ||||
| .device-desktop .phui-timeline-wedge { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Chad Little
					Chad Little