[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/owners/storage/ -> PhabricatorOwnersPackage.php (source)

   1  <?php
   2  
   3  final class PhabricatorOwnersPackage extends PhabricatorOwnersDAO
   4    implements PhabricatorPolicyInterface {
   5  
   6    protected $name;
   7    protected $originalName;
   8    protected $auditingEnabled;
   9    protected $description;
  10    protected $primaryOwnerPHID;
  11  
  12    private $unsavedOwners;
  13    private $unsavedPaths;
  14    private $actorPHID;
  15    private $oldPrimaryOwnerPHID;
  16    private $oldAuditingEnabled;
  17  
  18    public function getCapabilities() {
  19      return array(
  20        PhabricatorPolicyCapability::CAN_VIEW,
  21      );
  22    }
  23  
  24    public function getPolicy($capability) {
  25      return PhabricatorPolicies::POLICY_USER;
  26    }
  27  
  28    public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
  29      return false;
  30    }
  31  
  32    public function describeAutomaticCapability($capability) {
  33      return null;
  34    }
  35  
  36    public function getConfiguration() {
  37      return array(
  38        // This information is better available from the history table.
  39        self::CONFIG_TIMESTAMPS => false,
  40        self::CONFIG_AUX_PHID   => true,
  41        self::CONFIG_COLUMN_SCHEMA => array(
  42          'name' => 'text128',
  43          'originalName' => 'text255',
  44          'description' => 'text',
  45          'primaryOwnerPHID' => 'phid?',
  46          'auditingEnabled' => 'bool',
  47        ),
  48        self::CONFIG_KEY_SCHEMA => array(
  49          'key_phid' => null,
  50          'phid' => array(
  51            'columns' => array('phid'),
  52            'unique' => true,
  53          ),
  54          'name' => array(
  55            'columns' => array('name'),
  56            'unique' => true,
  57          ),
  58        ),
  59      ) + parent::getConfiguration();
  60    }
  61  
  62    public function generatePHID() {
  63      return PhabricatorPHID::generateNewPHID('OPKG');
  64    }
  65  
  66    public function attachUnsavedOwners(array $owners) {
  67      $this->unsavedOwners = $owners;
  68      return $this;
  69    }
  70  
  71    public function attachUnsavedPaths(array $paths) {
  72      $this->unsavedPaths = $paths;
  73      return $this;
  74    }
  75  
  76    public function attachActorPHID($actor_phid) {
  77      $this->actorPHID = $actor_phid;
  78      return $this;
  79    }
  80  
  81    public function getActorPHID() {
  82      return $this->actorPHID;
  83    }
  84  
  85    public function attachOldPrimaryOwnerPHID($old_primary) {
  86      $this->oldPrimaryOwnerPHID = $old_primary;
  87      return $this;
  88    }
  89  
  90    public function getOldPrimaryOwnerPHID() {
  91      return $this->oldPrimaryOwnerPHID;
  92    }
  93  
  94    public function attachOldAuditingEnabled($auditing_enabled) {
  95      $this->oldAuditingEnabled = $auditing_enabled;
  96      return $this;
  97    }
  98  
  99    public function getOldAuditingEnabled() {
 100      return $this->oldAuditingEnabled;
 101    }
 102  
 103    public function setName($name) {
 104      $this->name = $name;
 105      if (!$this->getID()) {
 106        $this->originalName = $name;
 107      }
 108      return $this;
 109    }
 110  
 111    public function loadOwners() {
 112      if (!$this->getID()) {
 113        return array();
 114      }
 115      return id(new PhabricatorOwnersOwner())->loadAllWhere(
 116        'packageID = %d',
 117        $this->getID());
 118    }
 119  
 120    public function loadPaths() {
 121      if (!$this->getID()) {
 122        return array();
 123      }
 124      return id(new PhabricatorOwnersPath())->loadAllWhere(
 125        'packageID = %d',
 126        $this->getID());
 127    }
 128  
 129    public static function loadAffectedPackages(
 130      PhabricatorRepository $repository,
 131      array $paths) {
 132  
 133      if (!$paths) {
 134        return array();
 135      }
 136  
 137      return self::loadPackagesForPaths($repository, $paths);
 138   }
 139  
 140   public static function loadOwningPackages($repository, $path) {
 141      if (empty($path)) {
 142        return array();
 143      }
 144  
 145      return self::loadPackagesForPaths($repository, array($path), 1);
 146   }
 147  
 148    private static function loadPackagesForPaths(
 149      PhabricatorRepository $repository,
 150      array $paths,
 151      $limit = 0) {
 152  
 153      $fragments = array();
 154      foreach ($paths as $path) {
 155        foreach (self::splitPath($path) as $fragment) {
 156          $fragments[$fragment][$path] = true;
 157        }
 158      }
 159  
 160      $package = new PhabricatorOwnersPackage();
 161      $path = new PhabricatorOwnersPath();
 162      $conn = $package->establishConnection('r');
 163  
 164      $repository_clause = qsprintf(
 165        $conn,
 166        'AND p.repositoryPHID = %s',
 167        $repository->getPHID());
 168  
 169      // NOTE: The list of $paths may be very large if we're coming from
 170      // the OwnersWorker and processing, e.g., an SVN commit which created a new
 171      // branch. Break it apart so that it will fit within 'max_allowed_packet',
 172      // and then merge results in PHP.
 173  
 174      $rows = array();
 175      foreach (array_chunk(array_keys($fragments), 128) as $chunk) {
 176        $rows[] = queryfx_all(
 177          $conn,
 178          'SELECT pkg.id, p.excluded, p.path
 179            FROM %T pkg JOIN %T p ON p.packageID = pkg.id
 180            WHERE p.path IN (%Ls) %Q',
 181          $package->getTableName(),
 182          $path->getTableName(),
 183          $chunk,
 184          $repository_clause);
 185      }
 186      $rows = array_mergev($rows);
 187  
 188      $ids = self::findLongestPathsPerPackage($rows, $fragments);
 189  
 190      if (!$ids) {
 191        return array();
 192      }
 193  
 194      arsort($ids);
 195      if ($limit) {
 196        $ids = array_slice($ids, 0, $limit, $preserve_keys = true);
 197      }
 198      $ids = array_keys($ids);
 199  
 200      $packages = $package->loadAllWhere('id in (%Ld)', $ids);
 201      $packages = array_select_keys($packages, $ids);
 202  
 203      return $packages;
 204    }
 205  
 206    public static function loadPackagesForRepository($repository) {
 207      $package = new PhabricatorOwnersPackage();
 208      $ids = ipull(
 209        queryfx_all(
 210          $package->establishConnection('r'),
 211          'SELECT DISTINCT packageID FROM %T WHERE repositoryPHID = %s',
 212          id(new PhabricatorOwnersPath())->getTableName(),
 213          $repository->getPHID()),
 214        'packageID');
 215  
 216      return $package->loadAllWhere('id in (%Ld)', $ids);
 217    }
 218  
 219    public static function findLongestPathsPerPackage(array $rows, array $paths) {
 220      $ids = array();
 221  
 222      foreach (igroup($rows, 'id') as $id => $package_paths) {
 223        $relevant_paths = array_select_keys(
 224          $paths,
 225          ipull($package_paths, 'path'));
 226  
 227        // For every package, remove all excluded paths.
 228        $remove = array();
 229        foreach ($package_paths as $package_path) {
 230          if ($package_path['excluded']) {
 231            $remove += idx($relevant_paths, $package_path['path'], array());
 232            unset($relevant_paths[$package_path['path']]);
 233          }
 234        }
 235  
 236        if ($remove) {
 237          foreach ($relevant_paths as $fragment => $fragment_paths) {
 238            $relevant_paths[$fragment] = array_diff_key($fragment_paths, $remove);
 239          }
 240        }
 241  
 242        $relevant_paths = array_filter($relevant_paths);
 243        if ($relevant_paths) {
 244          $ids[$id] = max(array_map('strlen', array_keys($relevant_paths)));
 245        }
 246      }
 247  
 248      return $ids;
 249    }
 250  
 251    private function getActor() {
 252      // TODO: This should be cleaner, but we'd likely need to move the whole
 253      // thing to an Editor (T603).
 254      return PhabricatorUser::getOmnipotentUser();
 255    }
 256  
 257    public function save() {
 258  
 259      if ($this->getID()) {
 260        $is_new = false;
 261      } else {
 262        $is_new = true;
 263      }
 264  
 265      $this->openTransaction();
 266  
 267      $ret = parent::save();
 268  
 269      $add_owners = array();
 270      $remove_owners = array();
 271      $all_owners = array();
 272      if ($this->unsavedOwners) {
 273        $new_owners = array_fill_keys($this->unsavedOwners, true);
 274        $cur_owners = array();
 275        foreach ($this->loadOwners() as $owner) {
 276          if (empty($new_owners[$owner->getUserPHID()])) {
 277            $remove_owners[$owner->getUserPHID()] = true;
 278            $owner->delete();
 279            continue;
 280          }
 281          $cur_owners[$owner->getUserPHID()] = true;
 282        }
 283  
 284        $add_owners = array_diff_key($new_owners, $cur_owners);
 285        $all_owners = array_merge(
 286          array($this->getPrimaryOwnerPHID() => true),
 287          $new_owners,
 288          $remove_owners);
 289        foreach ($add_owners as $phid => $ignored) {
 290          $owner = new PhabricatorOwnersOwner();
 291          $owner->setPackageID($this->getID());
 292          $owner->setUserPHID($phid);
 293          $owner->save();
 294        }
 295        unset($this->unsavedOwners);
 296      }
 297  
 298      $add_paths = array();
 299      $remove_paths = array();
 300      $touched_repos = array();
 301      if ($this->unsavedPaths) {
 302        $new_paths = igroup($this->unsavedPaths, 'repositoryPHID', 'path');
 303        $cur_paths = $this->loadPaths();
 304        foreach ($cur_paths as $key => $path) {
 305          $repository_phid = $path->getRepositoryPHID();
 306          $new_path = head(idx(
 307            idx($new_paths, $repository_phid, array()),
 308            $path->getPath(),
 309            array()));
 310          $excluded = $path->getExcluded();
 311          if (!$new_path || idx($new_path, 'excluded') != $excluded) {
 312            $touched_repos[$repository_phid] = true;
 313            $remove_paths[$repository_phid][$path->getPath()] = $excluded;
 314            $path->delete();
 315            unset($cur_paths[$key]);
 316          }
 317        }
 318  
 319        $cur_paths = mgroup($cur_paths, 'getRepositoryPHID', 'getPath');
 320        foreach ($new_paths as $repository_phid => $paths) {
 321          // TODO: (T603) Thread policy stuff in here.
 322  
 323          // get repository object for path validation
 324          $repository = id(new PhabricatorRepository())->loadOneWhere(
 325            'phid = %s',
 326            $repository_phid);
 327          if (!$repository) {
 328            continue;
 329          }
 330          foreach ($paths as $path => $dicts) {
 331            $path = ltrim($path, '/');
 332            // build query to validate path
 333            $drequest = DiffusionRequest::newFromDictionary(
 334              array(
 335                'user' => $this->getActor(),
 336                'repository'  => $repository,
 337                'path'        => $path,
 338              ));
 339            $results = DiffusionBrowseResultSet::newFromConduit(
 340              DiffusionQuery::callConduitWithDiffusionRequest(
 341                $this->getActor(),
 342                $drequest,
 343                'diffusion.browsequery',
 344                array(
 345                  'commit' => $drequest->getCommit(),
 346                  'path' => $path,
 347                  'needValidityOnly' => true,
 348                )));
 349            $valid = $results->isValidResults();
 350            $is_directory = true;
 351            if (!$valid) {
 352              switch ($results->getReasonForEmptyResultSet()) {
 353                case DiffusionBrowseResultSet::REASON_IS_FILE:
 354                  $valid = true;
 355                  $is_directory = false;
 356                  break;
 357                case DiffusionBrowseResultSet::REASON_IS_EMPTY:
 358                  $valid = true;
 359                  break;
 360              }
 361            }
 362            if ($is_directory && substr($path, -1) != '/') {
 363              $path .= '/';
 364            }
 365            if (substr($path, 0, 1) != '/') {
 366              $path = '/'.$path;
 367            }
 368            if (empty($cur_paths[$repository_phid][$path]) && $valid) {
 369              $touched_repos[$repository_phid] = true;
 370              $excluded = idx(reset($dicts), 'excluded', 0);
 371              $add_paths[$repository_phid][$path] = $excluded;
 372              $obj = new PhabricatorOwnersPath();
 373              $obj->setPackageID($this->getID());
 374              $obj->setRepositoryPHID($repository_phid);
 375              $obj->setPath($path);
 376              $obj->setExcluded($excluded);
 377              $obj->save();
 378            }
 379          }
 380        }
 381        unset($this->unsavedPaths);
 382      }
 383  
 384      $this->saveTransaction();
 385  
 386      if ($is_new) {
 387        $mail = new PackageCreateMail($this);
 388      } else {
 389        $mail = new PackageModifyMail(
 390          $this,
 391          array_keys($add_owners),
 392          array_keys($remove_owners),
 393          array_keys($all_owners),
 394          array_keys($touched_repos),
 395          $add_paths,
 396          $remove_paths);
 397      }
 398      $mail->setActor($this->getActor());
 399      $mail->send();
 400  
 401      return $ret;
 402    }
 403  
 404    public function delete() {
 405      $mails = id(new PackageDeleteMail($this))
 406        ->setActor($this->getActor())
 407        ->prepareMails();
 408  
 409      $this->openTransaction();
 410      foreach ($this->loadOwners() as $owner) {
 411        $owner->delete();
 412      }
 413      foreach ($this->loadPaths() as $path) {
 414        $path->delete();
 415      }
 416  
 417      $ret = parent::delete();
 418  
 419      $this->saveTransaction();
 420  
 421      foreach ($mails as $mail) {
 422        $mail->saveAndSend();
 423      }
 424  
 425      return $ret;
 426    }
 427  
 428    private static function splitPath($path) {
 429      $result = array('/');
 430      $trailing_slash = preg_match('@/$@', $path) ? '/' : '';
 431      $path = trim($path, '/');
 432      $parts = explode('/', $path);
 433      while (count($parts)) {
 434        $result[] = '/'.implode('/', $parts).$trailing_slash;
 435        $trailing_slash = '/';
 436        array_pop($parts);
 437      }
 438      return $result;
 439    }
 440  }


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