@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.

Improve UI for selecting profile pictures

Summary:
Ref T1703. Move profile pictures to a separate, dedicated interface. Instead of the 35 controls we currently provide, just show all the possible images we can find and then let the user upload an additional one if they want.

Possible improvements to this interface:

- Write an edge so we can show old profile pictures too.
- The cropping/scaling got a bit buggy at some point, fix that.
- Refresh OAuth sources which we're capable of refreshing before showing images (more work than I really want to deal with).
- We could show little inset icons for the image source ("f" for Facebook, etc.) instead of just the tooltips.

Test Plan:
Chose images, uploaded new images, hit various error cases.

{F49344}

Reviewers: chad, btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T2919, T1703

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

+373 -137
+54 -45
src/__celerity_resource_map__.php
··· 3009 3009 ), 3010 3010 'disk' => '/rsrc/js/application/herald/PathTypeahead.js', 3011 3011 ), 3012 + 'people-profile-css' => 3013 + array( 3014 + 'uri' => '/res/1f0e94c5/rsrc/css/application/people/people-profile.css', 3015 + 'type' => 'css', 3016 + 'requires' => 3017 + array( 3018 + ), 3019 + 'disk' => '/rsrc/css/application/people/people-profile.css', 3020 + ), 3012 3021 'phabricator-action-header-view-css' => 3013 3022 array( 3014 3023 'uri' => '/res/3b701648/rsrc/css/layout/phabricator-action-header-view.css', ··· 3197 3206 ), 3198 3207 'phabricator-header-view-css' => 3199 3208 array( 3200 - 'uri' => '/res/76173bb6/rsrc/css/layout/phabricator-header-view.css', 3209 + 'uri' => '/res/da35cfa0/rsrc/css/layout/phabricator-header-view.css', 3201 3210 'type' => 'css', 3202 3211 'requires' => 3203 3212 array( ··· 4140 4149 ), array( 4141 4150 'packages' => 4142 4151 array( 4143 - 'd7254b92' => 4152 + '680ace9b' => 4144 4153 array( 4145 4154 'name' => 'core.pkg.css', 4146 4155 'symbols' => ··· 4188 4197 40 => 'phabricator-property-list-view-css', 4189 4198 41 => 'phabricator-tag-view-css', 4190 4199 ), 4191 - 'uri' => '/res/pkg/d7254b92/core.pkg.css', 4200 + 'uri' => '/res/pkg/680ace9b/core.pkg.css', 4192 4201 'type' => 'css', 4193 4202 ), 4194 4203 '75ccea43' => ··· 4382 4391 'reverse' => 4383 4392 array( 4384 4393 'aphront-attached-file-view-css' => 'adc3c36d', 4385 - 'aphront-dialog-view-css' => 'd7254b92', 4386 - 'aphront-error-view-css' => 'd7254b92', 4387 - 'aphront-form-view-css' => 'd7254b92', 4388 - 'aphront-list-filter-view-css' => 'd7254b92', 4389 - 'aphront-pager-view-css' => 'd7254b92', 4390 - 'aphront-panel-view-css' => 'd7254b92', 4391 - 'aphront-table-view-css' => 'd7254b92', 4392 - 'aphront-tokenizer-control-css' => 'd7254b92', 4393 - 'aphront-tooltip-css' => 'd7254b92', 4394 - 'aphront-typeahead-control-css' => 'd7254b92', 4394 + 'aphront-dialog-view-css' => '680ace9b', 4395 + 'aphront-error-view-css' => '680ace9b', 4396 + 'aphront-form-view-css' => '680ace9b', 4397 + 'aphront-list-filter-view-css' => '680ace9b', 4398 + 'aphront-pager-view-css' => '680ace9b', 4399 + 'aphront-panel-view-css' => '680ace9b', 4400 + 'aphront-table-view-css' => '680ace9b', 4401 + 'aphront-tokenizer-control-css' => '680ace9b', 4402 + 'aphront-tooltip-css' => '680ace9b', 4403 + 'aphront-typeahead-control-css' => '680ace9b', 4395 4404 'differential-changeset-view-css' => 'dd27a69b', 4396 4405 'differential-core-view-css' => 'dd27a69b', 4397 4406 'differential-inline-comment-editor' => '4ad86dee', ··· 4405 4414 'differential-table-of-contents-css' => 'dd27a69b', 4406 4415 'diffusion-commit-view-css' => 'c8ce2d88', 4407 4416 'diffusion-icons-css' => 'c8ce2d88', 4408 - 'global-drag-and-drop-css' => 'd7254b92', 4417 + 'global-drag-and-drop-css' => '680ace9b', 4409 4418 'inline-comment-summary-css' => 'dd27a69b', 4410 4419 'javelin-aphlict' => '75ccea43', 4411 4420 'javelin-behavior' => 'a9f14d76', ··· 4479 4488 'javelin-util' => 'a9f14d76', 4480 4489 'javelin-vector' => 'a9f14d76', 4481 4490 'javelin-workflow' => 'a9f14d76', 4482 - 'lightbox-attachment-css' => 'd7254b92', 4491 + 'lightbox-attachment-css' => '680ace9b', 4483 4492 'maniphest-task-summary-css' => 'adc3c36d', 4484 4493 'maniphest-transaction-detail-css' => 'adc3c36d', 4485 - 'phabricator-action-list-view-css' => 'd7254b92', 4486 - 'phabricator-application-launch-view-css' => 'd7254b92', 4494 + 'phabricator-action-list-view-css' => '680ace9b', 4495 + 'phabricator-application-launch-view-css' => '680ace9b', 4487 4496 'phabricator-busy' => '75ccea43', 4488 4497 'phabricator-content-source-view-css' => 'dd27a69b', 4489 - 'phabricator-core-css' => 'd7254b92', 4490 - 'phabricator-crumbs-view-css' => 'd7254b92', 4498 + 'phabricator-core-css' => '680ace9b', 4499 + 'phabricator-crumbs-view-css' => '680ace9b', 4491 4500 'phabricator-drag-and-drop-file-upload' => '4ad86dee', 4492 4501 'phabricator-dropdown-menu' => '75ccea43', 4493 4502 'phabricator-file-upload' => '75ccea43', 4494 - 'phabricator-filetree-view-css' => 'd7254b92', 4495 - 'phabricator-flag-css' => 'd7254b92', 4496 - 'phabricator-form-view-css' => 'd7254b92', 4497 - 'phabricator-header-view-css' => 'd7254b92', 4503 + 'phabricator-filetree-view-css' => '680ace9b', 4504 + 'phabricator-flag-css' => '680ace9b', 4505 + 'phabricator-form-view-css' => '680ace9b', 4506 + 'phabricator-header-view-css' => '680ace9b', 4498 4507 'phabricator-hovercard' => '75ccea43', 4499 - 'phabricator-jump-nav' => 'd7254b92', 4508 + 'phabricator-jump-nav' => '680ace9b', 4500 4509 'phabricator-keyboard-shortcut' => '75ccea43', 4501 4510 'phabricator-keyboard-shortcut-manager' => '75ccea43', 4502 - 'phabricator-main-menu-view' => 'd7254b92', 4511 + 'phabricator-main-menu-view' => '680ace9b', 4503 4512 'phabricator-menu-item' => '75ccea43', 4504 - 'phabricator-nav-view-css' => 'd7254b92', 4513 + 'phabricator-nav-view-css' => '680ace9b', 4505 4514 'phabricator-notification' => '75ccea43', 4506 - 'phabricator-notification-css' => 'd7254b92', 4507 - 'phabricator-notification-menu-css' => 'd7254b92', 4508 - 'phabricator-object-item-list-view-css' => 'd7254b92', 4515 + 'phabricator-notification-css' => '680ace9b', 4516 + 'phabricator-notification-menu-css' => '680ace9b', 4517 + 'phabricator-object-item-list-view-css' => '680ace9b', 4509 4518 'phabricator-object-selector-css' => 'dd27a69b', 4510 4519 'phabricator-phtize' => '75ccea43', 4511 4520 'phabricator-prefab' => '75ccea43', 4512 4521 'phabricator-project-tag-css' => 'adc3c36d', 4513 - 'phabricator-property-list-view-css' => 'd7254b92', 4514 - 'phabricator-remarkup-css' => 'd7254b92', 4522 + 'phabricator-property-list-view-css' => '680ace9b', 4523 + 'phabricator-remarkup-css' => '680ace9b', 4515 4524 'phabricator-shaped-request' => '4ad86dee', 4516 - 'phabricator-side-menu-view-css' => 'd7254b92', 4517 - 'phabricator-standard-page-view' => 'd7254b92', 4518 - 'phabricator-tag-view-css' => 'd7254b92', 4525 + 'phabricator-side-menu-view-css' => '680ace9b', 4526 + 'phabricator-standard-page-view' => '680ace9b', 4527 + 'phabricator-tag-view-css' => '680ace9b', 4519 4528 'phabricator-textareautils' => '75ccea43', 4520 4529 'phabricator-tooltip' => '75ccea43', 4521 - 'phabricator-transaction-view-css' => 'd7254b92', 4522 - 'phabricator-zindex-css' => 'd7254b92', 4523 - 'phui-button-css' => 'd7254b92', 4524 - 'phui-form-css' => 'd7254b92', 4525 - 'phui-icon-view-css' => 'd7254b92', 4526 - 'phui-spacing-css' => 'd7254b92', 4527 - 'sprite-apps-large-css' => 'd7254b92', 4528 - 'sprite-gradient-css' => 'd7254b92', 4529 - 'sprite-icons-css' => 'd7254b92', 4530 - 'sprite-menu-css' => 'd7254b92', 4531 - 'syntax-highlighting-css' => 'd7254b92', 4530 + 'phabricator-transaction-view-css' => '680ace9b', 4531 + 'phabricator-zindex-css' => '680ace9b', 4532 + 'phui-button-css' => '680ace9b', 4533 + 'phui-form-css' => '680ace9b', 4534 + 'phui-icon-view-css' => '680ace9b', 4535 + 'phui-spacing-css' => '680ace9b', 4536 + 'sprite-apps-large-css' => '680ace9b', 4537 + 'sprite-gradient-css' => '680ace9b', 4538 + 'sprite-icons-css' => '680ace9b', 4539 + 'sprite-menu-css' => '680ace9b', 4540 + 'syntax-highlighting-css' => '680ace9b', 4532 4541 ), 4533 4542 ));
+2
src/__phutil_library_map__.php
··· 1344 1344 'PhabricatorPeopleLogsController' => 'applications/people/controller/PhabricatorPeopleLogsController.php', 1345 1345 'PhabricatorPeopleProfileController' => 'applications/people/controller/PhabricatorPeopleProfileController.php', 1346 1346 'PhabricatorPeopleProfileEditController' => 'applications/people/controller/PhabricatorPeopleProfileEditController.php', 1347 + 'PhabricatorPeopleProfilePictureController' => 'applications/people/controller/PhabricatorPeopleProfilePictureController.php', 1347 1348 'PhabricatorPeopleQuery' => 'applications/people/query/PhabricatorPeopleQuery.php', 1348 1349 'PhabricatorPeopleSearchEngine' => 'applications/people/query/PhabricatorPeopleSearchEngine.php', 1349 1350 'PhabricatorPeopleTestDataGenerator' => 'applications/people/lipsum/PhabricatorPeopleTestDataGenerator.php', ··· 3303 3304 'PhabricatorPeopleLogsController' => 'PhabricatorPeopleController', 3304 3305 'PhabricatorPeopleProfileController' => 'PhabricatorPeopleController', 3305 3306 'PhabricatorPeopleProfileEditController' => 'PhabricatorPeopleController', 3307 + 'PhabricatorPeopleProfilePictureController' => 'PhabricatorPeopleController', 3306 3308 'PhabricatorPeopleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 3307 3309 'PhabricatorPeopleSearchEngine' => 'PhabricatorApplicationSearchEngine', 3308 3310 'PhabricatorPeopleTestDataGenerator' => 'PhabricatorTestDataGenerator',
-1
src/applications/files/PhabricatorImageTransformer.php
··· 94 94 } 95 95 96 96 $cropped = $this->applyScaleWithImagemagick($file, $x, $scaled_y); 97 - 98 97 if ($cropped != null) { 99 98 return $cropped; 100 99 }
+2
src/applications/people/application/PhabricatorApplicationPeople.php
··· 46 46 'ldap/' => 'PhabricatorPeopleLdapController', 47 47 'editprofile/(?P<id>[1-9]\d*)/' => 48 48 'PhabricatorPeopleProfileEditController', 49 + 'picture/(?P<id>[1-9]\d*)/' => 50 + 'PhabricatorPeopleProfilePictureController', 49 51 ), 50 52 '/p/(?P<username>[\w._-]+)/(?:(?P<page>\w+)/)?' 51 53 => 'PhabricatorPeopleProfileController',
+8
src/applications/people/controller/PhabricatorPeopleProfileController.php
··· 117 117 ->setDisabled(!$can_edit) 118 118 ->setWorkflow(!$can_edit)); 119 119 120 + $actions->addAction( 121 + id(new PhabricatorActionView()) 122 + ->setIcon('image') 123 + ->setName(pht('Edit Profile Picture')) 124 + ->setHref($this->getApplicationURI('picture/'.$user->getID().'/')) 125 + ->setDisabled(!$can_edit) 126 + ->setWorkflow(!$can_edit)); 127 + 120 128 if ($viewer->getIsAdmin()) { 121 129 $actions->addAction( 122 130 id(new PhabricatorActionView())
+292
src/applications/people/controller/PhabricatorPeopleProfilePictureController.php
··· 1 + <?php 2 + 3 + final class PhabricatorPeopleProfilePictureController 4 + extends PhabricatorPeopleController { 5 + 6 + private $id; 7 + 8 + public function shouldRequireAdmin() { 9 + return false; 10 + } 11 + 12 + public function willProcessRequest(array $data) { 13 + $this->id = $data['id']; 14 + } 15 + 16 + public function processRequest() { 17 + $request = $this->getRequest(); 18 + $viewer = $request->getUser(); 19 + 20 + $user = id(new PhabricatorPeopleQuery()) 21 + ->setViewer($viewer) 22 + ->withIDs(array($this->id)) 23 + ->requireCapabilities( 24 + array( 25 + PhabricatorPolicyCapability::CAN_VIEW, 26 + PhabricatorPolicyCapability::CAN_EDIT, 27 + )) 28 + ->executeOne(); 29 + if (!$user) { 30 + return new Aphront404Response(); 31 + } 32 + 33 + $profile_uri = '/p/'.$user->getUsername().'/'; 34 + 35 + $supported_formats = PhabricatorFile::getTransformableImageFormats(); 36 + $e_file = true; 37 + $errors = array(); 38 + 39 + if ($request->isFormPost()) { 40 + $phid = $request->getStr('phid'); 41 + $is_default = false; 42 + if ($phid == PhabricatorPHIDConstants::PHID_VOID) { 43 + $phid = null; 44 + $is_default = true; 45 + } else if ($phid) { 46 + $file = id(new PhabricatorFileQuery()) 47 + ->setViewer($viewer) 48 + ->withPHIDs(array($phid)) 49 + ->executeOne(); 50 + } else { 51 + if ($request->getFileExists('picture')) { 52 + $file = PhabricatorFile::newFromPHPUpload( 53 + $_FILES['picture'], 54 + array( 55 + 'authorPHID' => $viewer->getPHID(), 56 + )); 57 + } else { 58 + $e_file = pht('Required'); 59 + $errors[] = pht( 60 + 'You must choose a file when uploading a new profile picture.'); 61 + } 62 + } 63 + 64 + if (!$errors && !$is_default) { 65 + if (!$file->isTransformableImage()) { 66 + $e_file = pht('Not Supported'); 67 + $errors[] = pht( 68 + 'This server only supports these image formats: %s.', 69 + implode(', ', $supported_formats)); 70 + } else { 71 + $xformer = new PhabricatorImageTransformer(); 72 + $xformed = $xformer->executeProfileTransform( 73 + $file, 74 + $width = 50, 75 + $min_height = 50, 76 + $max_height = 50); 77 + } 78 + } 79 + 80 + if (!$errors) { 81 + $user->setProfileImagePHID($xformed->getPHID()); 82 + $user->save(); 83 + return id(new AphrontRedirectResponse())->setURI($profile_uri); 84 + } 85 + } 86 + 87 + $title = pht('Edit Profile Picture'); 88 + $crumbs = $this->buildApplicationCrumbs(); 89 + $crumbs->addCrumb( 90 + id(new PhabricatorCrumbView()) 91 + ->setName($user->getUsername()) 92 + ->setHref($profile_uri)); 93 + $crumbs->addCrumb( 94 + id(new PhabricatorCrumbView()) 95 + ->setName($title)); 96 + 97 + $form = id(new AphrontFormLayoutView()) 98 + ->setUser($viewer); 99 + 100 + $default_image = PhabricatorFile::loadBuiltin($viewer, 'profile.png'); 101 + 102 + $images = array(); 103 + 104 + $current = $user->getProfileImagePHID(); 105 + if ($current) { 106 + $files = id(new PhabricatorFileQuery()) 107 + ->setViewer($viewer) 108 + ->withPHIDs(array($current)) 109 + ->execute(); 110 + if ($files) { 111 + $file = head($files); 112 + if ($file->isTransformableImage()) { 113 + $images[$current] = array( 114 + 'uri' => $file->getBestURI(), 115 + 'tip' => pht('Current Picture'), 116 + ); 117 + } 118 + } 119 + } 120 + 121 + // Try to add external account images for any associated external accounts. 122 + $accounts = id(new PhabricatorExternalAccountQuery()) 123 + ->setViewer($viewer) 124 + ->withUserPHIDs(array($user->getPHID())) 125 + ->needImages(true) 126 + ->execute(); 127 + 128 + foreach ($accounts as $account) { 129 + $file = $account->getProfileImageFile(); 130 + if ($account->getProfileImagePHID() != $file->getPHID()) { 131 + // This is a default image, just skip it. 132 + continue; 133 + } 134 + 135 + $provider = PhabricatorAuthProvider::getEnabledProviderByKey( 136 + $account->getProviderKey()); 137 + if ($provider) { 138 + $tip = pht('Picture From %s', $provider->getProviderName()); 139 + } else { 140 + $tip = pht('Picture From External Account'); 141 + } 142 + 143 + if ($file->isTransformableImage()) { 144 + $images[$file->getPHID()] = array( 145 + 'uri' => $file->getBestURI(), 146 + 'tip' => $tip, 147 + ); 148 + } 149 + } 150 + 151 + // Try to add Gravatar images for any email addresses associated with the 152 + // account. 153 + if (PhabricatorEnv::getEnvConfig('security.allow-outbound-http')) { 154 + $emails = id(new PhabricatorUserEmail())->loadAllWhere( 155 + 'userPHID = %s ORDER BY address', 156 + $viewer->getPHID()); 157 + 158 + $futures = array(); 159 + foreach ($emails as $email_object) { 160 + $email = $email_object->getAddress(); 161 + 162 + $hash = md5(strtolower(trim($email))); 163 + $uri = id(new PhutilURI("https://secure.gravatar.com/avatar/{$hash}")) 164 + ->setQueryParams( 165 + array( 166 + 'size' => 200, 167 + 'default' => '404', 168 + 'rating' => 'x', 169 + )); 170 + $futures[$email] = new HTTPSFuture($uri); 171 + } 172 + 173 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 174 + foreach (Futures($futures) as $email => $future) { 175 + try { 176 + list($body) = $future->resolvex(); 177 + $file = PhabricatorFile::newFromFileData( 178 + $body, 179 + array( 180 + 'name' => 'profile-gravatar', 181 + 'ttl' => (60 * 60 * 4), 182 + )); 183 + if ($file->isTransformableImage()) { 184 + $images[$file->getPHID()] = array( 185 + 'uri' => $file->getBestURI(), 186 + 'tip' => pht('Gravatar for %s', $email), 187 + ); 188 + } 189 + } catch (Exception $ex) { 190 + // Just continue. 191 + } 192 + } 193 + unset($unguarded); 194 + } 195 + 196 + $images[PhabricatorPHIDConstants::PHID_VOID] = array( 197 + 'uri' => $default_image->getBestURI(), 198 + 'tip' => pht('Default Picture'), 199 + ); 200 + 201 + require_celerity_resource('people-profile-css'); 202 + Javelin::initBehavior('phabricator-tooltips', array()); 203 + 204 + $buttons = array(); 205 + foreach ($images as $phid => $spec) { 206 + $button = javelin_tag( 207 + 'button', 208 + array( 209 + 'class' => 'grey profile-image-button', 210 + 'sigil' => 'has-tooltip', 211 + 'meta' => array( 212 + 'tip' => $spec['tip'], 213 + 'size' => 300, 214 + ), 215 + ), 216 + phutil_tag( 217 + 'img', 218 + array( 219 + 'height' => 50, 220 + 'width' => 50, 221 + 'src' => $spec['uri'], 222 + ))); 223 + 224 + $button = array( 225 + phutil_tag( 226 + 'input', 227 + array( 228 + 'type' => 'hidden', 229 + 'name' => 'phid', 230 + 'value' => $phid, 231 + )), 232 + $button); 233 + 234 + $button = phabricator_form( 235 + $viewer, 236 + array( 237 + 'class' => 'profile-image-form', 238 + 'method' => 'POST', 239 + ), 240 + $button); 241 + 242 + $buttons[] = $button; 243 + } 244 + 245 + $form->appendChild( 246 + id(new AphrontFormMarkupControl()) 247 + ->setLabel(pht('Current Picture')) 248 + ->setValue(array_slice($buttons, 0, 1))); 249 + 250 + $form->appendChild( 251 + id(new AphrontFormMarkupControl()) 252 + ->setLabel(pht('Use Picture')) 253 + ->setValue(array_slice($buttons, 1))); 254 + 255 + $upload_head = id(new PhabricatorHeaderView()) 256 + ->setHeader(pht('Upload New Picture')); 257 + 258 + $upload_form = id(new AphrontFormView()) 259 + ->setUser($user) 260 + ->setFlexible(true) 261 + ->setEncType('multipart/form-data') 262 + ->appendChild( 263 + id(new AphrontFormFileControl()) 264 + ->setName('picture') 265 + ->setLabel(pht('Upload Picture')) 266 + ->setError($e_file) 267 + ->setCaption( 268 + pht('Supported formats: %s', implode(', ', $supported_formats)))) 269 + ->appendChild( 270 + id(new AphrontFormSubmitControl()) 271 + ->addCancelButton($profile_uri) 272 + ->setValue(pht('Upload Picture'))); 273 + 274 + if ($errors) { 275 + $errors = id(new AphrontErrorView())->setErrors($errors); 276 + } 277 + 278 + return $this->buildApplicationPage( 279 + array( 280 + $crumbs, 281 + $errors, 282 + $form, 283 + $upload_head, 284 + $upload_form, 285 + ), 286 + array( 287 + 'title' => $title, 288 + 'device' => true, 289 + 'dust' => true, 290 + )); 291 + } 292 + }
+2 -91
src/applications/settings/panel/PhabricatorSettingsPanelProfile.php
··· 18 18 public function processRequest(AphrontRequest $request) { 19 19 $user = $request->getUser(); 20 20 21 - $profile = $user->loadUserProfile(); 22 - 23 - $supported_formats = PhabricatorFile::getTransformableImageFormats(); 24 - 25 - $e_image = null; 26 21 $errors = array(); 27 22 if ($request->isFormPost()) { 28 23 $sex = $request->getStr('sex'); ··· 36 31 // Checked in runtime. 37 32 $user->setTranslation($request->getStr('translation')); 38 33 39 - $default_image = $request->getExists('default_image'); 40 - $gravatar_email = $request->getStr('gravatar'); 41 - if ($default_image) { 42 - $profile->setProfileImagePHID(null); 43 - $user->setProfileImagePHID(null); 44 - } else if (!empty($gravatar_email) || $request->getFileExists('image')) { 45 - $file = null; 46 - if (!empty($gravatar_email)) { 47 - // These steps recommended by: 48 - // https://en.gravatar.com/site/implement/hash/ 49 - $trimmed = trim($gravatar_email); 50 - $lower_cased = strtolower($trimmed); 51 - $hash = md5($lower_cased); 52 - $url = 'http://www.gravatar.com/avatar/'.($hash).'?s=200'; 53 - $file = PhabricatorFile::newFromFileDownload( 54 - $url, 55 - array( 56 - 'name' => 'gravatar', 57 - 'authorPHID' => $user->getPHID(), 58 - )); 59 - } else if ($request->getFileExists('image')) { 60 - $file = PhabricatorFile::newFromPHPUpload( 61 - $_FILES['image'], 62 - array( 63 - 'authorPHID' => $user->getPHID(), 64 - )); 65 - } 66 - 67 - $okay = $file->isTransformableImage(); 68 - if ($okay) { 69 - $xformer = new PhabricatorImageTransformer(); 70 - 71 - // Generate the large picture for the profile page. 72 - $large_xformed = $xformer->executeProfileTransform( 73 - $file, 74 - $width = 280, 75 - $min_height = 140, 76 - $max_height = 420); 77 - $profile->setProfileImagePHID($large_xformed->getPHID()); 78 - 79 - // Generate the small picture for comments, etc. 80 - $small_xformed = $xformer->executeProfileTransform( 81 - $file, 82 - $width = 50, 83 - $min_height = 50, 84 - $max_height = 50); 85 - $user->setProfileImagePHID($small_xformed->getPHID()); 86 - } else { 87 - $e_image = pht('Not Supported'); 88 - $errors[] = 89 - pht('This server only supports these image formats:'). 90 - ' ' .implode(', ', $supported_formats); 91 - } 92 - } 93 - 94 34 if (!$errors) { 95 35 $user->save(); 96 - $profile->save(); 97 36 $response = id(new AphrontRedirectResponse()) 98 37 ->setURI($this->getPanelURI('?saved=true')); 99 38 return $response; ··· 116 55 } 117 56 } 118 57 119 - $img_src = $user->loadProfileImageURI(); 120 58 $profile_uri = PhabricatorEnv::getURI('/p/'.$user->getUsername().'/'); 121 59 122 60 $sexes = array( ··· 144 82 $form = new AphrontFormView(); 145 83 $form 146 84 ->setUser($request->getUser()) 147 - ->setEncType('multipart/form-data') 148 85 ->appendChild( 149 86 id(new AphrontFormSelectControl()) 150 87 ->setOptions($sexes) ··· 156 93 ->setOptions($translations) 157 94 ->setLabel(pht('Translation')) 158 95 ->setName('translation') 159 - ->setValue($user->getTranslation())) 160 - ->appendChild( 161 - id(new AphrontFormMarkupControl()) 162 - ->setLabel(pht('Profile Image')) 163 - ->setValue( 164 - phutil_tag( 165 - 'img', 166 - array( 167 - 'src' => $img_src, 168 - )))) 169 - ->appendChild( 170 - id(new AphrontFormImageControl()) 171 - ->setLabel(pht('Change Image')) 172 - ->setName('image') 173 - ->setError($e_image) 174 - ->setCaption( 175 - pht('Supported formats: %s', implode(', ', $supported_formats)))); 176 - 177 - if (PhabricatorEnv::getEnvConfig('security.allow-outbound-http')) { 178 - $form->appendChild( 179 - id(new AphrontFormTextControl()) 180 - ->setLabel(pht('Import Gravatar')) 181 - ->setName('gravatar') 182 - ->setError($e_image) 183 - ->setCaption(pht('Enter gravatar email address'))); 184 - } 96 + ->setValue($user->getTranslation())); 185 97 186 98 $form->appendChild( 187 99 id(new AphrontFormSubmitControl()) 188 - ->setValue(pht('Save')) 189 - ->addCancelButton('/p/'.$user->getUsername().'/')); 100 + ->setValue(pht('Save'))); 190 101 191 102 $header = new PhabricatorHeaderView(); 192 103 $header->setHeader(pht('Edit Profile Details'));
+13
webroot/rsrc/css/application/people/people-profile.css
··· 1 + /** 2 + * @provides people-profile-css 3 + */ 4 + 5 + form.profile-image-form { 6 + display: inline-block; 7 + margin: 0 8px 8px 0; 8 + } 9 + 10 + button.profile-image-button { 11 + padding: 4px; 12 + margin: 0; 13 + }