[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/project/editor/ -> PhabricatorProjectTransactionEditor.php (source)

   1  <?php
   2  
   3  final class PhabricatorProjectTransactionEditor
   4    extends PhabricatorApplicationTransactionEditor {
   5  
   6    public function getEditorApplicationClass() {
   7      return 'PhabricatorProjectApplication';
   8    }
   9  
  10    public function getEditorObjectsDescription() {
  11      return pht('Projects');
  12    }
  13  
  14    public function getTransactionTypes() {
  15      $types = parent::getTransactionTypes();
  16  
  17      $types[] = PhabricatorTransactions::TYPE_EDGE;
  18      $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
  19      $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
  20      $types[] = PhabricatorTransactions::TYPE_JOIN_POLICY;
  21  
  22      $types[] = PhabricatorProjectTransaction::TYPE_NAME;
  23      $types[] = PhabricatorProjectTransaction::TYPE_SLUGS;
  24      $types[] = PhabricatorProjectTransaction::TYPE_STATUS;
  25      $types[] = PhabricatorProjectTransaction::TYPE_IMAGE;
  26      $types[] = PhabricatorProjectTransaction::TYPE_ICON;
  27      $types[] = PhabricatorProjectTransaction::TYPE_COLOR;
  28      $types[] = PhabricatorProjectTransaction::TYPE_LOCKED;
  29  
  30      return $types;
  31    }
  32  
  33    protected function getCustomTransactionOldValue(
  34      PhabricatorLiskDAO $object,
  35      PhabricatorApplicationTransaction $xaction) {
  36  
  37      switch ($xaction->getTransactionType()) {
  38        case PhabricatorProjectTransaction::TYPE_NAME:
  39          return $object->getName();
  40        case PhabricatorProjectTransaction::TYPE_SLUGS:
  41          $slugs = $object->getSlugs();
  42          $slugs = mpull($slugs, 'getSlug', 'getSlug');
  43          unset($slugs[$object->getPrimarySlug()]);
  44          return $slugs;
  45        case PhabricatorProjectTransaction::TYPE_STATUS:
  46          return $object->getStatus();
  47        case PhabricatorProjectTransaction::TYPE_IMAGE:
  48          return $object->getProfileImagePHID();
  49        case PhabricatorProjectTransaction::TYPE_ICON:
  50          return $object->getIcon();
  51        case PhabricatorProjectTransaction::TYPE_COLOR:
  52          return $object->getColor();
  53        case PhabricatorProjectTransaction::TYPE_LOCKED:
  54          return (int) $object->getIsMembershipLocked();
  55      }
  56  
  57      return parent::getCustomTransactionOldValue($object, $xaction);
  58    }
  59  
  60    protected function getCustomTransactionNewValue(
  61      PhabricatorLiskDAO $object,
  62      PhabricatorApplicationTransaction $xaction) {
  63  
  64      switch ($xaction->getTransactionType()) {
  65        case PhabricatorProjectTransaction::TYPE_NAME:
  66        case PhabricatorProjectTransaction::TYPE_SLUGS:
  67        case PhabricatorProjectTransaction::TYPE_STATUS:
  68        case PhabricatorProjectTransaction::TYPE_IMAGE:
  69        case PhabricatorProjectTransaction::TYPE_ICON:
  70        case PhabricatorProjectTransaction::TYPE_COLOR:
  71        case PhabricatorProjectTransaction::TYPE_LOCKED:
  72          return $xaction->getNewValue();
  73      }
  74  
  75      return parent::getCustomTransactionNewValue($object, $xaction);
  76    }
  77  
  78    protected function applyCustomInternalTransaction(
  79      PhabricatorLiskDAO $object,
  80      PhabricatorApplicationTransaction $xaction) {
  81  
  82      switch ($xaction->getTransactionType()) {
  83        case PhabricatorProjectTransaction::TYPE_NAME:
  84          $object->setName($xaction->getNewValue());
  85          $object->setPhrictionSlug($xaction->getNewValue());
  86          return;
  87        case PhabricatorProjectTransaction::TYPE_SLUGS:
  88          return;
  89        case PhabricatorProjectTransaction::TYPE_STATUS:
  90          $object->setStatus($xaction->getNewValue());
  91          return;
  92        case PhabricatorProjectTransaction::TYPE_IMAGE:
  93          $object->setProfileImagePHID($xaction->getNewValue());
  94          return;
  95        case PhabricatorProjectTransaction::TYPE_ICON:
  96          $object->setIcon($xaction->getNewValue());
  97          return;
  98        case PhabricatorProjectTransaction::TYPE_COLOR:
  99          $object->setColor($xaction->getNewValue());
 100          return;
 101        case PhabricatorProjectTransaction::TYPE_LOCKED:
 102          $object->setIsMembershipLocked($xaction->getNewValue());
 103          return;
 104        case PhabricatorTransactions::TYPE_EDGE:
 105          return;
 106        case PhabricatorTransactions::TYPE_VIEW_POLICY:
 107          $object->setViewPolicy($xaction->getNewValue());
 108          return;
 109        case PhabricatorTransactions::TYPE_EDIT_POLICY:
 110          $object->setEditPolicy($xaction->getNewValue());
 111          return;
 112        case PhabricatorTransactions::TYPE_JOIN_POLICY:
 113          $object->setJoinPolicy($xaction->getNewValue());
 114          return;
 115      }
 116  
 117      return parent::applyCustomInternalTransaction($object, $xaction);
 118    }
 119  
 120    protected function applyCustomExternalTransaction(
 121      PhabricatorLiskDAO $object,
 122      PhabricatorApplicationTransaction $xaction) {
 123  
 124      $old = $xaction->getOldValue();
 125      $new = $xaction->getNewValue();
 126  
 127      switch ($xaction->getTransactionType()) {
 128        case PhabricatorProjectTransaction::TYPE_NAME:
 129          // First, remove the old and new slugs. Removing the old slug is
 130          // important when changing the project's capitalization or punctuation.
 131          // Removing the new slug is important when changing the project's name
 132          // so that one of its secondary slugs is now the primary slug.
 133          if ($old !== null) {
 134            $this->removeSlug($object, $old);
 135          }
 136          $this->removeSlug($object, $new);
 137  
 138          $new_slug = id(new PhabricatorProjectSlug())
 139            ->setSlug($object->getPrimarySlug())
 140            ->setProjectPHID($object->getPHID())
 141            ->save();
 142  
 143          return;
 144        case PhabricatorProjectTransaction::TYPE_SLUGS:
 145          $old = $xaction->getOldValue();
 146          $new = $xaction->getNewValue();
 147          $add = array_diff($new, $old);
 148          $rem = array_diff($old, $new);
 149  
 150          if ($add) {
 151            $add_slug_template = id(new PhabricatorProjectSlug())
 152              ->setProjectPHID($object->getPHID());
 153            foreach ($add as $add_slug_str) {
 154              $add_slug = id(clone $add_slug_template)
 155                ->setSlug($add_slug_str)
 156                ->save();
 157            }
 158          }
 159          if ($rem) {
 160            $rem_slugs = id(new PhabricatorProjectSlug())
 161              ->loadAllWhere('slug IN (%Ls)', $rem);
 162            foreach ($rem_slugs as $rem_slug) {
 163              $rem_slug->delete();
 164            }
 165          }
 166  
 167          return;
 168        case PhabricatorTransactions::TYPE_VIEW_POLICY:
 169        case PhabricatorTransactions::TYPE_EDIT_POLICY:
 170        case PhabricatorTransactions::TYPE_JOIN_POLICY:
 171        case PhabricatorProjectTransaction::TYPE_STATUS:
 172        case PhabricatorProjectTransaction::TYPE_IMAGE:
 173        case PhabricatorProjectTransaction::TYPE_ICON:
 174        case PhabricatorProjectTransaction::TYPE_COLOR:
 175        case PhabricatorProjectTransaction::TYPE_LOCKED:
 176          return;
 177        case PhabricatorTransactions::TYPE_EDGE:
 178          $edge_type = $xaction->getMetadataValue('edge:type');
 179          switch ($edge_type) {
 180            case PhabricatorEdgeConfig::TYPE_PROJ_MEMBER:
 181            case PhabricatorEdgeConfig::TYPE_OBJECT_HAS_WATCHER:
 182              $old = $xaction->getOldValue();
 183              $new = $xaction->getNewValue();
 184  
 185              // When adding members or watchers, we add subscriptions.
 186              $add = array_keys(array_diff_key($new, $old));
 187  
 188              // When removing members, we remove their subscription too.
 189              // When unwatching, we leave subscriptions, since it's fine to be
 190              // subscribed to a project but not be a member of it.
 191              if ($edge_type == PhabricatorEdgeConfig::TYPE_PROJ_MEMBER) {
 192                $rem = array_keys(array_diff_key($old, $new));
 193              } else {
 194                $rem = array();
 195              }
 196  
 197              // NOTE: The subscribe is "explicit" because there's no implicit
 198              // unsubscribe, so Join -> Leave -> Join doesn't resubscribe you
 199              // if we use an implicit subscribe, even though you never willfully
 200              // unsubscribed. Not sure if adding implicit unsubscribe (which
 201              // would not write the unsubscribe row) is justified to deal with
 202              // this, which is a fairly weird edge case and pretty arguable both
 203              // ways.
 204  
 205              // Subscriptions caused by watches should also clearly be explicit,
 206              // and that case is unambiguous.
 207  
 208              id(new PhabricatorSubscriptionsEditor())
 209                ->setActor($this->requireActor())
 210                ->setObject($object)
 211                ->subscribeExplicit($add)
 212                ->unsubscribe($rem)
 213                ->save();
 214  
 215              if ($rem) {
 216                // When removing members, also remove any watches on the project.
 217                $edge_editor = new PhabricatorEdgeEditor();
 218                foreach ($rem as $rem_phid) {
 219                  $edge_editor->removeEdge(
 220                    $object->getPHID(),
 221                    PhabricatorEdgeConfig::TYPE_OBJECT_HAS_WATCHER,
 222                    $rem_phid);
 223                }
 224                $edge_editor->save();
 225              }
 226              break;
 227          }
 228          return;
 229      }
 230  
 231      return parent::applyCustomExternalTransaction($object, $xaction);
 232    }
 233  
 234    protected function validateTransaction(
 235      PhabricatorLiskDAO $object,
 236      $type,
 237      array $xactions) {
 238  
 239      $errors = parent::validateTransaction($object, $type, $xactions);
 240  
 241      switch ($type) {
 242        case PhabricatorProjectTransaction::TYPE_NAME:
 243          $missing = $this->validateIsEmptyTextField(
 244            $object->getName(),
 245            $xactions);
 246  
 247          if ($missing) {
 248            $error = new PhabricatorApplicationTransactionValidationError(
 249              $type,
 250              pht('Required'),
 251              pht('Project name is required.'),
 252              nonempty(last($xactions), null));
 253  
 254            $error->setIsMissingFieldError(true);
 255            $errors[] = $error;
 256          }
 257  
 258          if (!$xactions) {
 259            break;
 260          }
 261  
 262          $name = last($xactions)->getNewValue();
 263          $name_used_already = id(new PhabricatorProjectQuery())
 264            ->setViewer($this->getActor())
 265            ->withNames(array($name))
 266            ->executeOne();
 267          if ($name_used_already &&
 268             ($name_used_already->getPHID() != $object->getPHID())) {
 269            $error = new PhabricatorApplicationTransactionValidationError(
 270              $type,
 271              pht('Duplicate'),
 272              pht('Project name is already used.'),
 273              nonempty(last($xactions), null));
 274            $errors[] = $error;
 275          }
 276  
 277          $slug_builder = clone $object;
 278          $slug_builder->setPhrictionSlug($name);
 279          $slug = $slug_builder->getPrimarySlug();
 280          $slug_used_already = id(new PhabricatorProjectSlug())
 281            ->loadOneWhere('slug = %s', $slug);
 282          if ($slug_used_already &&
 283              $slug_used_already->getProjectPHID() != $object->getPHID()) {
 284            $error = new PhabricatorApplicationTransactionValidationError(
 285              $type,
 286              pht('Duplicate'),
 287              pht('Project name can not be used due to hashtag collision.'),
 288              nonempty(last($xactions), null));
 289            $errors[] = $error;
 290          }
 291          break;
 292        case PhabricatorProjectTransaction::TYPE_SLUGS:
 293          if (!$xactions) {
 294            break;
 295          }
 296  
 297          $slug_xaction = last($xactions);
 298          $new = $slug_xaction->getNewValue();
 299  
 300          if ($new) {
 301            $slugs_used_already = id(new PhabricatorProjectSlug())
 302              ->loadAllWhere('slug IN (%Ls)', $new);
 303          } else {
 304            // The project doesn't have any extra slugs.
 305            $slugs_used_already = array();
 306          }
 307  
 308          $slugs_used_already = mgroup($slugs_used_already, 'getProjectPHID');
 309          foreach ($slugs_used_already as $project_phid => $used_slugs) {
 310            $used_slug_strs = mpull($used_slugs, 'getSlug');
 311            if ($project_phid == $object->getPHID()) {
 312              if (in_array($object->getPrimarySlug(), $used_slug_strs)) {
 313                $error = new PhabricatorApplicationTransactionValidationError(
 314                  $type,
 315                  pht('Invalid'),
 316                  pht(
 317                    'Project hashtag %s is already the primary hashtag.',
 318                    $object->getPrimarySlug()),
 319                  $slug_xaction);
 320                $errors[] = $error;
 321              }
 322              continue;
 323            }
 324  
 325            $error = new PhabricatorApplicationTransactionValidationError(
 326              $type,
 327              pht('Invalid'),
 328              pht(
 329                '%d project hashtag(s) are already used: %s',
 330                count($used_slug_strs),
 331                implode(', ', $used_slug_strs)),
 332              $slug_xaction);
 333            $errors[] = $error;
 334          }
 335  
 336          break;
 337  
 338      }
 339  
 340      return $errors;
 341    }
 342  
 343  
 344    protected function requireCapabilities(
 345      PhabricatorLiskDAO $object,
 346      PhabricatorApplicationTransaction $xaction) {
 347  
 348      switch ($xaction->getTransactionType()) {
 349        case PhabricatorProjectTransaction::TYPE_NAME:
 350        case PhabricatorProjectTransaction::TYPE_STATUS:
 351        case PhabricatorProjectTransaction::TYPE_IMAGE:
 352        case PhabricatorProjectTransaction::TYPE_ICON:
 353        case PhabricatorProjectTransaction::TYPE_COLOR:
 354          PhabricatorPolicyFilter::requireCapability(
 355            $this->requireActor(),
 356            $object,
 357            PhabricatorPolicyCapability::CAN_EDIT);
 358          return;
 359        case PhabricatorProjectTransaction::TYPE_LOCKED:
 360          PhabricatorPolicyFilter::requireCapability(
 361            $this->requireActor(),
 362            newv($this->getEditorApplicationClass(), array()),
 363            ProjectCanLockProjectsCapability::CAPABILITY);
 364          return;
 365        case PhabricatorTransactions::TYPE_EDGE:
 366          switch ($xaction->getMetadataValue('edge:type')) {
 367            case PhabricatorEdgeConfig::TYPE_PROJ_MEMBER:
 368              $old = $xaction->getOldValue();
 369              $new = $xaction->getNewValue();
 370  
 371              $add = array_keys(array_diff_key($new, $old));
 372              $rem = array_keys(array_diff_key($old, $new));
 373  
 374              $actor_phid = $this->requireActor()->getPHID();
 375  
 376              $is_join = (($add === array($actor_phid)) && !$rem);
 377              $is_leave = (($rem === array($actor_phid)) && !$add);
 378  
 379              if ($is_join) {
 380                // You need CAN_JOIN to join a project.
 381                PhabricatorPolicyFilter::requireCapability(
 382                  $this->requireActor(),
 383                  $object,
 384                  PhabricatorPolicyCapability::CAN_JOIN);
 385              } else if ($is_leave) {
 386                // You usually don't need any capabilities to leave a project.
 387                if ($object->getIsMembershipLocked()) {
 388                  // you must be able to edit though to leave locked projects
 389                  PhabricatorPolicyFilter::requireCapability(
 390                    $this->requireActor(),
 391                    $object,
 392                    PhabricatorPolicyCapability::CAN_EDIT);
 393                }
 394              } else {
 395                // You need CAN_EDIT to change members other than yourself.
 396                PhabricatorPolicyFilter::requireCapability(
 397                  $this->requireActor(),
 398                  $object,
 399                  PhabricatorPolicyCapability::CAN_EDIT);
 400              }
 401              return;
 402          }
 403          break;
 404      }
 405  
 406      return parent::requireCapabilities($object, $xaction);
 407    }
 408  
 409    protected function supportsSearch() {
 410      return true;
 411    }
 412  
 413    protected function extractFilePHIDsFromCustomTransaction(
 414      PhabricatorLiskDAO $object,
 415      PhabricatorApplicationTransaction $xaction) {
 416  
 417      switch ($xaction->getTransactionType()) {
 418        case PhabricatorProjectTransaction::TYPE_IMAGE:
 419          $new = $xaction->getNewValue();
 420          if ($new) {
 421            return array($new);
 422          }
 423          break;
 424      }
 425  
 426      return parent::extractFilePHIDsFromCustomTransaction($object, $xaction);
 427    }
 428  
 429    private function removeSlug(
 430      PhabricatorLiskDAO $object,
 431      $name) {
 432  
 433      $object = (clone $object);
 434      $object->setPhrictionSlug($name);
 435      $slug = $object->getPrimarySlug();
 436  
 437      $slug_object = id(new PhabricatorProjectSlug())->loadOneWhere(
 438        'slug = %s',
 439        $slug);
 440  
 441      if (!$slug_object) {
 442        return;
 443      }
 444  
 445      if ($slug_object->getProjectPHID() != $object->getPHID()) {
 446        throw new Exception(
 447          pht('Trying to remove slug owned by another project!'));
 448      }
 449  
 450      $slug_object->delete();
 451    }
 452  
 453  }


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