@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add Hero Image to Phame Post

Summary: Adds a headerimage and lets you set it on posts for added reverence. Is that a word?

Test Plan:
Add an image, see an image.

{F1923010}

Reviewers: epriestley

Reviewed By: epriestley

Subscribers: Korvin

Differential Revision: https://secure.phabricator.com/D16873

+249 -1
+2
resources/sql/autopatches/20161115.phamepost.02.header.sql
··· 1 + ALTER TABLE {$NAMESPACE}_phame.phame_post 2 + ADD headerImagePHID VARBINARY(64);
+2
src/__phutil_library_map__.php
··· 4085 4085 'PhamePostEditEngine' => 'applications/phame/editor/PhamePostEditEngine.php', 4086 4086 'PhamePostEditor' => 'applications/phame/editor/PhamePostEditor.php', 4087 4087 'PhamePostFulltextEngine' => 'applications/phame/search/PhamePostFulltextEngine.php', 4088 + 'PhamePostHeaderPictureController' => 'applications/phame/controller/post/PhamePostHeaderPictureController.php', 4088 4089 'PhamePostHistoryController' => 'applications/phame/controller/post/PhamePostHistoryController.php', 4089 4090 'PhamePostListController' => 'applications/phame/controller/post/PhamePostListController.php', 4090 4091 'PhamePostListView' => 'applications/phame/view/PhamePostListView.php', ··· 9324 9325 'PhamePostEditEngine' => 'PhabricatorEditEngine', 9325 9326 'PhamePostEditor' => 'PhabricatorApplicationTransactionEditor', 9326 9327 'PhamePostFulltextEngine' => 'PhabricatorFulltextEngine', 9328 + 'PhamePostHeaderPictureController' => 'PhamePostController', 9327 9329 'PhamePostHistoryController' => 'PhamePostController', 9328 9330 'PhamePostListController' => 'PhamePostController', 9329 9331 'PhamePostListView' => 'AphrontTagView',
+1
src/applications/phame/application/PhabricatorPhameApplication.php
··· 53 53 'preview/' => 'PhabricatorMarkupPreviewController', 54 54 'move/(?P<id>\d+)/' => 'PhamePostMoveController', 55 55 'archive/(?P<id>\d+)/' => 'PhamePostArchiveController', 56 + 'header/(?P<id>[1-9]\d*)/' => 'PhamePostHeaderPictureController', 56 57 ), 57 58 'blog/' => array( 58 59 '(?:query/(?P<queryKey>[^/]+)/)?' => 'PhameBlogListController',
+1
src/applications/phame/controller/PhameLiveController.php
··· 90 90 if (strlen($post_id)) { 91 91 $post_query = id(new PhamePostQuery()) 92 92 ->setViewer($viewer) 93 + ->needHeaderImage(true) 93 94 ->withIDs(array($post_id)); 94 95 95 96 if ($blog) {
+136
src/applications/phame/controller/post/PhamePostHeaderPictureController.php
··· 1 + <?php 2 + 3 + final class PhamePostHeaderPictureController 4 + extends PhamePostController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $request->getViewer(); 8 + $id = $request->getURIData('id'); 9 + 10 + $post = id(new PhamePostQuery()) 11 + ->setViewer($viewer) 12 + ->withIDs(array($id)) 13 + ->needHeaderImage(true) 14 + ->requireCapabilities( 15 + array( 16 + PhabricatorPolicyCapability::CAN_VIEW, 17 + PhabricatorPolicyCapability::CAN_EDIT, 18 + )) 19 + ->executeOne(); 20 + if (!$post) { 21 + return new Aphront404Response(); 22 + } 23 + 24 + $post_uri = '/phame/post/view/'.$id; 25 + 26 + $supported_formats = PhabricatorFile::getTransformableImageFormats(); 27 + $e_file = true; 28 + $errors = array(); 29 + $delete_header = ($request->getInt('delete') == 1); 30 + 31 + if ($request->isFormPost()) { 32 + if ($request->getFileExists('header')) { 33 + $file = PhabricatorFile::newFromPHPUpload( 34 + $_FILES['header'], 35 + array( 36 + 'authorPHID' => $viewer->getPHID(), 37 + 'canCDN' => true, 38 + )); 39 + } else if (!$delete_header) { 40 + $e_file = pht('Required'); 41 + $errors[] = pht( 42 + 'You must choose a file when uploading a new post header.'); 43 + } 44 + 45 + if (!$errors && !$delete_header) { 46 + if (!$file->isTransformableImage()) { 47 + $e_file = pht('Not Supported'); 48 + $errors[] = pht( 49 + 'This server only supports these image formats: %s.', 50 + implode(', ', $supported_formats)); 51 + } 52 + } 53 + 54 + if (!$errors) { 55 + if ($delete_header) { 56 + $new_value = null; 57 + } else { 58 + $file->attachToObject($post->getPHID()); 59 + $new_value = $file->getPHID(); 60 + } 61 + 62 + $xactions = array(); 63 + $xactions[] = id(new PhamePostTransaction()) 64 + ->setTransactionType(PhamePostTransaction::TYPE_HEADERIMAGE) 65 + ->setNewValue($new_value); 66 + 67 + $editor = id(new PhamePostEditor()) 68 + ->setActor($viewer) 69 + ->setContentSourceFromRequest($request) 70 + ->setContinueOnMissingFields(true) 71 + ->setContinueOnNoEffect(true); 72 + 73 + $editor->applyTransactions($post, $xactions); 74 + 75 + return id(new AphrontRedirectResponse())->setURI($post_uri); 76 + } 77 + } 78 + 79 + $title = pht('Edit Post Header'); 80 + 81 + $upload_form = id(new AphrontFormView()) 82 + ->setUser($viewer) 83 + ->setEncType('multipart/form-data') 84 + ->appendChild( 85 + id(new AphrontFormFileControl()) 86 + ->setName('header') 87 + ->setLabel(pht('Upload Header')) 88 + ->setError($e_file) 89 + ->setCaption( 90 + pht('Supported formats: %s', implode(', ', $supported_formats)))) 91 + ->appendChild( 92 + id(new AphrontFormCheckboxControl()) 93 + ->setName('delete') 94 + ->setLabel(pht('Delete Header')) 95 + ->addCheckbox( 96 + 'delete', 97 + 1, 98 + null, 99 + null)) 100 + ->appendChild( 101 + id(new AphrontFormSubmitControl()) 102 + ->addCancelButton($post_uri) 103 + ->setValue(pht('Upload Header'))); 104 + 105 + $upload_box = id(new PHUIObjectBoxView()) 106 + ->setHeaderText(pht('Upload New Header')) 107 + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) 108 + ->setForm($upload_form); 109 + 110 + $crumbs = $this->buildApplicationCrumbs(); 111 + $crumbs->addTextCrumb( 112 + $post->getTitle(), 113 + $this->getApplicationURI('post/view/'.$id)); 114 + $crumbs->addTextCrumb(pht('Post Header')); 115 + $crumbs->setBorder(true); 116 + 117 + $header = id(new PHUIHeaderView()) 118 + ->setHeader(pht('Edit Post Header')) 119 + ->setHeaderIcon('fa-camera'); 120 + 121 + $view = id(new PHUITwoColumnView()) 122 + ->setHeader($header) 123 + ->setFooter(array( 124 + $upload_box, 125 + )); 126 + 127 + return $this->newPage() 128 + ->setTitle($title) 129 + ->setCrumbs($crumbs) 130 + ->appendChild( 131 + array( 132 + $view, 133 + )); 134 + 135 + } 136 + }
+40 -1
src/applications/phame/controller/post/PhamePostViewController.php
··· 19 19 $is_external = $this->getIsExternal(); 20 20 21 21 $header = id(new PHUIHeaderView()) 22 - ->setHeader($post->getTitle()) 22 + ->addClass('phame-header-bar') 23 23 ->setUser($viewer); 24 + 25 + $hero = $this->buildPhamePostHeader($post); 24 26 25 27 if (!$is_external) { 26 28 $actions = $this->renderActions($post); ··· 167 169 ->setCrumbs($crumbs) 168 170 ->appendChild( 169 171 array( 172 + $hero, 170 173 $document, 171 174 $about, 172 175 $properties, ··· 206 209 207 210 $actions->addAction( 208 211 id(new PhabricatorActionView()) 212 + ->setIcon('fa-camera-retro') 213 + ->setHref($this->getApplicationURI('post/header/'.$id.'/')) 214 + ->setName(pht('Edit Header Image')) 215 + ->setDisabled(!$can_edit)); 216 + 217 + $actions->addAction( 218 + id(new PhabricatorActionView()) 209 219 ->setIcon('fa-arrows') 210 220 ->setHref($this->getApplicationURI('post/move/'.$id.'/')) 211 221 ->setName(pht('Move Post')) ··· 305 315 ->execute(); 306 316 307 317 return array(head($prev), head($next)); 318 + } 319 + 320 + private function buildPhamePostHeader( 321 + PhamePost $post) { 322 + 323 + $image = null; 324 + if ($post->getHeaderImagePHID()) { 325 + $image = phutil_tag( 326 + 'div', 327 + array( 328 + 'class' => 'phame-header-hero', 329 + ), 330 + phutil_tag( 331 + 'img', 332 + array( 333 + 'src' => $post->getHeaderImageURI(), 334 + 'class' => 'phame-header-image', 335 + ))); 336 + } 337 + 338 + $title = phutil_tag_div('phame-header-title', $post->getTitle()); 339 + $subtitle = null; 340 + if ($post->getSubtitle()) { 341 + $subtitle = phutil_tag_div('phame-header-subtitle', $post->getSubtitle()); 342 + } 343 + 344 + return phutil_tag_div( 345 + 'phame-mega-header', array($image, $title, $subtitle)); 346 + 308 347 } 309 348 310 349 }
+7
src/applications/phame/editor/PhamePostEditor.php
··· 19 19 $types[] = PhamePostTransaction::TYPE_SUBTITLE; 20 20 $types[] = PhamePostTransaction::TYPE_BODY; 21 21 $types[] = PhamePostTransaction::TYPE_VISIBILITY; 22 + $types[] = PhamePostTransaction::TYPE_HEADERIMAGE; 22 23 $types[] = PhabricatorTransactions::TYPE_COMMENT; 23 24 24 25 return $types; ··· 39 40 return $object->getBody(); 40 41 case PhamePostTransaction::TYPE_VISIBILITY: 41 42 return $object->getVisibility(); 43 + case PhamePostTransaction::TYPE_HEADERIMAGE: 44 + return $object->getHeaderImagePHID(); 42 45 } 43 46 } 44 47 ··· 51 54 case PhamePostTransaction::TYPE_SUBTITLE: 52 55 case PhamePostTransaction::TYPE_BODY: 53 56 case PhamePostTransaction::TYPE_VISIBILITY: 57 + case PhamePostTransaction::TYPE_HEADERIMAGE: 54 58 case PhamePostTransaction::TYPE_BLOG: 55 59 return $xaction->getNewValue(); 56 60 } ··· 69 73 return $object->setBody($xaction->getNewValue()); 70 74 case PhamePostTransaction::TYPE_BLOG: 71 75 return $object->setBlogPHID($xaction->getNewValue()); 76 + case PhamePostTransaction::TYPE_HEADERIMAGE: 77 + return $object->setHeaderImagePHID($xaction->getNewValue()); 72 78 case PhamePostTransaction::TYPE_VISIBILITY: 73 79 if ($xaction->getNewValue() == PhameConstants::VISIBILITY_DRAFT) { 74 80 $object->setDatePublished(0); ··· 93 99 case PhamePostTransaction::TYPE_SUBTITLE: 94 100 case PhamePostTransaction::TYPE_BODY: 95 101 case PhamePostTransaction::TYPE_VISIBILITY: 102 + case PhamePostTransaction::TYPE_HEADERIMAGE: 96 103 case PhamePostTransaction::TYPE_BLOG: 97 104 return; 98 105 }
+29
src/applications/phame/query/PhamePostQuery.php
··· 9 9 private $publishedAfter; 10 10 private $phids; 11 11 12 + private $needHeaderImage; 13 + 12 14 public function withIDs(array $ids) { 13 15 $this->ids = $ids; 14 16 return $this; ··· 39 41 return $this; 40 42 } 41 43 44 + public function needHeaderImage($need) { 45 + $this->needHeaderImage = $need; 46 + return $this; 47 + } 48 + 42 49 public function newResultObject() { 43 50 return new PhamePost(); 44 51 } ··· 69 76 } 70 77 71 78 $post->attachBlog($blog); 79 + } 80 + 81 + if ($this->needHeaderImage) { 82 + $file_phids = mpull($posts, 'getHeaderImagePHID'); 83 + $file_phids = array_filter($file_phids); 84 + if ($file_phids) { 85 + $files = id(new PhabricatorFileQuery()) 86 + ->setParentQuery($this) 87 + ->setViewer($this->getViewer()) 88 + ->withPHIDs($file_phids) 89 + ->execute(); 90 + $files = mpull($files, null, 'getPHID'); 91 + } else { 92 + $files = array(); 93 + } 94 + 95 + foreach ($posts as $post) { 96 + $file = idx($files, $post->getHeaderImagePHID()); 97 + if ($file) { 98 + $post->attachHeaderImageFile($file); 99 + } 100 + } 72 101 } 73 102 74 103 return $posts;
+16
src/applications/phame/storage/PhamePost.php
··· 26 26 protected $datePublished; 27 27 protected $blogPHID; 28 28 protected $mailKey; 29 + protected $headerImagePHID; 29 30 30 31 private $blog = self::ATTACHABLE; 32 + private $headerImageFile = self::ATTACHABLE; 31 33 32 34 public static function initializePost( 33 35 PhabricatorUser $blogger, ··· 127 129 'phameTitle' => 'sort64?', 128 130 'visibility' => 'uint32', 129 131 'mailKey' => 'bytes20', 132 + 'headerImagePHID' => 'phid?', 130 133 131 134 // T6203/NULLABILITY 132 135 // These seem like they should always be non-null? ··· 170 173 171 174 public function getSlug() { 172 175 return PhabricatorSlug::normalizeProjectSlug($this->getTitle(), true); 176 + } 177 + 178 + public function getHeaderImageURI() { 179 + return $this->getHeaderImageFile()->getBestURI(); 180 + } 181 + 182 + public function attachHeaderImageFile(PhabricatorFile $file) { 183 + $this->headerImageFile = $file; 184 + return $this; 185 + } 186 + 187 + public function getHeaderImageFile() { 188 + return $this->assertAttached($this->headerImageFile); 173 189 } 174 190 175 191
+15
src/applications/phame/storage/PhamePostTransaction.php
··· 7 7 const TYPE_SUBTITLE = 'phame.post.subtitle'; 8 8 const TYPE_BODY = 'phame.post.body'; 9 9 const TYPE_VISIBILITY = 'phame.post.visibility'; 10 + const TYPE_HEADERIMAGE = 'phame.post.headerimage'; 10 11 const TYPE_BLOG = 'phame.post.blog'; 11 12 12 13 const MAILTAG_CONTENT = 'phame-post-content'; ··· 70 71 switch ($this->getTransactionType()) { 71 72 case PhabricatorTransactions::TYPE_CREATE: 72 73 return 'fa-plus'; 74 + break; 75 + case self::TYPE_HEADERIMAGE: 76 + return 'fa-camera-retro'; 73 77 break; 74 78 case self::TYPE_VISIBILITY: 75 79 if ($new == PhameConstants::VISIBILITY_PUBLISHED) { ··· 156 160 '%s updated the blog post.', 157 161 $this->renderHandleLink($author_phid)); 158 162 break; 163 + case self::TYPE_HEADERIMAGE: 164 + return pht( 165 + '%s updated the header image.', 166 + $this->renderHandleLink($author_phid)); 167 + break; 159 168 case self::TYPE_VISIBILITY: 160 169 if ($new == PhameConstants::VISIBILITY_DRAFT) { 161 170 return pht( ··· 219 228 case self::TYPE_BODY: 220 229 return pht( 221 230 '%s updated the blog post %s.', 231 + $this->renderHandleLink($author_phid), 232 + $this->renderHandleLink($object_phid)); 233 + break; 234 + case self::TYPE_HEADERIMAGE: 235 + return pht( 236 + '%s updated the header image for post %s.', 222 237 $this->renderHandleLink($author_phid), 223 238 $this->renderHandleLink($object_phid)); 224 239 break;