@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<?php
2
3final class PhabricatorProjectEditPictureController
4 extends PhabricatorProjectController {
5
6 public function handleRequest(AphrontRequest $request) {
7 $viewer = $request->getViewer();
8 $id = $request->getURIData('id');
9
10 $project = id(new PhabricatorProjectQuery())
11 ->setViewer($viewer)
12 ->withIDs(array($id))
13 ->needImages(true)
14 ->requireCapabilities(
15 array(
16 PhabricatorPolicyCapability::CAN_VIEW,
17 PhabricatorPolicyCapability::CAN_EDIT,
18 ))
19 ->executeOne();
20 if (!$project) {
21 return new Aphront404Response();
22 }
23
24 $this->setProject($project);
25
26 $manage_uri = $this->getApplicationURI('manage/'.$project->getID().'/');
27
28 $supported_formats = PhabricatorFile::getTransformableImageFormats();
29 if ($supported_formats) {
30 $supported_formats_message = pht('Supported image formats: %s.',
31 implode(', ', $supported_formats));
32 } else {
33 $supported_formats_message = pht('Server supports no image formats.');
34 }
35 $e_file = true;
36 $errors = array();
37
38 // Get the image file transform.
39 $xform = PhabricatorFileTransform::getTransformByKey(
40 PhabricatorFileThumbnailTransform::TRANSFORM_PROFILE);
41
42 // Have an hard-limit to save our resources.
43 $max_image_dimensions = $xform->getMaxTransformDimensions();
44 $max_image_dimensions_message = pht('Maximum image dimensions: %s pixels.',
45 implode(mb_chr(215), $max_image_dimensions));
46
47 if ($request->isFormPost()) {
48 $phid = $request->getStr('phid');
49 $is_default = false;
50 if ($phid == PhabricatorPHIDConstants::PHID_VOID) {
51 $phid = null;
52 $is_default = true;
53 } else if ($phid) {
54 $file = id(new PhabricatorFileQuery())
55 ->setViewer($viewer)
56 ->withPHIDs(array($phid))
57 ->executeOne();
58 } else {
59 if ($request->getFileExists('picture')) {
60 $file = PhabricatorFile::newFromPHPUpload(
61 $_FILES['picture'],
62 array(
63 'authorPHID' => $viewer->getPHID(),
64 'canCDN' => true,
65 ));
66 } else {
67 $e_file = pht('Required');
68 $errors[] = pht(
69 'You must choose a file when uploading a new project picture.');
70 }
71 }
72
73 if (!$errors && !$is_default) {
74 if (!$file->isTransformableImage()) {
75 $e_file = pht('Not Supported');
76 $errors[] = $supported_formats_message;
77 } else {
78 $xformed = $xform->executeTransformExplicit($file);
79 }
80 }
81
82 if (!$errors) {
83 if ($is_default) {
84 $new_value = null;
85 } else {
86 $new_value = $xformed->getPHID();
87 }
88
89 $xactions = array();
90 $xactions[] = id(new PhabricatorProjectTransaction())
91 ->setTransactionType(
92 PhabricatorProjectImageTransaction::TRANSACTIONTYPE)
93 ->setNewValue($new_value);
94
95 $editor = id(new PhabricatorProjectTransactionEditor())
96 ->setActor($viewer)
97 ->setContentSourceFromRequest($request)
98 ->setContinueOnMissingFields(true)
99 ->setContinueOnNoEffect(true);
100
101 $editor->applyTransactions($project, $xactions);
102
103 return id(new AphrontRedirectResponse())->setURI($manage_uri);
104 }
105 }
106
107 $title = pht('Edit Project Picture');
108
109 $form = id(new PHUIFormLayoutView())
110 ->setViewer($viewer);
111
112 $builtin = PhabricatorProjectIconSet::getIconImage(
113 $project->getIcon());
114 $default_image = PhabricatorFile::loadBuiltin($this->getViewer(),
115 'projects/'.$builtin);
116
117 $images = array();
118
119 $current = $project->getProfileImagePHID();
120 $has_current = false;
121 if ($current) {
122 $files = id(new PhabricatorFileQuery())
123 ->setViewer($viewer)
124 ->withPHIDs(array($current))
125 ->execute();
126 if ($files) {
127 $file = head($files);
128 if ($file->isTransformableImage()) {
129 $has_current = true;
130 $images[$current] = array(
131 'uri' => $file->getBestURI(),
132 'tip' => pht('Current Picture'),
133 );
134 }
135 }
136 }
137
138 $root = dirname(phutil_get_library_root('phabricator'));
139 $root = $root.'/resources/builtin/projects/v3/';
140
141 $builtins = id(new FileFinder($root))
142 ->withType('f')
143 ->withFollowSymlinks(true)
144 ->find();
145
146 foreach ($builtins as $builtin) {
147 $file = PhabricatorFile::loadBuiltin($viewer, 'projects/v3/'.$builtin);
148 $images[$file->getPHID()] = array(
149 'uri' => $file->getBestURI(),
150 'tip' => pht('Builtin Image'),
151 );
152 }
153
154 $images[PhabricatorPHIDConstants::PHID_VOID] = array(
155 'uri' => $default_image->getBestURI(),
156 'tip' => pht('Default Picture'),
157 );
158
159 require_celerity_resource('people-profile-css');
160 Javelin::initBehavior('phabricator-tooltips', array());
161
162 $buttons = array();
163 foreach ($images as $phid => $spec) {
164 $button = javelin_tag(
165 'button',
166 array(
167 'class' => 'button-grey profile-image-button',
168 'sigil' => 'has-tooltip',
169 'meta' => array(
170 'tip' => $spec['tip'],
171 'size' => 300,
172 ),
173 ),
174 phutil_tag(
175 'img',
176 array(
177 'height' => 50,
178 'width' => 50,
179 'src' => $spec['uri'],
180 )));
181
182 $button = array(
183 phutil_tag(
184 'input',
185 array(
186 'type' => 'hidden',
187 'name' => 'phid',
188 'value' => $phid,
189 )),
190 $button,
191 );
192
193 $button = phabricator_form(
194 $viewer,
195 array(
196 'class' => 'profile-image-form',
197 'method' => 'POST',
198 ),
199 $button);
200
201 $buttons[] = $button;
202 }
203
204 if ($has_current) {
205 $form->appendChild(
206 id(new AphrontFormMarkupControl())
207 ->setLabel(pht('Current Picture'))
208 ->setValue(array_shift($buttons)));
209 }
210
211 $form->appendChild(
212 id(new AphrontFormMarkupControl())
213 ->setLabel(pht('Use Picture'))
214 ->setValue(
215 array(
216 $this->renderDefaultForm($project),
217 $buttons,
218 )));
219
220 $launch_id = celerity_generate_unique_node_id();
221 $input_id = celerity_generate_unique_node_id();
222
223 Javelin::initBehavior(
224 'launch-icon-composer',
225 array(
226 'launchID' => $launch_id,
227 'inputID' => $input_id,
228 ));
229
230 $compose_button = javelin_tag(
231 'button',
232 array(
233 'class' => 'button-grey',
234 'id' => $launch_id,
235 'sigil' => 'icon-composer',
236 ),
237 pht('Choose Icon and Color...'));
238
239 $compose_input = javelin_tag(
240 'input',
241 array(
242 'type' => 'hidden',
243 'id' => $input_id,
244 'name' => 'phid',
245 ));
246
247 $compose_form = phabricator_form(
248 $viewer,
249 array(
250 'class' => 'profile-image-form',
251 'method' => 'POST',
252 ),
253 array(
254 $compose_input,
255 $compose_button,
256 ));
257
258 $form->appendChild(
259 id(new AphrontFormMarkupControl())
260 ->setLabel(pht('Custom'))
261 ->setValue($compose_form));
262
263 $upload_form = id(new AphrontFormView())
264 ->setViewer($viewer)
265 ->setEncType('multipart/form-data')
266 ->appendChild(
267 id(new AphrontFormFileControl())
268 ->setName('picture')
269 ->setLabel(pht('Upload Picture'))
270 ->setError($e_file)
271 ->setCaption($supported_formats_message.' '.
272 $max_image_dimensions_message))
273 ->appendChild(
274 id(new AphrontFormSubmitControl())
275 ->addCancelButton($manage_uri)
276 ->setValue(pht('Upload Picture')));
277
278 $form_box = id(new PHUIObjectBoxView())
279 ->setHeaderText($title)
280 ->setFormErrors($errors)
281 ->setForm($form);
282
283 $upload_box = id(new PHUIObjectBoxView())
284 ->setHeaderText(pht('Upload New Picture'))
285 ->setForm($upload_form);
286
287 $nav = $this->newNavigation(
288 $project,
289 PhabricatorProject::ITEM_MANAGE);
290
291 return $this->newPage()
292 ->setTitle($title)
293 ->setNavigation($nav)
294 ->appendChild(
295 array(
296 $form_box,
297 $upload_box,
298 ));
299 }
300
301 private function renderDefaultForm(PhabricatorProject $project) {
302 $viewer = $this->getViewer();
303 $compose_color = $project->getDisplayIconComposeColor();
304 $compose_icon = $project->getDisplayIconComposeIcon();
305
306 $default_builtin = id(new PhabricatorFilesComposeIconBuiltinFile())
307 ->setColor($compose_color)
308 ->setIcon($compose_icon);
309
310 $file_builtins = PhabricatorFile::loadBuiltins(
311 $viewer,
312 array($default_builtin));
313
314 $file_builtin = head($file_builtins);
315
316 $default_button = javelin_tag(
317 'button',
318 array(
319 'class' => 'button-grey profile-image-button',
320 'sigil' => 'has-tooltip',
321 'meta' => array(
322 'tip' => pht('Use Icon and Color'),
323 'size' => 300,
324 ),
325 ),
326 phutil_tag(
327 'img',
328 array(
329 'height' => 50,
330 'width' => 50,
331 'src' => $file_builtin->getBestURI(),
332 )));
333
334 $inputs = array(
335 'projectPHID' => $project->getPHID(),
336 'icon' => $compose_icon,
337 'color' => $compose_color,
338 );
339
340 foreach ($inputs as $key => $value) {
341 $inputs[$key] = javelin_tag(
342 'input',
343 array(
344 'type' => 'hidden',
345 'name' => $key,
346 'value' => $value,
347 ));
348 }
349
350 $default_form = phabricator_form(
351 $viewer,
352 array(
353 'class' => 'profile-image-form',
354 'method' => 'POST',
355 'action' => '/file/compose/',
356 ),
357 array(
358 $inputs,
359 $default_button,
360 ));
361
362 return $default_form;
363 }
364
365}