[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/phragment/storage/ -> PhragmentFragment.php (source)

   1  <?php
   2  
   3  final class PhragmentFragment extends PhragmentDAO
   4    implements PhabricatorPolicyInterface {
   5  
   6    protected $path;
   7    protected $depth;
   8    protected $latestVersionPHID;
   9    protected $viewPolicy;
  10    protected $editPolicy;
  11  
  12    private $latestVersion = self::ATTACHABLE;
  13  
  14    public function getConfiguration() {
  15      return array(
  16        self::CONFIG_AUX_PHID => true,
  17        self::CONFIG_COLUMN_SCHEMA => array(
  18          'path' => 'text128',
  19          'depth' => 'uint32',
  20          'latestVersionPHID' => 'phid?',
  21        ),
  22        self::CONFIG_KEY_SCHEMA => array(
  23          'key_path' => array(
  24            'columns' => array('path'),
  25            'unique' => true,
  26          ),
  27        ),
  28      ) + parent::getConfiguration();
  29    }
  30  
  31    public function generatePHID() {
  32      return PhabricatorPHID::generateNewPHID(
  33        PhragmentFragmentPHIDType::TYPECONST);
  34    }
  35  
  36    public function getURI() {
  37      return '/phragment/browse/'.$this->getPath();
  38    }
  39  
  40    public function getName() {
  41      return basename($this->path);
  42    }
  43  
  44    public function getFile() {
  45      return $this->assertAttached($this->file);
  46    }
  47  
  48    public function attachFile(PhabricatorFile $file) {
  49      return $this->file = $file;
  50    }
  51  
  52    public function isDirectory() {
  53      return $this->latestVersionPHID === null;
  54    }
  55  
  56    public function isDeleted() {
  57      return $this->getLatestVersion()->getFilePHID() === null;
  58    }
  59  
  60    public function getLatestVersion() {
  61      if ($this->latestVersionPHID === null) {
  62        return null;
  63      }
  64      return $this->assertAttached($this->latestVersion);
  65    }
  66  
  67    public function attachLatestVersion(PhragmentFragmentVersion $version) {
  68      return $this->latestVersion = $version;
  69    }
  70  
  71  
  72  /* -(  Updating  )  --------------------------------------------------------- */
  73  
  74  
  75    /**
  76     * Create a new fragment from a file.
  77     */
  78    public static function createFromFile(
  79      PhabricatorUser $viewer,
  80      PhabricatorFile $file = null,
  81      $path,
  82      $view_policy,
  83      $edit_policy) {
  84  
  85      $fragment = id(new PhragmentFragment());
  86      $fragment->setPath($path);
  87      $fragment->setDepth(count(explode('/', $path)));
  88      $fragment->setLatestVersionPHID(null);
  89      $fragment->setViewPolicy($view_policy);
  90      $fragment->setEditPolicy($edit_policy);
  91      $fragment->save();
  92  
  93      // Directory fragments have no versions associated with them, so we
  94      // just return the fragment at this point.
  95      if ($file === null) {
  96        return $fragment;
  97      }
  98  
  99      if ($file->getMimeType() === 'application/zip') {
 100        $fragment->updateFromZIP($viewer, $file);
 101      } else {
 102        $fragment->updateFromFile($viewer, $file);
 103      }
 104  
 105      return $fragment;
 106    }
 107  
 108  
 109    /**
 110     * Set the specified file as the next version for the fragment.
 111     */
 112    public function updateFromFile(
 113      PhabricatorUser $viewer,
 114      PhabricatorFile $file) {
 115  
 116      $existing = id(new PhragmentFragmentVersionQuery())
 117        ->setViewer($viewer)
 118        ->withFragmentPHIDs(array($this->getPHID()))
 119        ->execute();
 120      $sequence = count($existing);
 121  
 122      $this->openTransaction();
 123        $version = id(new PhragmentFragmentVersion());
 124        $version->setSequence($sequence);
 125        $version->setFragmentPHID($this->getPHID());
 126        $version->setFilePHID($file->getPHID());
 127        $version->save();
 128  
 129        $this->setLatestVersionPHID($version->getPHID());
 130        $this->save();
 131      $this->saveTransaction();
 132  
 133      $file->attachToObject($version->getPHID());
 134    }
 135  
 136    /**
 137     * Apply the specified ZIP archive onto the fragment, removing
 138     * and creating fragments as needed.
 139     */
 140    public function updateFromZIP(
 141      PhabricatorUser $viewer,
 142      PhabricatorFile $file) {
 143  
 144      if ($file->getMimeType() !== 'application/zip') {
 145        throw new Exception("File must have mimetype 'application/zip'");
 146      }
 147  
 148      // First apply the ZIP as normal.
 149      $this->updateFromFile($viewer, $file);
 150  
 151      // Ensure we have ZIP support.
 152      $zip = null;
 153      try {
 154        $zip = new ZipArchive();
 155      } catch (Exception $e) {
 156        // The server doesn't have php5-zip, so we can't do recursive updates.
 157        return;
 158      }
 159  
 160      $temp = new TempFile();
 161      Filesystem::writeFile($temp, $file->loadFileData());
 162      if (!$zip->open($temp)) {
 163        throw new Exception('Unable to open ZIP');
 164      }
 165  
 166      // Get all of the paths and their data from the ZIP.
 167      $mappings = array();
 168      for ($i = 0; $i < $zip->numFiles; $i++) {
 169        $path = trim($zip->getNameIndex($i), '/');
 170        $stream = $zip->getStream($path);
 171        $data = null;
 172        // If the stream is false, then it is a directory entry. We leave
 173        // $data set to null for directories so we know not to create a
 174        // version entry for them.
 175        if ($stream !== false) {
 176          $data = stream_get_contents($stream);
 177          fclose($stream);
 178        }
 179        $mappings[$path] = $data;
 180      }
 181  
 182      // We need to detect any directories that are in the ZIP folder that
 183      // aren't explicitly noted in the ZIP. This can happen if the file
 184      // entries in the ZIP look like:
 185      //
 186      //  * something/blah.png
 187      //  * something/other.png
 188      //  * test.png
 189      //
 190      // Where there is no explicit "something/" entry.
 191      foreach ($mappings as $path_key => $data) {
 192        if ($data === null) {
 193          continue;
 194        }
 195        $directory = dirname($path_key);
 196        while ($directory !== '.') {
 197          if (!array_key_exists($directory, $mappings)) {
 198            $mappings[$directory] = null;
 199          }
 200          if (dirname($directory) === $directory) {
 201            // dirname() will not reduce this directory any further; to
 202            // prevent infinite loop we just break out here.
 203            break;
 204          }
 205          $directory = dirname($directory);
 206        }
 207      }
 208  
 209      // Adjust the paths relative to this fragment so we can look existing
 210      // fragments up in the DB.
 211      $base_path = $this->getPath();
 212      $paths = array();
 213      foreach ($mappings as $p => $data) {
 214        $paths[] = $base_path.'/'.$p;
 215      }
 216  
 217      // FIXME: What happens when a child exists, but the current user
 218      // can't see it. We're going to create a new child with the exact
 219      // same path and then bad things will happen.
 220      $children = id(new PhragmentFragmentQuery())
 221        ->setViewer($viewer)
 222        ->needLatestVersion(true)
 223        ->withLeadingPath($this->getPath().'/')
 224        ->execute();
 225      $children = mpull($children, null, 'getPath');
 226  
 227      // Iterate over the existing fragments.
 228      foreach ($children as $full_path => $child) {
 229        $path = substr($full_path, strlen($base_path) + 1);
 230        if (array_key_exists($path, $mappings)) {
 231          if ($child->isDirectory() && $mappings[$path] === null) {
 232            // Don't create a version entry for a directory
 233            // (unless it's been converted into a file).
 234            continue;
 235          }
 236  
 237          // The file is being updated.
 238          $file = PhabricatorFile::newFromFileData(
 239            $mappings[$path],
 240            array('name' => basename($path)));
 241          $child->updateFromFile($viewer, $file);
 242        } else {
 243          // The file is being deleted.
 244          $child->deleteFile($viewer);
 245        }
 246      }
 247  
 248      // Iterate over the mappings to find new files.
 249      foreach ($mappings as $path => $data) {
 250        if (!array_key_exists($base_path.'/'.$path, $children)) {
 251          // The file is being created. If the data is null,
 252          // then this is explicitly a directory being created.
 253          $file = null;
 254          if ($mappings[$path] !== null) {
 255            $file = PhabricatorFile::newFromFileData(
 256              $mappings[$path],
 257              array('name' => basename($path)));
 258          }
 259          PhragmentFragment::createFromFile(
 260            $viewer,
 261            $file,
 262            $base_path.'/'.$path,
 263            $this->getViewPolicy(),
 264            $this->getEditPolicy());
 265        }
 266      }
 267    }
 268  
 269    /**
 270     * Delete the contents of the specified fragment.
 271     */
 272    public function deleteFile(PhabricatorUser $viewer) {
 273      $existing = id(new PhragmentFragmentVersionQuery())
 274        ->setViewer($viewer)
 275        ->withFragmentPHIDs(array($this->getPHID()))
 276        ->execute();
 277      $sequence = count($existing);
 278  
 279      $this->openTransaction();
 280        $version = id(new PhragmentFragmentVersion());
 281        $version->setSequence($sequence);
 282        $version->setFragmentPHID($this->getPHID());
 283        $version->setFilePHID(null);
 284        $version->save();
 285  
 286        $this->setLatestVersionPHID($version->getPHID());
 287        $this->save();
 288      $this->saveTransaction();
 289    }
 290  
 291  
 292  /* -(  Utility  )  ---------------------------------------------------------- */
 293  
 294  
 295    public function getFragmentMappings(
 296      PhabricatorUser $viewer,
 297      $base_path) {
 298  
 299      $children = id(new PhragmentFragmentQuery())
 300        ->setViewer($viewer)
 301        ->needLatestVersion(true)
 302        ->withLeadingPath($this->getPath().'/')
 303        ->withDepths(array($this->getDepth() + 1))
 304        ->execute();
 305  
 306      if (count($children) === 0) {
 307        $path = substr($this->getPath(), strlen($base_path) + 1);
 308        return array($path => $this);
 309      } else {
 310        $mappings = array();
 311        foreach ($children as $child) {
 312          $child_mappings = $child->getFragmentMappings(
 313            $viewer,
 314            $base_path);
 315          foreach ($child_mappings as $key => $value) {
 316            $mappings[$key] = $value;
 317          }
 318        }
 319        return $mappings;
 320      }
 321    }
 322  
 323  
 324  /* -(  Policy Interface  )--------------------------------------------------- */
 325  
 326  
 327    public function getCapabilities() {
 328      return array(
 329        PhabricatorPolicyCapability::CAN_VIEW,
 330        PhabricatorPolicyCapability::CAN_EDIT,
 331      );
 332    }
 333  
 334    public function getPolicy($capability) {
 335      switch ($capability) {
 336        case PhabricatorPolicyCapability::CAN_VIEW:
 337          return $this->getViewPolicy();
 338        case PhabricatorPolicyCapability::CAN_EDIT:
 339          return $this->getEditPolicy();
 340      }
 341    }
 342  
 343    public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
 344      return false;
 345    }
 346  
 347    public function describeAutomaticCapability($capability) {
 348      return null;
 349    }
 350  
 351  }


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