2013-07-10 01:23:54 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
final class PhabricatorPeopleProfilePictureController
|
|
|
|
extends PhabricatorPeopleController {
|
|
|
|
|
|
|
|
private $id;
|
|
|
|
|
|
|
|
public function shouldRequireAdmin() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function willProcessRequest(array $data) {
|
|
|
|
$this->id = $data['id'];
|
|
|
|
}
|
|
|
|
|
|
|
|
public function processRequest() {
|
|
|
|
$request = $this->getRequest();
|
|
|
|
$viewer = $request->getUser();
|
|
|
|
|
|
|
|
$user = id(new PhabricatorPeopleQuery())
|
|
|
|
->setViewer($viewer)
|
|
|
|
->withIDs(array($this->id))
|
|
|
|
->requireCapabilities(
|
|
|
|
array(
|
|
|
|
PhabricatorPolicyCapability::CAN_VIEW,
|
|
|
|
PhabricatorPolicyCapability::CAN_EDIT,
|
|
|
|
))
|
|
|
|
->executeOne();
|
|
|
|
if (!$user) {
|
|
|
|
return new Aphront404Response();
|
|
|
|
}
|
|
|
|
|
|
|
|
$profile_uri = '/p/'.$user->getUsername().'/';
|
|
|
|
|
|
|
|
$supported_formats = PhabricatorFile::getTransformableImageFormats();
|
|
|
|
$e_file = true;
|
|
|
|
$errors = array();
|
|
|
|
|
|
|
|
if ($request->isFormPost()) {
|
|
|
|
$phid = $request->getStr('phid');
|
|
|
|
$is_default = false;
|
|
|
|
if ($phid == PhabricatorPHIDConstants::PHID_VOID) {
|
|
|
|
$phid = null;
|
|
|
|
$is_default = true;
|
|
|
|
} else if ($phid) {
|
|
|
|
$file = id(new PhabricatorFileQuery())
|
|
|
|
->setViewer($viewer)
|
|
|
|
->withPHIDs(array($phid))
|
|
|
|
->executeOne();
|
|
|
|
} else {
|
|
|
|
if ($request->getFileExists('picture')) {
|
|
|
|
$file = PhabricatorFile::newFromPHPUpload(
|
|
|
|
$_FILES['picture'],
|
|
|
|
array(
|
|
|
|
'authorPHID' => $viewer->getPHID(),
|
|
|
|
));
|
|
|
|
} else {
|
|
|
|
$e_file = pht('Required');
|
|
|
|
$errors[] = pht(
|
|
|
|
'You must choose a file when uploading a new profile picture.');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$errors && !$is_default) {
|
|
|
|
if (!$file->isTransformableImage()) {
|
|
|
|
$e_file = pht('Not Supported');
|
|
|
|
$errors[] = pht(
|
|
|
|
'This server only supports these image formats: %s.',
|
|
|
|
implode(', ', $supported_formats));
|
|
|
|
} else {
|
|
|
|
$xformer = new PhabricatorImageTransformer();
|
|
|
|
$xformed = $xformer->executeProfileTransform(
|
|
|
|
$file,
|
|
|
|
$width = 50,
|
|
|
|
$min_height = 50,
|
|
|
|
$max_height = 50);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$errors) {
|
2013-07-16 22:54:14 +02:00
|
|
|
if ($is_default) {
|
|
|
|
$user->setProfileImagePHID(null);
|
|
|
|
} else {
|
|
|
|
$user->setProfileImagePHID($xformed->getPHID());
|
2013-10-07 02:07:55 +02:00
|
|
|
$xformed->attachToObject($viewer, $user->getPHID());
|
2013-07-16 22:54:14 +02:00
|
|
|
}
|
2013-07-10 01:23:54 +02:00
|
|
|
$user->save();
|
|
|
|
return id(new AphrontRedirectResponse())->setURI($profile_uri);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$title = pht('Edit Profile Picture');
|
|
|
|
$crumbs = $this->buildApplicationCrumbs();
|
2013-12-19 02:47:34 +01:00
|
|
|
$crumbs->addTextCrumb($user->getUsername(), $profile_uri);
|
|
|
|
$crumbs->addTextCrumb($title);
|
2013-07-10 01:23:54 +02:00
|
|
|
|
2013-08-26 20:53:11 +02:00
|
|
|
$form = id(new PHUIFormLayoutView())
|
2013-07-10 01:23:54 +02:00
|
|
|
->setUser($viewer);
|
|
|
|
|
|
|
|
$default_image = PhabricatorFile::loadBuiltin($viewer, 'profile.png');
|
|
|
|
|
|
|
|
$images = array();
|
|
|
|
|
|
|
|
$current = $user->getProfileImagePHID();
|
2013-07-12 20:20:18 +02:00
|
|
|
$has_current = false;
|
2013-07-10 01:23:54 +02:00
|
|
|
if ($current) {
|
|
|
|
$files = id(new PhabricatorFileQuery())
|
|
|
|
->setViewer($viewer)
|
|
|
|
->withPHIDs(array($current))
|
|
|
|
->execute();
|
|
|
|
if ($files) {
|
|
|
|
$file = head($files);
|
|
|
|
if ($file->isTransformableImage()) {
|
2013-07-12 20:20:18 +02:00
|
|
|
$has_current = true;
|
2013-07-10 01:23:54 +02:00
|
|
|
$images[$current] = array(
|
|
|
|
'uri' => $file->getBestURI(),
|
|
|
|
'tip' => pht('Current Picture'),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try to add external account images for any associated external accounts.
|
|
|
|
$accounts = id(new PhabricatorExternalAccountQuery())
|
|
|
|
->setViewer($viewer)
|
|
|
|
->withUserPHIDs(array($user->getPHID()))
|
|
|
|
->needImages(true)
|
Introduce CAN_EDIT for ExternalAccount, and make CAN_VIEW more liberal
Summary:
Fixes T3732. Ref T1205. Ref T3116.
External accounts (like emails used as identities, Facebook accounts, LDAP accounts, etc.) are stored in "ExternalAccount" objects.
Currently, we have a very restrictive `CAN_VIEW` policy for ExternalAccounts, to add an extra layer of protection to make sure users can't use them in unintended ways. For example, it would be bad if a user could link their Phabricator account to a Facebook account without proper authentication. All of the controllers which do sensitive things have checks anyway, but a restrictive CAN_VIEW provided an extra layer of protection. Se T3116 for some discussion.
However, this means that when grey/external users take actions (via email, or via applications like Legalpad) other users can't load the account handles and can't see anything about the actor (they just see "Restricted External Account" or similar).
Balancing these concerns is mostly about not making a huge mess while doing it. This seems like a reasonable approach:
- Add `CAN_EDIT` on these objects.
- Make that very restricted, but open up `CAN_VIEW`.
- Require `CAN_EDIT` any time we're going to do something authentication/identity related.
This is slightly easier to get wrong (forget CAN_EDIT) than other approaches, but pretty simple, and we always have extra checks in place anyway -- this is just a safety net.
I'm not quite sure how we should identify external accounts, so for now we're just rendering "Email User" or similar -- clearly not a bug, but not identifying. We can figure out what to render in the long term elsewhere.
Test Plan:
- Viewed external accounts.
- Linked an external account.
- Refreshed an external account.
- Edited profile picture.
- Viewed sessions panel.
- Published a bunch of stuff to Asana/JIRA.
- Legalpad signature page now shows external accounts.
{F171595}
Reviewers: chad, btrahan
Reviewed By: btrahan
Subscribers: epriestley
Maniphest Tasks: T3732, T1205, T3116
Differential Revision: https://secure.phabricator.com/D9767
2014-07-10 19:18:10 +02:00
|
|
|
->requireCapabilities(
|
|
|
|
array(
|
|
|
|
PhabricatorPolicyCapability::CAN_VIEW,
|
|
|
|
PhabricatorPolicyCapability::CAN_EDIT,
|
|
|
|
))
|
2013-07-10 01:23:54 +02:00
|
|
|
->execute();
|
|
|
|
|
|
|
|
foreach ($accounts as $account) {
|
|
|
|
$file = $account->getProfileImageFile();
|
|
|
|
if ($account->getProfileImagePHID() != $file->getPHID()) {
|
|
|
|
// This is a default image, just skip it.
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$provider = PhabricatorAuthProvider::getEnabledProviderByKey(
|
|
|
|
$account->getProviderKey());
|
|
|
|
if ($provider) {
|
|
|
|
$tip = pht('Picture From %s', $provider->getProviderName());
|
|
|
|
} else {
|
|
|
|
$tip = pht('Picture From External Account');
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($file->isTransformableImage()) {
|
|
|
|
$images[$file->getPHID()] = array(
|
|
|
|
'uri' => $file->getBestURI(),
|
|
|
|
'tip' => $tip,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try to add Gravatar images for any email addresses associated with the
|
|
|
|
// account.
|
|
|
|
if (PhabricatorEnv::getEnvConfig('security.allow-outbound-http')) {
|
|
|
|
$emails = id(new PhabricatorUserEmail())->loadAllWhere(
|
|
|
|
'userPHID = %s ORDER BY address',
|
2014-04-02 21:05:34 +02:00
|
|
|
$user->getPHID());
|
2013-07-10 01:23:54 +02:00
|
|
|
|
|
|
|
$futures = array();
|
|
|
|
foreach ($emails as $email_object) {
|
|
|
|
$email = $email_object->getAddress();
|
|
|
|
|
|
|
|
$hash = md5(strtolower(trim($email)));
|
|
|
|
$uri = id(new PhutilURI("https://secure.gravatar.com/avatar/{$hash}"))
|
|
|
|
->setQueryParams(
|
|
|
|
array(
|
|
|
|
'size' => 200,
|
|
|
|
'default' => '404',
|
|
|
|
'rating' => 'x',
|
|
|
|
));
|
|
|
|
$futures[$email] = new HTTPSFuture($uri);
|
|
|
|
}
|
|
|
|
|
|
|
|
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
|
|
|
foreach (Futures($futures) as $email => $future) {
|
|
|
|
try {
|
|
|
|
list($body) = $future->resolvex();
|
|
|
|
$file = PhabricatorFile::newFromFileData(
|
|
|
|
$body,
|
|
|
|
array(
|
|
|
|
'name' => 'profile-gravatar',
|
|
|
|
'ttl' => (60 * 60 * 4),
|
|
|
|
));
|
|
|
|
if ($file->isTransformableImage()) {
|
|
|
|
$images[$file->getPHID()] = array(
|
|
|
|
'uri' => $file->getBestURI(),
|
|
|
|
'tip' => pht('Gravatar for %s', $email),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} catch (Exception $ex) {
|
|
|
|
// Just continue.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
unset($unguarded);
|
|
|
|
}
|
|
|
|
|
|
|
|
$images[PhabricatorPHIDConstants::PHID_VOID] = array(
|
|
|
|
'uri' => $default_image->getBestURI(),
|
|
|
|
'tip' => pht('Default Picture'),
|
|
|
|
);
|
|
|
|
|
|
|
|
require_celerity_resource('people-profile-css');
|
|
|
|
Javelin::initBehavior('phabricator-tooltips', array());
|
|
|
|
|
|
|
|
$buttons = array();
|
|
|
|
foreach ($images as $phid => $spec) {
|
|
|
|
$button = javelin_tag(
|
|
|
|
'button',
|
|
|
|
array(
|
|
|
|
'class' => 'grey profile-image-button',
|
|
|
|
'sigil' => 'has-tooltip',
|
|
|
|
'meta' => array(
|
|
|
|
'tip' => $spec['tip'],
|
|
|
|
'size' => 300,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
phutil_tag(
|
|
|
|
'img',
|
|
|
|
array(
|
|
|
|
'height' => 50,
|
|
|
|
'width' => 50,
|
|
|
|
'src' => $spec['uri'],
|
|
|
|
)));
|
|
|
|
|
|
|
|
$button = array(
|
|
|
|
phutil_tag(
|
|
|
|
'input',
|
|
|
|
array(
|
|
|
|
'type' => 'hidden',
|
|
|
|
'name' => 'phid',
|
|
|
|
'value' => $phid,
|
|
|
|
)),
|
|
|
|
$button);
|
|
|
|
|
|
|
|
$button = phabricator_form(
|
|
|
|
$viewer,
|
|
|
|
array(
|
|
|
|
'class' => 'profile-image-form',
|
|
|
|
'method' => 'POST',
|
|
|
|
),
|
|
|
|
$button);
|
|
|
|
|
|
|
|
$buttons[] = $button;
|
|
|
|
}
|
|
|
|
|
2013-07-12 20:20:18 +02:00
|
|
|
if ($has_current) {
|
|
|
|
$form->appendChild(
|
|
|
|
id(new AphrontFormMarkupControl())
|
|
|
|
->setLabel(pht('Current Picture'))
|
|
|
|
->setValue(array_shift($buttons)));
|
|
|
|
}
|
2013-07-10 01:23:54 +02:00
|
|
|
|
|
|
|
$form->appendChild(
|
|
|
|
id(new AphrontFormMarkupControl())
|
|
|
|
->setLabel(pht('Use Picture'))
|
2013-07-12 20:20:18 +02:00
|
|
|
->setValue($buttons));
|
2013-07-10 01:23:54 +02:00
|
|
|
|
2013-09-25 20:23:29 +02:00
|
|
|
$form_box = id(new PHUIObjectBoxView())
|
2013-08-27 00:45:58 +02:00
|
|
|
->setHeaderText($title)
|
2014-01-10 18:17:37 +01:00
|
|
|
->setFormErrors($errors)
|
2013-08-27 00:45:58 +02:00
|
|
|
->setForm($form);
|
2013-07-10 01:23:54 +02:00
|
|
|
|
|
|
|
$upload_form = id(new AphrontFormView())
|
2014-04-02 21:05:34 +02:00
|
|
|
->setUser($viewer)
|
2013-07-10 01:23:54 +02:00
|
|
|
->setEncType('multipart/form-data')
|
|
|
|
->appendChild(
|
|
|
|
id(new AphrontFormFileControl())
|
|
|
|
->setName('picture')
|
|
|
|
->setLabel(pht('Upload Picture'))
|
|
|
|
->setError($e_file)
|
|
|
|
->setCaption(
|
|
|
|
pht('Supported formats: %s', implode(', ', $supported_formats))))
|
|
|
|
->appendChild(
|
|
|
|
id(new AphrontFormSubmitControl())
|
|
|
|
->addCancelButton($profile_uri)
|
|
|
|
->setValue(pht('Upload Picture')));
|
|
|
|
|
2013-09-25 20:23:29 +02:00
|
|
|
$upload_box = id(new PHUIObjectBoxView())
|
2013-08-27 00:45:58 +02:00
|
|
|
->setHeaderText(pht('Upload New Picture'))
|
|
|
|
->setForm($upload_form);
|
|
|
|
|
2013-07-10 01:23:54 +02:00
|
|
|
return $this->buildApplicationPage(
|
|
|
|
array(
|
|
|
|
$crumbs,
|
2013-08-27 00:45:58 +02:00
|
|
|
$form_box,
|
|
|
|
$upload_box,
|
2013-07-10 01:23:54 +02:00
|
|
|
),
|
|
|
|
array(
|
|
|
|
'title' => $title,
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|