diff --git a/conf/default.conf.php b/conf/default.conf.php index a5bb05d2fa..cf02b03b6a 100644 --- a/conf/default.conf.php +++ b/conf/default.conf.php @@ -1,46 +1,48 @@ <?php /* * Copyright 2011 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ return array( // The root URI which Phabricator is installed on. // Example: "http://phabricator.example.com/" 'phabricator.base-uri' => null, // 'phabricator.csrf-key' => '0b7ec0592e0a2829d8b71df2fa269b2c6172eca3', // -- Facebook --------------------------------------------------------------- // Can users use Facebook credentials to login to Phabricator? 'facebook.auth-enabled' => false, // The Facebook "Application ID" to use for Facebook API access. 'facebook.application-id' => null, // The Facebook "Application Secret" to use for Facebook API access. 'facebook.application-secret' => null, 'recaptcha.public-key' => null, 'recaptcha.private-key' => null, + 'user.default-profile-image-phid' => 'PHID-FILE-f57aaefce707fc4060ef', + ); diff --git a/src/applications/auth/controller/facebookauth/PhabricatorFacebookAuthController.php b/src/applications/auth/controller/facebookauth/PhabricatorFacebookAuthController.php index 7f86928722..818e291d21 100644 --- a/src/applications/auth/controller/facebookauth/PhabricatorFacebookAuthController.php +++ b/src/applications/auth/controller/facebookauth/PhabricatorFacebookAuthController.php @@ -1,275 +1,293 @@ <?php /* * Copyright 2011 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class PhabricatorFacebookAuthController extends PhabricatorAuthController { public function shouldRequireLogin() { return false; } public function processRequest() { $auth_enabled = PhabricatorEnv::getEnvConfig('facebook.auth-enabled'); if (!$auth_enabled) { return new Aphront400Response(); } $diagnose_auth = '<a href="/facebook-auth/diagnose/" class="button green">'. 'Diagnose Facebook Auth Problems'. '</a>'; $request = $this->getRequest(); if ($request->getStr('error')) { $view = new AphrontRequestFailureView(); $view->setHeader('Facebook Auth Failed'); $view->appendChild( '<p>'. '<strong>Description:</strong> '. phutil_escape_html($request->getStr('error_description')). '</p>'); $view->appendChild( '<p>'. '<strong>Error:</strong> '. phutil_escape_html($request->getStr('error')). '</p>'); $view->appendChild( '<p>'. '<strong>Error Reason:</strong> '. phutil_escape_html($request->getStr('error_reason')). '</p>'); $view->appendChild( '<div class="aphront-failure-continue">'. '<a href="/login/" class="button">Continue</a>'. '</div>'); return $this->buildStandardPageResponse( $view, array( 'title' => 'Facebook Auth Failed', )); } $token = $request->getStr('token'); if (!$token) { $app_id = PhabricatorEnv::getEnvConfig('facebook.application-id'); $app_secret = PhabricatorEnv::getEnvConfig('facebook.application-secret'); $redirect_uri = PhabricatorEnv::getURI('/facebook-auth/'); $code = $request->getStr('code'); $auth_uri = new PhutilURI( "https://graph.facebook.com/oauth/access_token"); $auth_uri->setQueryParams( array( 'client_id' => $app_id, 'redirect_uri' => $redirect_uri, 'client_secret' => $app_secret, 'code' => $code, )); $response = @file_get_contents($auth_uri); if ($response === false) { $view = new AphrontRequestFailureView(); $view->setHeader('Facebook Auth Failed'); $view->appendChild( '<p>Unable to authenticate with Facebook. There are several reasons '. 'this might happen:</p>'. '<ul>'. '<li>Phabricator may be configured with the wrong Application '. 'Secret; or</li>'. '<li>the Facebook OAuth access token may have expired; or</li>'. '<li>Facebook may have revoked authorization for the '. 'Application; or</li>'. '<li>Facebook may be having technical problems.</li>'. '</ul>'. '<p>You can try again, or login using another method.</p>'); $view->appendChild( '<div class="aphront-failure-continue">'. $diagnose_auth. '<a href="/login/" class="button">Continue</a>'. '</div>'); return $this->buildStandardPageResponse( $view, array( 'title' => 'Facebook Auth Failed', )); } $data = array(); parse_str($response, $data); $token = $data['access_token']; } $user_json = @file_get_contents('https://graph.facebook.com/me?access_token='.$token); $user_data = json_decode($user_json, true); $user_id = $user_data['id']; $known_user = id(new PhabricatorUser()) ->loadOneWhere('facebookUID = %d', $user_id); if ($known_user) { $session_key = $known_user->establishSession('web'); $request->setCookie('phusr', $known_user->getUsername()); $request->setCookie('phsid', $session_key); return id(new AphrontRedirectResponse()) ->setURI('/'); } + + $known_email = id(new PhabricatorUser()) + ->loadOneWhere('email = %s', $user_data['email']); + if ($known_email) { + if ($known_email->getFacebookUID()) { + throw new Exception( + "The email associated with the Facebook account you just logged in ". + "with is already associated with another Phabricator account which ". + "is, in turn, associated with a Facebook account different from ". + "the one you just logged in with."); + } + $known_email->setFacebookUID($user_id); + $session_key = $known_email->establishSession('web'); + $request->setCookie('phusr', $known_email->getUsername()); + $request->setCookie('phsid', $session_key); + return id(new AphrontRedirectResponse()) + ->setURI('/'); + } $current_user = $this->getRequest()->getUser(); if ($current_user->getPHID()) { if ($current_user->getFacebookUID() && $current_user->getFacebookUID() != $user_id) { throw new Exception( "Your account is already associated with a Facebook user ID other ". "than the one you just logged in with...?"); } if ($request->isFormPost()) { $current_user->setFacebookUID($user_id); $current_user->save(); // TODO: ship them back to the 'account' page or whatever? return id(new AphrontRedirectResponse()) ->setURI('/'); } $ph_account = $current_user->getUsername(); $fb_account = phutil_escape_html($user_data['name']); $form = new AphrontFormView(); $form ->addHiddenInput('token', $token) ->setUser($request->getUser()) ->setAction('/facebook-auth/') ->appendChild( '<p class="aphront-form-view-instructions">Do you want to link your '. "existing Phabricator account (<strong>{$ph_account}</strong>) ". "with your Facebook account (<strong>{$fb_account}</strong>) so ". "you can login with Facebook?") ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Link Accounts') ->addCancelButton('/login/')); $panel = new AphrontPanelView(); $panel->setHeader('Link Facebook Account'); $panel->setWidth(AphrontPanelView::WIDTH_FORM); $panel->appendChild($form); return $this->buildStandardPageResponse( $panel, array( 'title' => 'Link Facebook Account', )); } $errors = array(); $e_username = true; $user = new PhabricatorUser(); $matches = null; if (preg_match('@/([a-zA-Z0-9]+)$@', $user_data['link'], $matches)) { $user->setUsername($matches[1]); } if ($request->isFormPost()) { $username = $request->getStr('username'); if (!strlen($username)) { $e_username = 'Required'; $errors[] = 'Username is required.'; } else if (!preg_match('/^[a-zA-Z0-9]+$/', $username, $matches)) { $e_username = 'Invalid'; $errors[] = 'Username may only contain letters and numbers.'; } $user->setUsername($username); $user->setFacebookUID($user_id); $user->setEmail($user_data['email']); if (!$errors) { $image = @file_get_contents('https://graph.facebook.com/me/picture?access_token='.$token); $file = PhabricatorFile::newFromFileData( $image, array( 'name' => 'fbprofile.jpg' )); $user->setProfileImagePHID($file->getPHID()); $user->setRealName($user_data['name']); try { $user->save(); $session_key = $user->establishSession('web'); $request->setCookie('phusr', $user->getUsername()); $request->setCookie('phsid', $session_key); return id(new AphrontRedirectResponse())->setURI('/'); } catch (AphrontQueryDuplicateKeyException $exception) { $key = $exception->getDuplicateKey(); if ($key == 'userName') { $e_username = 'Duplicate'; $errors[] = 'That username is not unique.'; } else { throw $exception; } } } } $error_view = null; if ($errors) { $error_view = new AphrontErrorView(); $error_view->setTitle('Facebook Auth Failed'); $error_view->setErrors($errors); } $form = new AphrontFormView(); $form ->addHiddenInput('token', $token) ->setUser($request->getUser()) ->setAction('/facebook-auth/') ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Username') ->setName('username') ->setValue($user->getUsername()) ->setError($e_username)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Create Account')); $panel = new AphrontPanelView(); $panel->setHeader('Create New Account'); $panel->setWidth(AphrontPanelView::WIDTH_FORM); $panel->appendChild($form); return $this->buildStandardPageResponse( array( $error_view, $panel, ), array( 'title' => 'Create New Account', )); } } diff --git a/src/applications/auth/controller/login/PhabricatorLoginController.php b/src/applications/auth/controller/login/PhabricatorLoginController.php index dc871ccb44..774a26d7e9 100644 --- a/src/applications/auth/controller/login/PhabricatorLoginController.php +++ b/src/applications/auth/controller/login/PhabricatorLoginController.php @@ -1,134 +1,135 @@ <?php /* * Copyright 2011 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class PhabricatorLoginController extends PhabricatorAuthController { public function shouldRequireLogin() { return false; } public function processRequest() { $request = $this->getRequest(); $error = false; $username = $request->getCookie('phusr'); if ($request->isFormPost()) { $username = $request->getStr('username'); $user = id(new PhabricatorUser())->loadOneWhere( 'username = %s', $username); $okay = false; if ($user) { if ($user->comparePassword($request->getStr('password'))) { $session_key = $user->establishSession('web'); $request->setCookie('phusr', $user->getUsername()); $request->setCookie('phsid', $session_key); return id(new AphrontRedirectResponse()) ->setURI('/'); } } if (!$okay) { $request->clearCookie('phusr'); $request->clearCookie('phsid'); } $error = true; } $error_view = null; if ($error) { $error_view = new AphrontErrorView(); $error_view->setTitle('Bad username/password.'); } $form = new AphrontFormView(); $form ->setUser($request->getUser()) ->setAction('/login/') ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Username/Email') ->setName('username') ->setValue($username)) ->appendChild( id(new AphrontFormPasswordControl()) ->setLabel('Password') ->setName('password') ->setCaption( '<a href="/login/email/">Forgot your password? / Email Login</a>')) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Login')); $panel = new AphrontPanelView(); $panel->setHeader('Phabricator Login'); $panel->setWidth(AphrontPanelView::WIDTH_FORM); +// $panel->setCreateButton('Register New Account', '/login/register/'); $panel->appendChild($form); $fbauth_enabled = PhabricatorEnv::getEnvConfig('facebook.auth-enabled'); if ($fbauth_enabled) { $auth_uri = new PhutilURI("https://www.facebook.com/dialog/oauth"); $user = $request->getUser(); $redirect_uri = PhabricatorEnv::getURI('/facebook-auth/'); $app_id = PhabricatorEnv::getEnvConfig('facebook.application-id'); // TODO: In theory we should use 'state' to prevent CSRF, but the total // effect of the CSRF attack is that an attacker can cause a user to login // to Phabricator if they're already logged into Facebook. This does not // seem like the most severe threat in the world, and generating CSRF for // logged-out users is vaugely tricky. $facebook_auth = new AphrontFormView(); $facebook_auth ->setAction($auth_uri) ->addHiddenInput('client_id', $app_id) ->addHiddenInput('redirect_uri', $redirect_uri) ->addHiddenInput('scope', 'email') ->setUser($request->getUser()) ->setMethod('GET') ->appendChild( '<p class="aphront-form-instructions">Login or register for '. 'Phabricator using your Facebook account.</p>') ->appendChild( id(new AphrontFormSubmitControl()) ->setValue("Login with Facebook \xC2\xBB")); - $panel->appendChild('<br /><h1>Login with Facebook</h1>'); + $panel->appendChild('<br /><h1>Login or Register with Facebook</h1>'); $panel->appendChild($facebook_auth); } return $this->buildStandardPageResponse( array( $error_view, $panel, ), array( 'title' => 'Login', )); } } diff --git a/src/applications/differential/view/revisioncomment/DifferentialRevisionCommentView.php b/src/applications/differential/view/revisioncomment/DifferentialRevisionCommentView.php index 31e07687ff..cb9bab5604 100644 --- a/src/applications/differential/view/revisioncomment/DifferentialRevisionCommentView.php +++ b/src/applications/differential/view/revisioncomment/DifferentialRevisionCommentView.php @@ -1,90 +1,96 @@ <?php /* * Copyright 2011 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ final class DifferentialRevisionCommentView extends AphrontView { private $comment; private $handles; private $markupEngine; public function setComment($comment) { $this->comment = $comment; return $this; } public function setHandles(array $handles) { $this->handles = $handles; return $this; } public function setMarkupEngine($markup_engine) { $this->markupEngine = $markup_engine; return $this; } public function render() { require_celerity_resource('phabricator-remarkup-css'); require_celerity_resource('differential-revision-comment-css'); $comment = $this->comment; $action = $comment->getAction(); $action_class = 'differential-comment-action-'.phutil_escape_html($action); $date = date('F jS, Y g:i:s A', $comment->getDateCreated()); - $author = $comment->getAuthorPHID(); - $author = $this->handles[$author]->renderLink(); + $author = $this->handles[$comment->getAuthorPHID()]; + $author_link = $author->renderLink(); $verb = DifferentialAction::getActionPastTenseVerb($comment->getAction()); $verb = phutil_escape_html($verb); $content = $comment->getContent(); if (strlen(rtrim($content))) { - $title = "{$author} {$verb} this revision:"; + $title = "{$author_link} {$verb} this revision:"; $content = '<div class="phabricator-remarkup">'. $this->markupEngine->markupText($content). '</div>'; } else { $title = null; $content = '<div class="differential-comment-nocontent">'. - "<p>{$author} {$verb} this revision.</p>". + "<p>{$author_link} {$verb} this revision.</p>". '</div>'; } + + $background = null; + $uri = $author->getImageURI(); + if ($uri) { + $background = "background-image: url('{$uri}');"; + } return '<div class="differential-comment '.$action_class.'">'. '<div class="differential-comment-head">'. '<div class="differential-comment-date">'.$date.'</div>'. '<div class="differential-comment-title">'.$title.'</div>'. '</div>'. - '<div class="differential-comment-body">'. + '<div class="differential-comment-body" style="'.$background.'">'. '<div class="differential-comment-core">'. '<div class="differential-comment-content">'. $content. '</div>'. '</div>'. '</div>'. '</div>'; } } diff --git a/src/applications/files/controller/view/PhabricatorFileViewController.php b/src/applications/files/controller/view/PhabricatorFileViewController.php index 361a7cb98b..38e9b92654 100644 --- a/src/applications/files/controller/view/PhabricatorFileViewController.php +++ b/src/applications/files/controller/view/PhabricatorFileViewController.php @@ -1,112 +1,113 @@ <?php /* * Copyright 2011 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class PhabricatorFileViewController extends PhabricatorFileController { private $phid; private $view; public function willProcessRequest(array $data) { $this->phid = $data['phid']; $this->view = $data['view']; } public function processRequest() { $file = id(new PhabricatorFile())->loadOneWhere( 'phid = %s', $this->phid); if (!$file) { return new Aphront404Response(); } switch ($this->view) { case 'download': case 'view': $data = $file->loadFileData(); $response = new AphrontFileResponse(); $response->setContent($data); $response->setMimeType($file->getMimeType()); if ($this->view == 'download') { $response->setDownload($file->getName()); } return $response; default: break; } $form = new AphrontFormView(); $form->setAction('/file/view/'.$file->getPHID().'/'); + $form->setUser($this->getRequest()->getUser()); $form ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Name') ->setName('name') ->setValue($file->getName())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('PHID') ->setName('phid') ->setValue($file->getPHID())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Created') ->setName('created') ->setValue(date('Y-m-d g:i:s A', $file->getDateCreated()))) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Mime Type') ->setName('mime') ->setValue($file->getMimeType())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Size') ->setName('size') ->setValue($file->getByteSize().' bytes')) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Engine') ->setName('storageEngine') ->setValue($file->getStorageEngine())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Format') ->setName('storageFormat') ->setValue($file->getStorageFormat())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Handle') ->setName('storageHandle') ->setValue($file->getStorageHandle())) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('View File')); $panel = new AphrontPanelView(); $panel->setHeader('File Info - '.$file->getName()); $panel->appendChild($form); $panel->setWidth(AphrontPanelView::WIDTH_FORM); return $this->buildStandardPageResponse( array($panel), array( 'title' => 'File Info - '.$file->getName(), )); } } diff --git a/src/applications/people/storage/user/PhabricatorUser.php b/src/applications/people/storage/user/PhabricatorUser.php index abef89f7a4..a8180333c1 100644 --- a/src/applications/people/storage/user/PhabricatorUser.php +++ b/src/applications/people/storage/user/PhabricatorUser.php @@ -1,145 +1,151 @@ <?php /* * Copyright 2011 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class PhabricatorUser extends PhabricatorUserDAO { const PHID_TYPE = 'USER'; protected $phid; protected $userName; protected $realName; protected $email; protected $passwordSalt; protected $passwordHash; protected $facebookUID; protected $profileImagePHID; private $sessionKey; + + public function getProfileImagePHID() { + return nonempty( + $this->profileImagePHID, + PhabricatorEnv::getEnvConfig('user.default-profile-image-phid')); + } public function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID(self::PHID_TYPE); } public function setPassword($password) { $this->setPasswordSalt(md5(mt_rand())); $hash = $this->hashPassword($password); $this->setPasswordHash($hash); return $this; } public function comparePassword($password) { $password = $this->hashPassword($password); return ($password === $this->getPasswordHash()); } private function hashPassword($password) { $password = $this->getUsername(). $password. $this->getPHID(). $this->getPasswordSalt(); for ($ii = 0; $ii < 1000; $ii++) { $password = md5($password); } return $password; } const CSRF_CYCLE_FREQUENCY = 3600; const CSRF_TOKEN_LENGTH = 16; const EMAIL_CYCLE_FREQUENCY = 86400; const EMAIL_TOKEN_LENGTH = 24; public function getCSRFToken($offset = 0) { return $this->generateToken( time() + (self::CSRF_CYCLE_FREQUENCY * $offset), self::CSRF_CYCLE_FREQUENCY, PhabricatorEnv::getEnvConfig('phabricator.csrf-key'), self::CSRF_TOKEN_LENGTH); } public function validateCSRFToken($token) { for ($ii = -1; $ii <= 1; $ii++) { $valid = $this->getCSRFToken($ii); if ($token == $valid) { return true; } } return false; } private function generateToken($epoch, $frequency, $key, $len) { $time_block = floor($epoch / $frequency); $vec = $this->getPHID().$this->passwordHash.$key.$time_block; return substr(sha1($vec), 0, $len); } public function establishSession($session_type) { $conn_w = $this->establishConnection('w'); $urandom = fopen('/dev/urandom', 'r'); if (!$urandom) { throw new Exception("Failed to open /dev/urandom!"); } $entropy = fread($urandom, 20); if (strlen($entropy) != 20) { throw new Exception("Failed to read /dev/urandom!"); } $session_key = sha1($entropy); queryfx( $conn_w, 'INSERT INTO phabricator_session '. '(userPHID, type, sessionKey, sessionStart)'. ' VALUES '. '(%s, %s, %s, UNIX_TIMESTAMP()) '. 'ON DUPLICATE KEY UPDATE '. 'sessionKey = VALUES(sessionKey), '. 'sessionStart = VALUES(sessionStart)', $this->getPHID(), $session_type, $session_key); return $session_key; } public function generateEmailToken($offset = 0) { return $this->generateToken( time() + ($offset * self::EMAIL_CYCLE_FREQUENCY), self::EMAIL_CYCLE_FREQUENCY, PhabricatorEnv::getEnvConfig('phabricator.csrf-key').$this->getEmail(), self::EMAIL_TOKEN_LENGTH); } public function validateEmailToken($token) { for ($ii = -1; $ii <= 1; $ii++) { $valid = $this->generateEmailToken($ii); if ($token == $valid) { return true; } } return false; } } diff --git a/src/applications/phid/handle/PhabricatorObjectHandle.php b/src/applications/phid/handle/PhabricatorObjectHandle.php index 5ef9dd861e..bd50f48a4a 100644 --- a/src/applications/phid/handle/PhabricatorObjectHandle.php +++ b/src/applications/phid/handle/PhabricatorObjectHandle.php @@ -1,81 +1,91 @@ <?php /* * Copyright 2011 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class PhabricatorObjectHandle { private $uri; private $phid; private $type; private $name; private $email; + private $imageURI; public function setURI($uri) { $this->uri = $uri; return $this; } public function getURI() { return $this->uri; } public function setPHID($phid) { $this->phid = $phid; return $this; } public function getPHID() { return $this->phid; } public function setName($name) { $this->name = $name; return $this; } public function getName() { return $this->name; } public function setType($type) { $this->type = $type; return $this; } public function getType() { return $this->type; } public function setEmail($email) { $this->email = $email; return $this; } public function getEmail() { return $this->email; } + + public function setImageURI($uri) { + $this->imageURI = $uri; + return $this; + } + + public function getImageURI() { + return $this->imageURI; + } public function renderLink() { return phutil_render_tag( 'a', array( 'href' => $this->getURI(), ), phutil_escape_html($this->getName())); } } diff --git a/src/applications/phid/handle/data/PhabricatorObjectHandleData.php b/src/applications/phid/handle/data/PhabricatorObjectHandleData.php index 49f05781c9..411c93ea9d 100644 --- a/src/applications/phid/handle/data/PhabricatorObjectHandleData.php +++ b/src/applications/phid/handle/data/PhabricatorObjectHandleData.php @@ -1,136 +1,142 @@ <?php /* * Copyright 2011 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class PhabricatorObjectHandleData { const TYPE_UNKNOWN = '????'; private $phids; public function __construct(array $phids) { $this->phids = $phids; } public function loadHandles() { $types = array(); foreach ($this->phids as $phid) { $type = $this->lookupType($phid); $types[$type][] = $phid; } $handles = array(); foreach ($types as $type => $phids) { switch ($type) { case 'USER': $class = 'PhabricatorUser'; PhutilSymbolLoader::loadClass($class); $object = newv($class, array()); $users = $object->loadAllWhere('phid IN (%Ls)', $phids); $users = mpull($users, null, 'getPHID'); foreach ($phids as $phid) { $handle = new PhabricatorObjectHandle(); $handle->setPHID($phid); if (empty($users[$phid])) { $handle->setType(self::TYPE_UNKNOWN); $handle->setName('Unknown User'); } else { $user = $users[$phid]; $handle->setType($type); $handle->setName($user->getUsername()); $handle->setURI('/p/'.$user->getUsername().'/'); $handle->setEmail($user->getEmail()); + + $img_phid = $user->getProfileImagePHID(); + if ($img_phid) { + $handle->setImageURI( + PhabricatorFileURI::getViewURIForPHID($img_phid)); + } } $handles[$phid] = $handle; } break; case 'MLST': $class = 'PhabricatorMetaMTAMailingList'; PhutilSymbolLoader::loadClass($class); $object = newv($class, array()); $lists = $object->loadAllWhere('phid IN (%Ls)', $phids); $lists = mpull($lists, null, 'getPHID'); foreach ($phids as $phid) { $handle = new PhabricatorObjectHandle(); $handle->setPHID($phid); if (empty($lists[$phid])) { $handle->setType(self::TYPE_UNKNOWN); $handle->setName('Unknown Mailing List'); } else { $list = $lists[$phid]; $handle->setType($type); $handle->setEmail($list->getEmail()); $handle->setName($list->getName()); $handle->setURI($list->getURI()); } $handles[$phid] = $handle; } break; case 'FILE': $class = 'PhabricatorFile'; PhutilSymbolLoader::loadClass($class); $object = newv($class, array()); $files = $object->loadAllWhere('phid IN (%Ls)', $phids); $files = mpull($files, null, 'getPHID'); foreach ($phids as $phid) { $handle = new PhabricatorObjectHandle(); $handle->setPHID($phid); if (empty($files[$phid])) { $handle->setType(self::TYPE_UNKNOWN); $handle->setName('Unknown File'); } else { $file = $files[$phid]; $handle->setType($type); $handle->setName($file->getName()); $handle->setURI($file->getViewURI()); } $handles[$phid] = $handle; } break; default: foreach ($phids as $phid) { $handle = new PhabricatorObjectHandle(); $handle->setType($type); $handle->setPHID($phid); $handle->setName('Unknown Object'); $handles[$phid] = $handle; } break; } } return $handles; } private function lookupType($phid) { $matches = null; if (preg_match('/^PHID-([^-]{4})-/', $phid, $matches)) { return $matches[1]; } return self::TYPE_UNKNOWN; } }