[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/maniphest/controller/ -> ManiphestBatchEditController.php (source)

   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  }


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1