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

DifferentialRevisionEditor

+1446 -42
+12
src/__phutil_library_map__.php
··· 64 64 'ConduitAPI_user_find_Method' => 'applications/conduit/method/user/find', 65 65 'ConduitException' => 'applications/conduit/protocol/exception', 66 66 'DifferentialAction' => 'applications/differential/constants/action', 67 + 'DifferentialCCWelcomeMail' => 'applications/differential/mail/ccwelcome', 67 68 'DifferentialChangeType' => 'applications/differential/constants/changetype', 68 69 'DifferentialChangeset' => 'applications/differential/storage/changeset', 69 70 'DifferentialChangesetDetailView' => 'applications/differential/view/changesetdetailview', ··· 73 74 'DifferentialController' => 'applications/differential/controller/base', 74 75 'DifferentialDAO' => 'applications/differential/storage/base', 75 76 'DifferentialDiff' => 'applications/differential/storage/diff', 77 + 'DifferentialDiffContentMail' => 'applications/differential/mail/diffcontent', 76 78 'DifferentialDiffProperty' => 'applications/differential/storage/diffproperty', 77 79 'DifferentialDiffTableOfContentsView' => 'applications/differential/view/difftableofcontents', 78 80 'DifferentialDiffViewController' => 'applications/differential/controller/diffview', 81 + 'DifferentialFeedbackMail' => 'applications/differential/mail/feedback', 79 82 'DifferentialHunk' => 'applications/differential/storage/hunk', 80 83 'DifferentialLintStatus' => 'applications/differential/constants/lintstatus', 84 + 'DifferentialMail' => 'applications/differential/mail/base', 85 + 'DifferentialNewDiffMail' => 'applications/differential/mail/newdiff', 86 + 'DifferentialReviewRequestMail' => 'applications/differential/mail/reviewrequest', 81 87 'DifferentialRevision' => 'applications/differential/storage/revision', 82 88 'DifferentialRevisionControlSystem' => 'applications/differential/constants/revisioncontrolsystem', 83 89 'DifferentialRevisionEditController' => 'applications/differential/controller/revisionedit', 90 + 'DifferentialRevisionEditor' => 'applications/differential/editor/revision', 84 91 'DifferentialRevisionListController' => 'applications/differential/controller/revisionlist', 85 92 'DifferentialRevisionStatus' => 'applications/differential/constants/revisionstatus', 86 93 'DifferentialUnitStatus' => 'applications/differential/constants/unitstatus', ··· 206 213 'ConduitAPI_differential_setdiffproperty_Method' => 'ConduitAPIMethod', 207 214 'ConduitAPI_file_upload_Method' => 'ConduitAPIMethod', 208 215 'ConduitAPI_user_find_Method' => 'ConduitAPIMethod', 216 + 'DifferentialCCWelcomeMail' => 'DifferentialReviewRequestMail', 209 217 'DifferentialChangeset' => 'DifferentialDAO', 210 218 'DifferentialChangesetDetailView' => 'AphrontView', 211 219 'DifferentialChangesetListView' => 'AphrontView', ··· 213 221 'DifferentialController' => 'PhabricatorController', 214 222 'DifferentialDAO' => 'PhabricatorLiskDAO', 215 223 'DifferentialDiff' => 'DifferentialDAO', 224 + 'DifferentialDiffContentMail' => 'DifferentialMail', 216 225 'DifferentialDiffProperty' => 'DifferentialDAO', 217 226 'DifferentialDiffTableOfContentsView' => 'AphrontView', 218 227 'DifferentialDiffViewController' => 'DifferentialController', 228 + 'DifferentialFeedbackMail' => 'DifferentialMail', 219 229 'DifferentialHunk' => 'DifferentialDAO', 230 + 'DifferentialNewDiffMail' => 'DifferentialReviewRequestMail', 231 + 'DifferentialReviewRequestMail' => 'DifferentialMail', 220 232 'DifferentialRevision' => 'DifferentialDAO', 221 233 'DifferentialRevisionEditController' => 'DifferentialController', 222 234 'DifferentialRevisionListController' => 'DifferentialController',
+4
src/applications/conduit/controller/api/PhabricatorConduitAPIController.php
··· 19 19 class PhabricatorConduitAPIController 20 20 extends PhabricatorConduitController { 21 21 22 + public function shouldRequireLogin() { 23 + return false; 24 + } 25 + 22 26 private $method; 23 27 24 28 public function willProcessRequest(array $data) {
+2
src/applications/differential/controller/diffview/DifferentialDiffViewController.php
··· 41 41 $action_form = new AphrontFormView(); 42 42 $action_form 43 43 ->setAction('/differential/revision/edit/') 44 + ->addHiddenInput('diffID', $diff->getID()) 45 + ->addHiddenInput('viaDiffView', 1) 44 46 ->appendChild( 45 47 id(new AphrontFormSelectControl()) 46 48 ->setLabel('Attach To')
+84 -37
src/applications/differential/controller/revisionedit/DifferentialRevisionEditController.php
··· 34 34 } else { 35 35 $revision = new DifferentialRevision(); 36 36 } 37 - /* 38 - $e_name = true; 37 + 38 + $request = $this->getRequest(); 39 + $diff_id = $request->getInt('diffID'); 40 + if ($diff_id) { 41 + $diff = id(new DifferentialDiff())->load($diff_id); 42 + if (!$diff) { 43 + return new Aphront404Response(); 44 + } 45 + if ($diff->getRevisionID()) { 46 + // TODO: Redirect? 47 + throw new Exception("This diff is already attached to a revision!"); 48 + } 49 + } else { 50 + $diff = null; 51 + } 52 + 53 + $e_title = true; 54 + $e_testplan = true; 39 55 $errors = array(); 40 56 41 - $request = $this->getRequest(); 42 - if ($request->isFormPost()) { 43 - $category->setName($request->getStr('name')); 44 - $category->setSequence($request->getStr('sequence')); 57 + if ($request->isFormPost() && !$request->getStr('viaDiffView')) { 58 + $revision->setTitle($request->getStr('title')); 59 + $revision->setSummary($request->getStr('summary')); 60 + $revision->setTestPlan($request->getStr('testplan')); 61 + $revision->setBlameRevision($request->getStr('blame')); 62 + $revision->setRevertPlan($request->getStr('revert')); 63 + 64 + if (!strlen(trim($revision->getTitle()))) { 65 + $errors[] = 'You must provide a title.'; 66 + $e_title = 'Required'; 67 + } 45 68 46 - if (!strlen($category->getName())) { 47 - $errors[] = 'Category name is required.'; 48 - $e_name = 'Required'; 69 + if (!strlen(trim($revision->getTestPlan()))) { 70 + $errors[] = 'You must provide a test plan.'; 71 + $e_testplan = 'Required'; 72 + } 73 + 74 + $user_phid = $request->getUser()->getPHID(); 75 + 76 + if (in_array($user_phid, $request->getArr('reviewers'))) { 77 + $errors[] = 'You may not review your own revision.'; 49 78 } 50 79 51 80 if (!$errors) { 52 - $category->save(); 53 - return id(new AphrontRedirectResponse()) 54 - ->setURI('/directory/category/'); 81 + $editor = new DifferentialRevisionEditor($revision, $user_phid); 82 + if ($diff) { 83 + $editor->addDiff($diff, $request->getStr('comments')); 84 + } 85 + $editor->setCCPHIDs($request->getArr('cc')); 86 + $editor->setReviewers($request->getArr('reviewers')); 87 + $editor->save(); 88 + 89 + $response = id(new AphrontRedirectResponse()) 90 + ->setURI('/D'.$revision->getID()); 55 91 } 92 + 93 + $reviewer_phids = $request->getArr('reviewers'); 94 + $cc_phids = $request->getArr('cc'); 95 + } else { 96 + // $reviewer_phids = $revision->getReviewers(); 97 + // $cc_phids = $revision->getCCPHIDs(); 98 + $reviewer_phids = array(); 99 + $cc_phids = array(); 56 100 } 57 101 58 - $error_view = null; 59 - if ($errors) { 60 - $error_view = id(new AphrontErrorView()) 61 - ->setTitle('Form Errors') 62 - ->setErrors($errors); 102 + $form = new AphrontFormView(); 103 + if ($diff) { 104 + $form->addHiddenInput('diffID', $diff->getID()); 63 105 } 64 - */ 65 - $e_name = true; 66 - $e_testplan = true; 67 106 68 - $form = new AphrontFormView(); 69 107 if ($revision->getID()) { 70 108 $form->setAction('/differential/revision/edit/'.$revision->getID().'/'); 71 109 } else { 72 110 $form->setAction('/differential/revision/edit/'); 73 111 } 74 112 75 - $reviewer_map = array( 76 - 1 => 'A Zebra', 77 - 2 => 'Pie Messenger', 78 - ); 113 + $error_view = null; 114 + if ($errors) { 115 + $error_view = id(new AphrontErrorView()) 116 + ->setTitle('Form Errors') 117 + ->setErrors($errors); 118 + } 79 119 80 120 $form 81 121 ->appendChild( 82 122 id(new AphrontFormTextAreaControl()) 83 - ->setLabel('Name') 84 - ->setName('name') 85 - ->setValue($revision->getName()) 86 - ->setError($e_name)) 123 + ->setLabel('Title') 124 + ->setName('title') 125 + ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT) 126 + ->setValue($revision->getTitle()) 127 + ->setError($e_title)) 87 128 ->appendChild( 88 129 id(new AphrontFormTextAreaControl()) 89 130 ->setLabel('Summary') ··· 99 140 id(new AphrontFormTokenizerControl()) 100 141 ->setLabel('Reviewers') 101 142 ->setName('reviewers') 102 - ->setDatasource('/typeahead/common/user/') 143 + ->setDatasource('/typeahead/common/users/') 103 144 ->setValue($reviewer_map)) 104 145 ->appendChild( 105 146 id(new AphrontFormTokenizerControl()) 106 147 ->setLabel('CC') 107 148 ->setName('cc') 108 - ->setDatasource('/typeahead/common/user/') 149 + ->setDatasource('/typeahead/common/mailable/') 109 150 ->setValue($reviewer_map)) 110 151 ->appendChild( 111 152 id(new AphrontFormTextControl()) ··· 116 157 'change fixes.')) 117 158 ->appendChild( 118 159 id(new AphrontFormTextAreaControl()) 119 - ->setLabel('Revert') 160 + ->setLabel('Revert Plan') 120 161 ->setName('revert') 121 162 ->setValue($revision->getRevertPlan()) 122 - ->setCaption('Special steps required to safely revert this change.')) 123 - ->appendChild( 124 - id(new AphrontFormSubmitControl()) 125 - ->setValue('Save')); 163 + ->setCaption('Special steps required to safely revert this change.')); 164 + 165 + $submit = id(new AphrontFormSubmitControl()) 166 + ->setValue('Save'); 167 + if ($diff) { 168 + $submit->addCancelButton('/differential/diff/'.$diff->getID().'/'); 169 + } else { 170 + $submit->addCancelButton('/D'.$revision->getID()); 171 + } 172 + 173 + $form->appendChild($submit); 126 174 127 175 $panel = new AphrontPanelView(); 128 176 if ($revision->getID()) { ··· 134 182 $panel->appendChild($form); 135 183 $panel->setWidth(AphrontPanelView::WIDTH_FORM); 136 184 137 - $error_view = null; 138 185 return $this->buildStandardPageResponse( 139 186 array($error_view, $panel), 140 187 array(
+5
src/applications/differential/controller/revisionedit/__init__.php
··· 7 7 8 8 9 9 phutil_require_module('phabricator', 'aphront/response/404'); 10 + phutil_require_module('phabricator', 'aphront/response/redirect'); 10 11 phutil_require_module('phabricator', 'applications/differential/controller/base'); 12 + phutil_require_module('phabricator', 'applications/differential/editor/revision'); 13 + phutil_require_module('phabricator', 'applications/differential/storage/diff'); 11 14 phutil_require_module('phabricator', 'applications/differential/storage/revision'); 12 15 phutil_require_module('phabricator', 'view/form/base'); 13 16 phutil_require_module('phabricator', 'view/form/control/submit'); 17 + phutil_require_module('phabricator', 'view/form/control/textarea'); 18 + phutil_require_module('phabricator', 'view/form/error'); 14 19 phutil_require_module('phabricator', 'view/layout/panel'); 15 20 16 21 phutil_require_module('phutil', 'utils');
+550
src/applications/differential/editor/revision/DifferentialRevisionEditor.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2011 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + /** 20 + * Handle major edit operations to DifferentialRevision -- adding and removing 21 + * reviewers, diffs, and CCs. Unlike simple edits, these changes trigger 22 + * complicated email workflows. 23 + */ 24 + class DifferentialRevisionEditor { 25 + 26 + protected $revision; 27 + protected $actorPHID; 28 + 29 + protected $cc = null; 30 + protected $reviewers = null; 31 + protected $diff; 32 + protected $comments; 33 + protected $silentUpdate; 34 + 35 + public function __construct(DifferentialRevision $revision, $actor_phid) { 36 + $this->revision = $revision; 37 + $this->actorPHID = $actor_phid; 38 + } 39 + 40 + /* 41 + public static function newRevisionFromRawMessageWithDiff( 42 + DifferentialRawMessage $message, 43 + Diff $diff, 44 + $user) { 45 + 46 + if ($message->getRevisionID()) { 47 + throw new Exception( 48 + "The provided commit message is already associated with a ". 49 + "Differential revision."); 50 + } 51 + 52 + if ($message->getReviewedByNames()) { 53 + throw new Exception( 54 + "The provided commit message contains a 'Reviewed By:' field."); 55 + } 56 + 57 + $revision = new DifferentialRevision(); 58 + $revision->setPHID($revision->generatePHID()); 59 + 60 + $revision->setOwnerID($user); 61 + $revision->setStatus(DifferentialRevisionStatus::NEEDS_REVIEW); 62 + $revision->attachReviewers(array()); 63 + $revision->attachCCPHIDs(array()); 64 + 65 + $editor = new DifferentialRevisionEditor($revision, $user); 66 + 67 + self::copyFields($editor, $revision, $message, $user); 68 + 69 + $editor->addDiff($diff, null); 70 + $editor->save(); 71 + 72 + return $revision; 73 + } 74 + 75 + public static function newRevisionFromConduitWithDiff( 76 + array $fields, 77 + Diff $diff, 78 + $user) { 79 + 80 + $revision = new DifferentialRevision(); 81 + $revision->setPHID($revision->generatePHID()); 82 + 83 + $revision->setOwnerID($user); 84 + $revision->setStatus(DifferentialRevisionStatus::NEEDS_REVIEW); 85 + $revision->attachReviewers(array()); 86 + $revision->attachCCPHIDs(array()); 87 + 88 + $editor = new DifferentialRevisionEditor($revision, $user); 89 + 90 + $editor->copyFieldFromConduit($fields); 91 + 92 + $editor->addDiff($diff, null); 93 + $editor->save(); 94 + 95 + return $revision; 96 + } 97 + 98 + 99 + public static function copyFields( 100 + DifferentialRevisionEditor $editor, 101 + DifferentialRevision $revision, 102 + DifferentialRawMessage $message, 103 + $user) { 104 + 105 + $revision->setName($message->getTitle()); 106 + $revision->setSummary($message->getSummary()); 107 + $revision->setTestPlan($message->getTestPlan()); 108 + $revision->setSVNBlameRevision($message->getBlameRevision()); 109 + $revision->setRevert($message->getRevertPlan()); 110 + $revision->setPlatformImpact($message->getPlatformImpact()); 111 + $revision->setBugzillaID($message->getBugzillaID()); 112 + 113 + $editor->setReviewers($message->getReviewerPHIDs()); 114 + $editor->setCCPHIDs($message->getCCPHIDs()); 115 + } 116 + 117 + public function copyFieldFromConduit(array $fields) { 118 + 119 + $user = $this->actorPHID; 120 + $revision = $this->revision; 121 + 122 + $revision->setName($fields['title']); 123 + $revision->setSummary($fields['summary']); 124 + $revision->setTestPlan($fields['testPlan']); 125 + $revision->setSVNBlameRevision($fields['blameRevision']); 126 + $revision->setRevert($fields['revertPlan']); 127 + $revision->setPlatformImpact($fields['platformImpact']); 128 + $revision->setBugzillaID($fields['bugzillaID']); 129 + 130 + $this->setReviewers($fields['reviewerGUIDs']); 131 + $this->setCCPHIDs($fields['ccGUIDs']); 132 + } 133 + */ 134 + 135 + public function getRevision() { 136 + return $this->revision; 137 + } 138 + 139 + public function setReviewers(array $reviewers) { 140 + $this->reviewers = $reviewers; 141 + return $this; 142 + } 143 + 144 + public function setCCPHIDs(array $cc) { 145 + $this->cc = $cc; 146 + return $this; 147 + } 148 + 149 + public function addDiff(DifferentialDiff $diff, $comments) { 150 + if ($diff->getRevisionID() && 151 + $diff->getRevisionID() != $this->getRevision()->getID()) { 152 + $diff_id = (int)$diff->getID(); 153 + $targ_id = (int)$this->getRevision()->getID(); 154 + $real_id = (int)$diff->getRevisionID(); 155 + throw new Exception( 156 + "Can not attach diff #{$diff_id} to Revision D{$targ_id}, it is ". 157 + "already attached to D{$real_id}."); 158 + } 159 + $this->diff = $diff; 160 + $this->comments = $comments; 161 + return $this; 162 + } 163 + 164 + protected function getDiff() { 165 + return $this->diff; 166 + } 167 + 168 + protected function getComments() { 169 + return $this->comments; 170 + } 171 + 172 + protected function getActorPHID() { 173 + return $this->actorPHID; 174 + } 175 + 176 + public function isNewRevision() { 177 + return !$this->getRevision()->getID(); 178 + } 179 + 180 + /** 181 + * A silent update does not trigger Herald rules or send emails. This is used 182 + * for auto-amends at commit time. 183 + */ 184 + public function setSilentUpdate($silent) { 185 + $this->silentUpdate = $silent; 186 + return $this; 187 + } 188 + 189 + public function save() { 190 + $revision = $this->getRevision(); 191 + 192 + // TODO 193 + // $revision->openTransaction(); 194 + 195 + $is_new = $this->isNewRevision(); 196 + if ($is_new) { 197 + // These fields aren't nullable; set them to sensible defaults if they 198 + // haven't been configured. We're just doing this so we can generate an 199 + // ID for the revision if we don't have one already. 200 + $revision->setLineCount(0); 201 + if ($revision->getStatus() === null) { 202 + $revision->setStatus(DifferentialRevisionStatus::NEEDS_REVIEW); 203 + } 204 + if ($revision->getTitle() === null) { 205 + $revision->setTitle('Untitled Revision'); 206 + } 207 + if ($revision->getOwnerPHID() === null) { 208 + $revision->setOwnerPHID($this->getActorPHID()); 209 + } 210 + 211 + $revision->save(); 212 + } 213 + 214 + $revision->loadRelationships(); 215 + 216 + if ($this->reviewers === null) { 217 + $this->reviewers = $revision->getReviewers(); 218 + } 219 + 220 + if ($this->cc === null) { 221 + $this->cc = $revision->getCCPHIDs(); 222 + } 223 + 224 + // We're going to build up three dictionaries: $add, $rem, and $stable. The 225 + // $add dictionary has added reviewers/CCs. The $rem dictionary has 226 + // reviewers/CCs who have been removed, and the $stable array is 227 + // reviewers/CCs who haven't changed. We're going to send new reviewers/CCs 228 + // a different ("welcome") email than we send stable reviewers/CCs. 229 + 230 + $old = array( 231 + 'rev' => array_fill_keys($revision->getReviewers(), true), 232 + 'ccs' => array_fill_keys($revision->getCCPHIDs(), true), 233 + ); 234 + 235 + $diff = $this->getDiff(); 236 + 237 + $xscript_header = null; 238 + $xscript_uri = null; 239 + 240 + $new = array( 241 + 'rev' => array_fill_keys($this->reviewers, true), 242 + 'ccs' => array_fill_keys($this->cc, true), 243 + ); 244 + 245 + 246 + $rem_ccs = array(); 247 + if ($diff) { 248 + $diff->setRevisionID($revision->getID()); 249 + $revision->setLineCount($diff->getLineCount()); 250 + 251 + // TODO! 252 + // $revision->setRepositoryID($diff->getRepositoryID()); 253 + 254 + /* 255 + $iface = new DifferentialRevisionHeraldable($revision); 256 + $iface->setExplicitCCs($new['ccs']); 257 + $iface->setExplicitReviewers($new['rev']); 258 + $iface->setForbiddenCCs($revision->getForbiddenCCPHIDs()); 259 + $iface->setForbiddenReviewers($revision->getForbiddenReviewers()); 260 + $iface->setDiff($diff); 261 + 262 + $xscript = HeraldEngine::loadAndApplyRules($iface); 263 + $xscript_uri = $xscript->getURI(); 264 + $xscript_phid = $xscript->getPHID(); 265 + $xscript_header = $xscript->getXHeraldRulesHeader(); 266 + 267 + 268 + $sub = array( 269 + 'rev' => array(), 270 + 'ccs' => $iface->getCCsAddedByHerald(), 271 + ); 272 + $rem_ccs = $iface->getCCsRemovedByHerald(); 273 + */ 274 + // TODO! 275 + $sub = array( 276 + 'rev' => array(), 277 + 'ccs' => array(), 278 + ); 279 + 280 + 281 + } else { 282 + $sub = array( 283 + 'rev' => array(), 284 + 'ccs' => array(), 285 + ); 286 + } 287 + 288 + // Remove any CCs which are prevented by Herald rules. 289 + $sub['ccs'] = array_diff_key($sub['ccs'], $rem_ccs); 290 + $new['ccs'] = array_diff_key($new['ccs'], $rem_ccs); 291 + 292 + $add = array(); 293 + $rem = array(); 294 + $stable = array(); 295 + foreach (array('rev', 'ccs') as $key) { 296 + $add[$key] = array(); 297 + if ($new[$key] !== null) { 298 + $add[$key] += array_diff_key($new[$key], $old[$key]); 299 + } 300 + $add[$key] += array_diff_key($sub[$key], $old[$key]); 301 + 302 + $combined = $sub[$key]; 303 + if ($new[$key] !== null) { 304 + $combined += $new[$key]; 305 + } 306 + $rem[$key] = array_diff_key($old[$key], $combined); 307 + 308 + $stable[$key] = array_diff_key($old[$key], $add[$key] + $rem[$key]); 309 + } 310 + 311 + self::removeReviewers( 312 + $revision, 313 + array_keys($rem['rev']), 314 + $this->actorPHID); 315 + self::addReviewers( 316 + $revision, 317 + array_keys($add['rev']), 318 + $this->actorPHID); 319 + 320 + // Add the owner to the relevant set of users so they get a copy of the 321 + // email. 322 + if (!$this->silentUpdate) { 323 + if ($is_new) { 324 + $add['rev'][$this->getActorPHID()] = true; 325 + } else { 326 + $stable['rev'][$this->getActorPHID()] = true; 327 + } 328 + } 329 + 330 + $mail = array(); 331 + 332 + $changesets = null; 333 + $feedback = null; 334 + if ($diff) { 335 + $changesets = $diff->loadChangesets(); 336 + // TODO: move to DifferentialFeedbackEditor 337 + if (!$is_new) { 338 + // TODO 339 + // $feedback = $this->createFeedback(); 340 + } 341 + if ($feedback) { 342 + $mail[] = id(new DifferentialNewDiffMail( 343 + $revision, 344 + $this->getActorPHID(), 345 + $changesets)) 346 + ->setIsFirstMailAboutRevision($is_new) 347 + ->setIsFirstMailToRecipients($is_new) 348 + ->setComments($this->getComments()) 349 + ->setToPHIDs(array_keys($stable['rev'])) 350 + ->setCCPHIDs(array_keys($stable['ccs'])); 351 + } 352 + 353 + // Save the changes we made above. 354 + 355 + // TODO 356 + // $diff->setDescription(substr($this->getComments(), 0, 80)); 357 + $diff->save(); 358 + 359 + // An updated diff should require review, as long as it's not committed 360 + // or accepted. The "accepted" status is "sticky" to encourage courtesy 361 + // re-diffs after someone accepts with minor changes/suggestions. 362 + 363 + $status = $revision->getStatus(); 364 + if ($status != DifferentialRevisionStatus::COMMITTED && 365 + $status != DifferentialRevisionStatus::ACCEPTED) { 366 + $revision->setStatus(DifferentialRevisionStatus::NEEDS_REVIEW); 367 + } 368 + 369 + } else { 370 + $diff = $revision->getActiveDiff(); 371 + if ($diff) { 372 + $changesets = id(new DifferentialChangeset())->loadAllWithDiff($diff); 373 + } else { 374 + $changesets = array(); 375 + } 376 + } 377 + 378 + $revision->save(); 379 + 380 + // TODO 381 + // $revision->saveTransaction(); 382 + 383 + $event = array( 384 + 'revision_id' => $revision->getID(), 385 + 'PHID' => $revision->getPHID(), 386 + 'action' => $is_new ? 'create' : 'update', 387 + 'actor' => $this->getActorPHID(), 388 + ); 389 + 390 + // TODO 391 + // id(new ToolsTimelineEvent('difx', fb_json_encode($event)))->record(); 392 + 393 + if ($this->silentUpdate) { 394 + return; 395 + } 396 + 397 + // TODO 398 + // $revision->attachReviewers(array_keys($new['rev'])); 399 + // $revision->attachCCPHIDs(array_keys($new['ccs'])); 400 + 401 + if ($add['ccs'] || $rem['ccs']) { 402 + foreach (array_keys($add['ccs']) as $id) { 403 + if (empty($new['ccs'][$id])) { 404 + $reason_phid = 'TODO';//$xscript_phid; 405 + } else { 406 + $reason_phid = $this->getActorPHID(); 407 + } 408 + self::addCCPHID($revision, $id, $reason_phid); 409 + } 410 + foreach (array_keys($rem['ccs']) as $id) { 411 + if (empty($new['ccs'][$id])) { 412 + $reason_phid = $this->getActorPHID(); 413 + } else { 414 + $reason_phid = 'TODO';//$xscript_phid; 415 + } 416 + self::removeCCPHID($revision, $id, $reason_phid); 417 + } 418 + } 419 + 420 + if ($add['rev']) { 421 + $message = id(new DifferentialNewDiffMail( 422 + $revision, 423 + $this->getActorPHID(), 424 + $changesets)) 425 + ->setIsFirstMailAboutRevision($is_new) 426 + ->setIsFirstMailToRecipients(true) 427 + ->setToPHIDs(array_keys($add['rev'])); 428 + 429 + if ($is_new) { 430 + // The first time we send an email about a revision, put the CCs in 431 + // the "CC:" field of the same "Review Requested" email that reviewers 432 + // get, so you don't get two initial emails if you're on a list that 433 + // is CC'd. 434 + $message->setCCPHIDs(array_keys($add['ccs'])); 435 + } 436 + 437 + $mail[] = $message; 438 + } 439 + 440 + // If you were added as a reviewer and a CC, just give you the reviewer 441 + // email. We could go to greater lengths to prevent this, but there's 442 + // bunch of stuff with list subscriptions anyway. You can still get two 443 + // emails, but only if a revision is updated and you are added as a reviewer 444 + // at the same time a list you are on is added as a CC, which is rare and 445 + // reasonable. 446 + $add['ccs'] = array_diff_key($add['ccs'], $add['rev']); 447 + 448 + if (!$is_new && $add['ccs']) { 449 + $mail[] = id(new DifferentialCCWelcomeMail( 450 + $revision, 451 + $this->getActorPHID(), 452 + $changesets)) 453 + ->setIsFirstMailToRecipients(true) 454 + ->setToPHIDs(array_keys($add['ccs'])); 455 + } 456 + 457 + foreach ($mail as $message) { 458 + // TODO 459 + // $message->setHeraldTranscriptURI($xscript_uri); 460 + // $message->setXHeraldRulesHeader($xscript_header); 461 + $message->send(); 462 + } 463 + } 464 + 465 + public function addCCPHID( 466 + DifferentialRevision $revision, 467 + $phid, 468 + $reason_phid) { 469 + self::alterCCPHID($revision, $phid, true, $reason_phid); 470 + } 471 + 472 + public function removeCCPHID( 473 + DifferentialRevision $revision, 474 + $phid, 475 + $reason_phid) { 476 + self::alterCCPHID($revision, $phid, false, $reason_phid); 477 + } 478 + 479 + protected static function alterCCPHID( 480 + DifferentialRevision $revision, 481 + $phid, 482 + $add, 483 + $reason_phid) { 484 + /* 485 + $relationship = new DifferentialRelationship(); 486 + $relationship->setRevisionID($revision->getID()); 487 + $relationship->setRelation(DifferentialRelationship::RELATION_SUBSCRIBED); 488 + $relationship->setRelatedPHID($phid); 489 + $relationship->setForbidden(!$add); 490 + $relationship->setReasonPHID($reason_phid); 491 + $relationship->replace(); 492 + */ 493 + } 494 + 495 + 496 + public static function addReviewers( 497 + DifferentialRevision $revision, 498 + array $reviewer_ids, 499 + $reason_phid) { 500 + /* 501 + foreach ($reviewer_ids as $reviewer_id) { 502 + $relationship = new DifferentialRelationship(); 503 + $relationship->setRevisionID($revision->getID()); 504 + $relationship->setRelatedPHID($reviewer_id); 505 + $relationship->setForbidden(false); 506 + $relationship->setReasonPHID($reason_phid); 507 + $relationship->setRelation(DifferentialRelationship::RELATION_REVIEWER); 508 + $relationship->replace(); 509 + } 510 + */ 511 + } 512 + 513 + public static function removeReviewers( 514 + DifferentialRevision $revision, 515 + array $reviewer_ids, 516 + $reason_phid) { 517 + /* 518 + if (!$reviewer_ids) { 519 + return; 520 + } 521 + 522 + foreach ($reviewer_ids as $reviewer_id) { 523 + $relationship = new DifferentialRelationship(); 524 + $relationship->setRevisionID($revision->getID()); 525 + $relationship->setRelatedPHID($reviewer_id); 526 + $relationship->setForbidden(true); 527 + $relationship->setReasonPHID($reason_phid); 528 + $relationship->setRelation(DifferentialRelationship::RELATION_REVIEWER); 529 + $relationship->replace(); 530 + } 531 + */ 532 + } 533 + 534 + /* 535 + protected function createFeedback() { 536 + $revision = $this->getRevision(); 537 + $feedback = id(new DifferentialFeedback()) 538 + ->setUserID($this->getActorPHID()) 539 + ->setRevision($revision) 540 + ->setContent($this->getComments()) 541 + ->setAction('update'); 542 + 543 + $feedback->save(); 544 + 545 + return $feedback; 546 + } 547 + */ 548 + 549 + } 550 +
+17
src/applications/differential/editor/revision/__init__.php
··· 1 + <?php 2 + /** 3 + * This file is automatically generated. Lint this module to rebuild it. 4 + * @generated 5 + */ 6 + 7 + 8 + 9 + phutil_require_module('phabricator', 'applications/differential/constants/revisionstatus'); 10 + phutil_require_module('phabricator', 'applications/differential/mail/ccwelcome'); 11 + phutil_require_module('phabricator', 'applications/differential/mail/newdiff'); 12 + phutil_require_module('phabricator', 'applications/differential/storage/changeset'); 13 + 14 + phutil_require_module('phutil', 'utils'); 15 + 16 + 17 + phutil_require_source('DifferentialRevisionEditor.php');
+311
src/applications/differential/mail/base/DifferentialMail.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2011 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + abstract class DifferentialMail { 20 + 21 + const SUBJECT_PREFIX = '[Differential]'; 22 + 23 + protected $to = array(); 24 + protected $cc = array(); 25 + 26 + protected $actorName; 27 + protected $actorID; 28 + 29 + protected $revision; 30 + protected $feedback; 31 + protected $changesets; 32 + protected $inlineComments; 33 + protected $isFirstMailAboutRevision; 34 + protected $isFirstMailToRecipients; 35 + protected $heraldTranscriptURI; 36 + protected $heraldRulesHeader; 37 + 38 + public function getActorName() { 39 + return $this->actorName; 40 + } 41 + 42 + public function setActorName($actor_name) { 43 + $this->actorName = $actor_name; 44 + return $this; 45 + } 46 + 47 + abstract protected function renderSubject(); 48 + abstract protected function renderBody(); 49 + 50 + public function setXHeraldRulesHeader($header) { 51 + $this->heraldRulesHeader = $header; 52 + return $this; 53 + } 54 + 55 + public function send() { 56 + $to_phids = $this->getToPHIDs(); 57 + if (!$to_phids) { 58 + throw new Exception('No "To:" users provided!'); 59 + } 60 + 61 + $message_id = $this->getMessageID(); 62 + 63 + $cc_phids = $this->getCCPHIDs(); 64 + $subject = $this->buildSubject(); 65 + $body = $this->buildBody(); 66 + 67 + $mail = new PhabricatorMetaMTAMail(); 68 + if ($this->getActorID()) { 69 + $mail->setFrom($this->getActorID()); 70 + $mail->setReplyTo($this->getReplyHandlerEmailAddress()); 71 + } else { 72 + $mail->setFrom($this->getReplyHandlerEmailAddress()); 73 + } 74 + 75 + $mail 76 + ->addTos($to_phids) 77 + ->addCCs($cc_phids) 78 + ->setSubject($subject) 79 + ->setBody($body) 80 + ->setIsHTML($this->shouldMarkMailAsHTML()) 81 + ->addHeader('Thread-Topic', $this->getRevision()->getTitle()) 82 + ->addHeader('Thread-Index', $this->generateThreadIndex()); 83 + 84 + if ($this->isFirstMailAboutRevision()) { 85 + $mail->addHeader('Message-ID', $message_id); 86 + } else { 87 + $mail->addHeader('In-Reply-To', $message_id); 88 + $mail->addHeader('References', $message_id); 89 + } 90 + 91 + if ($this->heraldRulesHeader) { 92 + $mail->addHeader('X-Herald-Rules', $this->heraldRulesHeader); 93 + } 94 + 95 + $mail->setRelatedPHID($this->getRevision()->getPHID()); 96 + 97 + // Save this to the MetaMTA queue for later delivery to the MTA. 98 + $mail->save(); 99 + } 100 + 101 + protected function buildSubject() { 102 + return self::SUBJECT_PREFIX.' '.$this->renderSubject(); 103 + } 104 + 105 + protected function shouldMarkMailAsHTML() { 106 + return false; 107 + } 108 + 109 + protected function buildBody() { 110 + 111 + $actions = array(); 112 + $body = $this->renderBody(); 113 + $body .= <<<EOTEXT 114 + 115 + ACTIONS 116 + Reply to comment, or !accept, !reject, !abandon, !resign, or !showdiff. 117 + 118 + EOTEXT; 119 + 120 + if ($this->getHeraldTranscriptURI() && $this->isFirstMailToRecipients()) { 121 + $xscript_uri = $this->getHeraldTranscriptURI(); 122 + $body .= <<<EOTEXT 123 + 124 + MANAGE HERALD RULES 125 + http://todo.com/herald/ 126 + 127 + WHY DID I GET THIS EMAIL? 128 + {$xscript_uri} 129 + 130 + Tip: use the X-Herald-Rules header to filter Herald messages in your client. 131 + 132 + EOTEXT; 133 + } 134 + 135 + return $body; 136 + } 137 + 138 + protected function getReplyHandlerEmailAddress() { 139 + // TODO 140 + $phid = $this->getRevision()->getPHID(); 141 + $server = 'todo.example.com'; 142 + return "differential+{$phid}@{$server}"; 143 + } 144 + 145 + protected function formatText($text) { 146 + $text = explode("\n", $text); 147 + foreach ($text as &$line) { 148 + $line = rtrim(' '.$line); 149 + } 150 + unset($line); 151 + return implode("\n", $text); 152 + } 153 + 154 + public function setToPHIDs(array $to) { 155 + $this->to = $this->filterContactPHIDs($to); 156 + return $this; 157 + } 158 + 159 + public function setCCPHIDs(array $cc) { 160 + $this->cc = $this->filterContactPHIDs($cc); 161 + return $this; 162 + } 163 + 164 + protected function filterContactPHIDs(array $phids) { 165 + return $phids; 166 + 167 + // TODO: actually do this? 168 + 169 + // Differential revisions use Subscriptions for CCs, so any arbitrary 170 + // PHID can end up CC'd to them. Only try to actually send email PHIDs 171 + // which have ToolsHandle types that are marked emailable. If we don't 172 + // filter here, sending the email will fail. 173 + /* 174 + $handles = array(); 175 + prep(new ToolsHandleData($phids, $handles)); 176 + foreach ($handles as $phid => $handle) { 177 + if (!$handle->isEmailable()) { 178 + unset($handles[$phid]); 179 + } 180 + } 181 + return array_keys($handles); 182 + */ 183 + } 184 + 185 + protected function getToPHIDs() { 186 + return $this->to; 187 + } 188 + 189 + protected function getCCPHIDs() { 190 + return $this->cc; 191 + } 192 + 193 + public function setActorID($actor_id) { 194 + $this->actorID = $actor_id; 195 + return $this; 196 + } 197 + 198 + public function getActorID() { 199 + return $this->actorID; 200 + } 201 + 202 + public function setRevision($revision) { 203 + $this->revision = $revision; 204 + return $this; 205 + } 206 + 207 + public function getRevision() { 208 + return $this->revision; 209 + } 210 + 211 + protected function getMessageID() { 212 + $phid = $this->getRevision()->getPHID(); 213 + // TODO 214 + return "<differential-rev-{$phid}-req@TODO.com>"; 215 + } 216 + 217 + public function setFeedback($feedback) { 218 + $this->feedback = $feedback; 219 + return $this; 220 + } 221 + 222 + public function getFeedback() { 223 + return $this->feedback; 224 + } 225 + 226 + public function setChangesets($changesets) { 227 + $this->changesets = $changesets; 228 + return $this; 229 + } 230 + 231 + public function getChangesets() { 232 + return $this->changesets; 233 + } 234 + 235 + public function setInlineComments(array $inline_comments) { 236 + $this->inlineComments = $inline_comments; 237 + return $this; 238 + } 239 + 240 + public function getInlineComments() { 241 + return $this->inlineComments; 242 + } 243 + 244 + public function renderRevisionDetailLink() { 245 + $uri = $this->getRevisionURI(); 246 + return "REVISION DETAIL\n {$uri}"; 247 + } 248 + 249 + public function getRevisionURI() { 250 + // TODO 251 + return 'http://local.aphront.com/D'.$this->getRevision()->getID(); 252 + } 253 + 254 + public function setIsFirstMailToRecipients($first) { 255 + $this->isFirstMailToRecipients = $first; 256 + return $this; 257 + } 258 + 259 + public function isFirstMailToRecipients() { 260 + return $this->isFirstMailToRecipients; 261 + } 262 + 263 + public function setIsFirstMailAboutRevision($first) { 264 + $this->isFirstMailAboutRevision = $first; 265 + return $this; 266 + } 267 + 268 + public function isFirstMailAboutRevision() { 269 + return $this->isFirstMailAboutRevision; 270 + } 271 + 272 + protected function generateThreadIndex() { 273 + // When threading, Outlook ignores the 'References' and 'In-Reply-To' 274 + // headers that most clients use. Instead, it uses a custom 'Thread-Index' 275 + // header. The format of this header is something like this (from 276 + // camel-exchange-folder.c in Evolution Exchange): 277 + 278 + /* A new post to a folder gets a 27-byte-long thread index. (The value 279 + * is apparently unique but meaningless.) Each reply to a post gets a 280 + * 32-byte-long thread index whose first 27 bytes are the same as the 281 + * parent's thread index. Each reply to any of those gets a 282 + * 37-byte-long thread index, etc. The Thread-Index header contains a 283 + * base64 representation of this value. 284 + */ 285 + 286 + // The specific implementation uses a 27-byte header for the first email 287 + // a recipient receives, and a random 5-byte suffix (32 bytes total) 288 + // thereafter. This means that all the replies are (incorrectly) siblings, 289 + // but it would be very difficult to keep track of the entire tree and this 290 + // gets us reasonable client behavior. 291 + 292 + $base = substr(md5($this->getRevision()->getPHID()), 0, 27); 293 + if (!$this->isFirstMailAboutRevision()) { 294 + // not totally sure, but it seems like outlook orders replies by 295 + // thread-index rather than timestamp, so to get these to show up in the 296 + // right order we use the time as the last 4 bytes. 297 + $base .= ' ' . pack("N", time()); 298 + } 299 + return base64_encode($base); 300 + } 301 + 302 + public function setHeraldTranscriptURI($herald_transcript_uri) { 303 + $this->heraldTranscriptURI = $herald_transcript_uri; 304 + return $this; 305 + } 306 + 307 + public function getHeraldTranscriptURI() { 308 + return $this->heraldTranscriptURI; 309 + } 310 + 311 + }
+12
src/applications/differential/mail/base/__init__.php
··· 1 + <?php 2 + /** 3 + * This file is automatically generated. Lint this module to rebuild it. 4 + * @generated 5 + */ 6 + 7 + 8 + 9 + phutil_require_module('phabricator', 'applications/metamta/storage/mail'); 10 + 11 + 12 + phutil_require_source('DifferentialMail.php');
+39
src/applications/differential/mail/ccwelcome/DifferentialCCWelcomeMail.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2011 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + class DifferentialCCWelcomeMail extends DifferentialReviewRequestMail { 20 + 21 + protected function renderSubject() { 22 + $revision = $this->getRevision(); 23 + return 'Added to CC: '.$revision->getName(); 24 + } 25 + 26 + protected function renderBody() { 27 + 28 + $actor = $this->getActorName(); 29 + $name = $this->getRevision()->getName(); 30 + $body = array(); 31 + 32 + $body[] = "{$actor} added you to the CC list for the revision \"{$name}\"."; 33 + $body[] = null; 34 + 35 + $body[] = $this->renderReviewRequestBody(); 36 + 37 + return implode("\n", $body); 38 + } 39 + }
+12
src/applications/differential/mail/ccwelcome/__init__.php
··· 1 + <?php 2 + /** 3 + * This file is automatically generated. Lint this module to rebuild it. 4 + * @generated 5 + */ 6 + 7 + 8 + 9 + phutil_require_module('phabricator', 'applications/differential/mail/reviewrequest'); 10 + 11 + 12 + phutil_require_source('DifferentialCCWelcomeMail.php');
+35
src/applications/differential/mail/diffcontent/DifferentialDiffContentMail.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2011 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + class DifferentialDiffContentMail extends DifferentialMail { 20 + 21 + protected $content; 22 + 23 + public function __construct(DifferentialRevision $revision, $content) { 24 + $this->setRevision($revision); 25 + $this->content = $content; 26 + } 27 + 28 + protected function renderSubject() { 29 + return "Content: ".$this->getRevision()->getName(); 30 + } 31 + 32 + protected function renderBody() { 33 + return $this->content; 34 + } 35 + }
+12
src/applications/differential/mail/diffcontent/__init__.php
··· 1 + <?php 2 + /** 3 + * This file is automatically generated. Lint this module to rebuild it. 4 + * @generated 5 + */ 6 + 7 + 8 + 9 + phutil_require_module('phabricator', 'applications/differential/mail/base'); 10 + 11 + 12 + phutil_require_source('DifferentialDiffContentMail.php');
+114
src/applications/differential/mail/feedback/DifferentialFeedbackMail.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2011 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + class DifferentialFeedbackMail extends DifferentialMail { 20 + 21 + protected $changedByCommit; 22 + 23 + public function setChangedByCommit($changed_by_commit) { 24 + $this->changedByCommit = $changed_by_commit; 25 + return $this; 26 + } 27 + 28 + public function getChangedByCommit() { 29 + return $this->changedByCommit; 30 + } 31 + 32 + public function __construct( 33 + DifferentialRevision $revision, 34 + $actor_id, 35 + DifferentialFeedback $feedback, 36 + array $changesets, 37 + array $inline_comments) { 38 + 39 + $this->setRevision($revision); 40 + $this->setActorID($actor_id); 41 + $this->setFeedback($feedback); 42 + $this->setChangesets($changesets); 43 + $this->setInlineComments($inline_comments); 44 + 45 + } 46 + 47 + protected function renderSubject() { 48 + $revision = $this->getRevision(); 49 + $verb = $this->getVerb(); 50 + return ucwords($verb).': '.$revision->getName(); 51 + } 52 + 53 + protected function getVerb() { 54 + $feedback = $this->getFeedback(); 55 + $action = $feedback->getAction(); 56 + $verb = DifferentialAction::getActionVerb($action); 57 + return $verb; 58 + } 59 + 60 + protected function renderBody() { 61 + 62 + $feedback = $this->getFeedback(); 63 + 64 + $actor = $this->getActorName(); 65 + $name = $this->getRevision()->getName(); 66 + $verb = $this->getVerb(); 67 + 68 + $body = array(); 69 + 70 + $body[] = "{$actor} has {$verb} the revision \"{$name}\"."; 71 + $body[] = null; 72 + 73 + $content = $feedback->getContent(); 74 + if (strlen($content)) { 75 + $body[] = $this->formatText($content); 76 + $body[] = null; 77 + } 78 + 79 + if ($this->getChangedByCommit()) { 80 + $body[] = 'CHANGED PRIOR TO COMMIT'; 81 + $body[] = ' This revision was updated prior to commit.'; 82 + $body[] = null; 83 + } 84 + 85 + $inlines = $this->getInlineComments(); 86 + if ($inlines) { 87 + $body[] = 'INLINE COMMENTS'; 88 + $changesets = $this->getChangesets(); 89 + foreach ($inlines as $inline) { 90 + $changeset = $changesets[$inline->getChangesetID()]; 91 + if (!$changeset) { 92 + throw new Exception('Changeset missing!'); 93 + } 94 + $file = $changeset->getFilename(); 95 + $line = $inline->renderLineRange(); 96 + $content = $inline->getContent(); 97 + $body[] = $this->formatText("{$file}:{$line} {$content}"); 98 + } 99 + $body[] = null; 100 + } 101 + 102 + $body[] = $this->renderRevisionDetailLink(); 103 + $revision = $this->getRevision(); 104 + if ($revision->getStatus() == DifferentialRevisionStatus::COMMITTED) { 105 + $rev_ref = $revision->getRevisionRef(); 106 + if ($rev_ref) { 107 + $body[] = " Detail URL: ".$rev_ref->getDetailURL(); 108 + } 109 + } 110 + $body[] = null; 111 + 112 + return implode("\n", $body); 113 + } 114 + }
+14
src/applications/differential/mail/feedback/__init__.php
··· 1 + <?php 2 + /** 3 + * This file is automatically generated. Lint this module to rebuild it. 4 + * @generated 5 + */ 6 + 7 + 8 + 9 + phutil_require_module('phabricator', 'applications/differential/constants/action'); 10 + phutil_require_module('phabricator', 'applications/differential/constants/revisionstatus'); 11 + phutil_require_module('phabricator', 'applications/differential/mail/base'); 12 + 13 + 14 + phutil_require_source('DifferentialFeedbackMail.php');
+65
src/applications/differential/mail/newdiff/DifferentialNewDiffMail.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2011 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + class DifferentialNewDiffMail extends DifferentialReviewRequestMail { 20 + 21 + protected function renderSubject() { 22 + $revision = $this->getRevision(); 23 + $line_count = $revision->getLineCount(); 24 + $lines = ($line_count == 1 ? "1 line" : "{$line_count} lines"); 25 + 26 + if ($this->isFirstMailToRecipients()) { 27 + $verb = 'Request'; 28 + } else { 29 + $verb = 'Updated'; 30 + } 31 + 32 + return "{$verb} ({$lines}): ".$revision->getTitle(); 33 + } 34 + 35 + protected function buildSubject() { 36 + if (!$this->isFirstMailToRecipients()) { 37 + return parent::buildSubject(); 38 + } 39 + 40 + $prefix = self::SUBJECT_PREFIX; 41 + 42 + $subject = $this->renderSubject(); 43 + 44 + return "{$prefix} {$subject}"; 45 + } 46 + 47 + protected function renderBody() { 48 + $actor = $this->getActorName(); 49 + 50 + $name = $this->getRevision()->getTitle(); 51 + 52 + $body = array(); 53 + 54 + if ($this->isFirstMailToRecipients()) { 55 + $body[] = "{$actor} requested code review of \"{$name}\"."; 56 + } else { 57 + $body[] = "{$actor} updated the revision \"{$name}\"."; 58 + } 59 + $body[] = null; 60 + 61 + $body[] = $this->renderReviewRequestBody(); 62 + 63 + return implode("\n", $body); 64 + } 65 + }
+12
src/applications/differential/mail/newdiff/__init__.php
··· 1 + <?php 2 + /** 3 + * This file is automatically generated. Lint this module to rebuild it. 4 + * @generated 5 + */ 6 + 7 + 8 + 9 + phutil_require_module('phabricator', 'applications/differential/mail/reviewrequest'); 10 + 11 + 12 + phutil_require_source('DifferentialNewDiffMail.php');
+74
src/applications/differential/mail/reviewrequest/DifferentialReviewRequestMail.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2011 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + abstract class DifferentialReviewRequestMail extends DifferentialMail { 20 + 21 + protected $comments; 22 + 23 + public function setComments($comments) { 24 + $this->comments = $comments; 25 + return $this; 26 + } 27 + 28 + public function getComments() { 29 + return $this->comments; 30 + } 31 + 32 + public function __construct( 33 + DifferentialRevision $revision, 34 + $actor_id, 35 + array $changesets) { 36 + 37 + $this->setRevision($revision); 38 + $this->setActorID($actor_id); 39 + $this->setChangesets($changesets); 40 + } 41 + 42 + protected function renderReviewRequestBody() { 43 + $revision = $this->getRevision(); 44 + 45 + $body = array(); 46 + if ($this->isFirstMailToRecipients()) { 47 + $body[] = $this->formatText($revision->getSummary()); 48 + $body[] = null; 49 + 50 + $body[] = 'TEST PLAN'; 51 + $body[] = $this->formatText($revision->getTestPlan()); 52 + $body[] = null; 53 + } else { 54 + if (strlen($this->getComments())) { 55 + $body[] = $this->formatText($this->getComments()); 56 + $body[] = null; 57 + } 58 + } 59 + 60 + $body[] = $this->renderRevisionDetailLink(); 61 + $body[] = null; 62 + 63 + $changesets = $this->getChangesets(); 64 + if ($changesets) { 65 + $body[] = 'AFFECTED FILES'; 66 + foreach ($changesets as $changeset) { 67 + $body[] = ' '.$changeset->getFilename(); 68 + } 69 + $body[] = null; 70 + } 71 + 72 + return implode("\n", $body); 73 + } 74 + }
+12
src/applications/differential/mail/reviewrequest/__init__.php
··· 1 + <?php 2 + /** 3 + * This file is automatically generated. Lint this module to rebuild it. 4 + * @generated 5 + */ 6 + 7 + 8 + 9 + phutil_require_module('phabricator', 'applications/differential/mail/base'); 10 + 11 + 12 + phutil_require_source('DifferentialReviewRequestMail.php');
+23 -1
src/applications/differential/storage/revision/DifferentialRevision.php
··· 18 18 19 19 class DifferentialRevision extends DifferentialDAO { 20 20 21 - protected $name; 21 + protected $title; 22 22 protected $status; 23 23 24 24 protected $summary; ··· 32 32 protected $dateCommitted; 33 33 34 34 protected $lineCount; 35 + 36 + public function getConfiguration() { 37 + return array( 38 + self::CONFIG_AUX_PHID => true, 39 + ) + parent::getConfiguration(); 40 + } 41 + 42 + public function generatePHID() { 43 + return PhabricatorPHID::generateNewPHID('DREV'); 44 + } 45 + 46 + public function loadRelationships() { 47 + 48 + } 49 + 50 + public function getReviewers() { 51 + return array(); 52 + } 53 + 54 + public function getCCPHIDs() { 55 + return array(); 56 + } 35 57 36 58 }
+8
src/view/form/base/AphrontFormView.php
··· 39 39 return $this; 40 40 } 41 41 42 + public function addHiddenInput($key, $value) { 43 + $this->data[$key] = $value; 44 + return $this; 45 + } 46 + 42 47 public function render() { 43 48 require_celerity_resource('aphront-form-view-css'); 44 49 return phutil_render_tag( ··· 59 64 ); 60 65 $inputs = array(); 61 66 foreach ($data as $key => $value) { 67 + if ($value === null) { 68 + continue; 69 + } 62 70 $inputs[] = phutil_render_tag( 63 71 'input', 64 72 array(
+20
src/view/form/control/textarea/AphrontFormTextAreaControl.php
··· 18 18 19 19 class AphrontFormTextAreaControl extends AphrontFormControl { 20 20 21 + const HEIGHT_VERY_SHORT = 'very-short'; 22 + const HEIGHT_SHORT = 'short'; 23 + 24 + private $height; 25 + 26 + public function setHeight($height) { 27 + $this->height = $height; 28 + return $this; 29 + } 30 + 21 31 protected function getCustomControlClass() { 22 32 return 'aphront-form-control-textarea'; 23 33 } 24 34 25 35 protected function renderInput() { 36 + 37 + $height_class = null; 38 + switch ($this->height) { 39 + case self::HEIGHT_VERY_SHORT: 40 + case self::HEIGHT_SHORT: 41 + $height_class = 'aphront-textarea-'.$this->height; 42 + break; 43 + } 44 + 26 45 return phutil_render_tag( 27 46 'textarea', 28 47 array( 29 48 'name' => $this->getName(), 30 49 'disabled' => $this->getDisabled() ? 'disabled' : null, 50 + 'class' => $height_class, 31 51 ), 32 52 phutil_escape_html($this->getValue())); 33 53 }
+5 -4
src/view/page/standard/PhabricatorStandardPageView.php
··· 114 114 115 115 $login_stuff = null; 116 116 $request = $this->getRequest(); 117 - $user = $request->getUser(); 118 - 119 - if ($user->getPHID()) { 120 - $login_stuff = 'Logged in as '.phutil_escape_html($user->getUsername()); 117 + if ($request) { 118 + $user = $request->getUser(); 119 + if ($user->getPHID()) { 120 + $login_stuff = 'Logged in as '.phutil_escape_html($user->getUsername()); 121 + } 121 122 } 122 123 123 124 return
+4
webroot/rsrc/css/aphront/form-view.css
··· 53 53 margin: 0.5em 0 0em 2%; 54 54 } 55 55 56 + .aphront-form-control-textarea textarea.aphront-textarea-very-short { 57 + height: 3em; 58 + } 59 + 56 60 .aphront-form-control-select .aphront-form-input { 57 61 padding-top: 2px; 58 62 }