[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 final class ManiphestBatchEditController extends ManiphestController { 4 5 public function processRequest() { 6 $this->requireApplicationCapability( 7 ManiphestBulkEditCapability::CAPABILITY); 8 9 $request = $this->getRequest(); 10 $user = $request->getUser(); 11 12 $task_ids = $request->getArr('batch'); 13 $tasks = id(new ManiphestTaskQuery()) 14 ->setViewer($user) 15 ->withIDs($task_ids) 16 ->requireCapabilities( 17 array( 18 PhabricatorPolicyCapability::CAN_VIEW, 19 PhabricatorPolicyCapability::CAN_EDIT, 20 )) 21 ->execute(); 22 23 $actions = $request->getStr('actions'); 24 if ($actions) { 25 $actions = json_decode($actions, true); 26 } 27 28 if ($request->isFormPost() && is_array($actions)) { 29 foreach ($tasks as $task) { 30 $field_list = PhabricatorCustomField::getObjectFields( 31 $task, 32 PhabricatorCustomField::ROLE_EDIT); 33 $field_list->readFieldsFromStorage($task); 34 35 $xactions = $this->buildTransactions($actions, $task); 36 if ($xactions) { 37 // TODO: Set content source to "batch edit". 38 39 $editor = id(new ManiphestTransactionEditor()) 40 ->setActor($user) 41 ->setContentSourceFromRequest($request) 42 ->setContinueOnNoEffect(true) 43 ->setContinueOnMissingFields(true) 44 ->applyTransactions($task, $xactions); 45 } 46 } 47 48 $task_ids = implode(',', mpull($tasks, 'getID')); 49 50 return id(new AphrontRedirectResponse()) 51 ->setURI('/maniphest/?ids='.$task_ids); 52 } 53 54 $handles = ManiphestTaskListView::loadTaskHandles($user, $tasks); 55 56 $list = new ManiphestTaskListView(); 57 $list->setTasks($tasks); 58 $list->setUser($user); 59 $list->setHandles($handles); 60 61 $template = new AphrontTokenizerTemplateView(); 62 $template = $template->render(); 63 64 $projects_source = new PhabricatorProjectDatasource(); 65 $mailable_source = new PhabricatorMetaMTAMailableDatasource(); 66 $owner_source = new PhabricatorTypeaheadOwnerDatasource(); 67 68 require_celerity_resource('maniphest-batch-editor'); 69 Javelin::initBehavior( 70 'maniphest-batch-editor', 71 array( 72 'root' => 'maniphest-batch-edit-form', 73 'tokenizerTemplate' => $template, 74 'sources' => array( 75 'project' => array( 76 'src' => $projects_source->getDatasourceURI(), 77 'placeholder' => $projects_source->getPlaceholderText(), 78 ), 79 'owner' => array( 80 'src' => $owner_source->getDatasourceURI(), 81 'placeholder' => $owner_source->getPlaceholderText(), 82 'limit' => 1, 83 ), 84 'cc' => array( 85 'src' => $mailable_source->getDatasourceURI(), 86 'placeholder' => $mailable_source->getPlaceholderText(), 87 ), 88 ), 89 'input' => 'batch-form-actions', 90 'priorityMap' => ManiphestTaskPriority::getTaskPriorityMap(), 91 'statusMap' => ManiphestTaskStatus::getTaskStatusMap(), 92 )); 93 94 $form = new AphrontFormView(); 95 $form->setUser($user); 96 $form->setID('maniphest-batch-edit-form'); 97 98 foreach ($tasks as $task) { 99 $form->appendChild( 100 phutil_tag( 101 'input', 102 array( 103 'type' => 'hidden', 104 'name' => 'batch[]', 105 'value' => $task->getID(), 106 ))); 107 } 108 109 $form->appendChild( 110 phutil_tag( 111 'input', 112 array( 113 'type' => 'hidden', 114 'name' => 'actions', 115 'id' => 'batch-form-actions', 116 ))); 117 $form->appendChild( 118 id(new PHUIFormInsetView()) 119 ->setTitle(pht('Actions')) 120 ->setRightButton(javelin_tag( 121 'a', 122 array( 123 'href' => '#', 124 'class' => 'button green', 125 'sigil' => 'add-action', 126 'mustcapture' => true, 127 ), 128 pht('Add Another Action'))) 129 ->setContent(javelin_tag( 130 'table', 131 array( 132 'sigil' => 'maniphest-batch-actions', 133 'class' => 'maniphest-batch-actions-table', 134 ), 135 ''))) 136 ->appendChild( 137 id(new AphrontFormSubmitControl()) 138 ->setValue(pht('Update Tasks')) 139 ->addCancelButton('/maniphest/')); 140 141 $title = pht('Batch Editor'); 142 143 $crumbs = $this->buildApplicationCrumbs(); 144 $crumbs->addTextCrumb($title); 145 146 $task_box = id(new PHUIObjectBoxView()) 147 ->setHeaderText(pht('Selected Tasks')) 148 ->appendChild($list); 149 150 $form_box = id(new PHUIObjectBoxView()) 151 ->setHeaderText(pht('Batch Editor')) 152 ->setForm($form); 153 154 return $this->buildApplicationPage( 155 array( 156 $crumbs, 157 $task_box, 158 $form_box, 159 ), 160 array( 161 'title' => $title, 162 )); 163 } 164 165 private function buildTransactions($actions, ManiphestTask $task) { 166 $value_map = array(); 167 $type_map = array( 168 'add_comment' => PhabricatorTransactions::TYPE_COMMENT, 169 'assign' => ManiphestTransaction::TYPE_OWNER, 170 'status' => ManiphestTransaction::TYPE_STATUS, 171 'priority' => ManiphestTransaction::TYPE_PRIORITY, 172 'add_project' => ManiphestTransaction::TYPE_PROJECTS, 173 'remove_project' => ManiphestTransaction::TYPE_PROJECTS, 174 'add_ccs' => ManiphestTransaction::TYPE_CCS, 175 'remove_ccs' => ManiphestTransaction::TYPE_CCS, 176 ); 177 178 $edge_edit_types = array( 179 'add_project' => true, 180 'remove_project' => true, 181 'add_ccs' => true, 182 'remove_ccs' => true, 183 ); 184 185 $xactions = array(); 186 foreach ($actions as $action) { 187 if (empty($type_map[$action['action']])) { 188 throw new Exception("Unknown batch edit action '{$action}'!"); 189 } 190 191 $type = $type_map[$action['action']]; 192 193 // Figure out the current value, possibly after modifications by other 194 // batch actions of the same type. For example, if the user chooses to 195 // "Add Comment" twice, we should add both comments. More notably, if the 196 // user chooses "Remove Project..." and also "Add Project...", we should 197 // avoid restoring the removed project in the second transaction. 198 199 if (array_key_exists($type, $value_map)) { 200 $current = $value_map[$type]; 201 } else { 202 switch ($type) { 203 case PhabricatorTransactions::TYPE_COMMENT: 204 $current = null; 205 break; 206 case ManiphestTransaction::TYPE_OWNER: 207 $current = $task->getOwnerPHID(); 208 break; 209 case ManiphestTransaction::TYPE_STATUS: 210 $current = $task->getStatus(); 211 break; 212 case ManiphestTransaction::TYPE_PRIORITY: 213 $current = $task->getPriority(); 214 break; 215 case ManiphestTransaction::TYPE_PROJECTS: 216 $current = $task->getProjectPHIDs(); 217 break; 218 case ManiphestTransaction::TYPE_CCS: 219 $current = $task->getCCPHIDs(); 220 break; 221 } 222 } 223 224 // Check if the value is meaningful / provided, and normalize it if 225 // necessary. This discards, e.g., empty comments and empty owner 226 // changes. 227 228 $value = $action['value']; 229 switch ($type) { 230 case PhabricatorTransactions::TYPE_COMMENT: 231 if (!strlen($value)) { 232 continue 2; 233 } 234 break; 235 case ManiphestTransaction::TYPE_OWNER: 236 if (empty($value)) { 237 continue 2; 238 } 239 $value = head($value); 240 if ($value === ManiphestTaskOwner::OWNER_UP_FOR_GRABS) { 241 $value = null; 242 } 243 break; 244 case ManiphestTransaction::TYPE_PROJECTS: 245 if (empty($value)) { 246 continue 2; 247 } 248 break; 249 case ManiphestTransaction::TYPE_CCS: 250 if (empty($value)) { 251 continue 2; 252 } 253 break; 254 } 255 256 // If the edit doesn't change anything, go to the next action. This 257 // check is only valid for changes like "owner", "status", etc, not 258 // for edge edits, because we should still apply an edit like 259 // "Remove Projects: A, B" to a task with projects "A, B". 260 261 if (empty($edge_edit_types[$action['action']])) { 262 if ($value == $current) { 263 continue; 264 } 265 } 266 267 // Apply the value change; for most edits this is just replacement, but 268 // some need to merge the current and edited values (add/remove project). 269 270 switch ($type) { 271 case PhabricatorTransactions::TYPE_COMMENT: 272 if (strlen($current)) { 273 $value = $current."\n\n".$value; 274 } 275 break; 276 case ManiphestTransaction::TYPE_PROJECTS: 277 case ManiphestTransaction::TYPE_CCS: 278 $remove_actions = array( 279 'remove_project' => true, 280 'remove_ccs' => true, 281 ); 282 $is_remove = isset($remove_actions[$action['action']]); 283 284 $current = array_fill_keys($current, true); 285 $value = array_fill_keys($value, true); 286 287 $new = $current; 288 $did_something = false; 289 290 if ($is_remove) { 291 foreach ($value as $phid => $ignored) { 292 if (isset($new[$phid])) { 293 unset($new[$phid]); 294 $did_something = true; 295 } 296 } 297 } else { 298 foreach ($value as $phid => $ignored) { 299 if (empty($new[$phid])) { 300 $new[$phid] = true; 301 $did_something = true; 302 } 303 } 304 } 305 306 if (!$did_something) { 307 continue 2; 308 } 309 310 $value = array_keys($new); 311 break; 312 } 313 314 $value_map[$type] = $value; 315 } 316 317 $template = new ManiphestTransaction(); 318 319 foreach ($value_map as $type => $value) { 320 $xaction = clone $template; 321 $xaction->setTransactionType($type); 322 323 switch ($type) { 324 case PhabricatorTransactions::TYPE_COMMENT: 325 $xaction->attachComment( 326 id(new ManiphestTransactionComment()) 327 ->setContent($value)); 328 break; 329 case ManiphestTransaction::TYPE_PROJECTS: 330 331 // TODO: Clean this mess up. 332 $project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; 333 $xaction 334 ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) 335 ->setMetadataValue('edge:type', $project_type) 336 ->setNewValue( 337 array( 338 '=' => array_fuse($value), 339 )); 340 break; 341 default: 342 $xaction->setNewValue($value); 343 break; 344 } 345 346 $xactions[] = $xaction; 347 } 348 349 return $xactions; 350 } 351 352 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |