00001 <?php
00002
00026 final class SnapDirectory extends SnapFile {
00027 public static $ATTRIBUTES = array('id', 'name', 'description', 'created', 'modified',
00028 'canonParentId', 'refCount', 'oldParentId');
00029
00031 public function __construct($id = "") {
00032 parent::__construct('Directory', array_merge(array_flip(self::$ATTRIBUTES),
00033 array('maxDirOrdinal' => array($this, 'loadMaxDirOrdinal')),
00034 array('maxResOrdinal' => array($this, 'loadMaxResOrdinal'))),
00035 self::$ATTRIBUTES, $id);
00036 }
00037
00039 public function toCacheDump() {
00040 $name = $this->get('name');
00041 $cparent = $this->get('canonParentId');
00042 return "Name: $name; Canon. Parent: $cparent";
00043 }
00044
00056 protected function doDestroy() {
00057
00058 $contents = $this->listContents();
00059 foreach($contents as $c) {
00060 if(count($c->getParents()) > 1)
00061 $c->unlinkFrom(-$this->getId());
00062 else
00063 $c->delete();
00064 }
00065 return true;
00066 }
00067
00080 protected function loadMaxDirOrdinal() {
00081 $id = $this->get('id');
00082 $query = "SELECT MAX(ordinal) FROM DirectoryLink WHERE parentId = $id";
00083 if(!($result = SnapDBI::query($query, true))) {
00084 $this->setError('Could not load maximum directory ordinal for this directory');
00085 return false;
00086 }
00087 $this->set('maxDirOrdinal', $result[0]['MAX(ordinal)']);
00088 SnapCache::updateById($this->getType(), $this->getId());
00089 return true;
00090 }
00091
00100 protected function loadMaxResOrdinal() {
00101 $id = $this->get('id');
00102 $query = "SELECT MAX(ordinal) FROM ResourceLink WHERE parentId = $id";
00103 if(!($result = SnapDBI::query($query, true))) {
00104 $this->setError('Could not load maximum resource ordinal for this directory');
00105 return false;
00106 }
00107 $this->set('maxResOrdinal', $result[0]['MAX(ordinal)']);
00108 SnapCache::updateById($this->getType(), $this->getId());
00109 return true;
00110 }
00111
00112
00113
00114
00115
00116
00117
00118
00119
00128 public static function retrieve($id) {
00129
00130 if(!Snap2::checkInit()) return null;
00131
00132
00133 if(!ctype_digit((string) $id) || $id <= 0) {
00134 self::setStaticError('ID must be numeric');
00135 return null;
00136 }
00137
00138
00139 $dir = SnapCache::getById('Directory', $id);
00140 if($dir != null)
00141 return $dir;
00142
00143
00144 $dir = new SnapDirectory($id);
00145 if(!($dir->getId() > 0))
00146 return null;
00147
00148
00149 SnapCache::putById('Directory', $id, $dir);
00150
00151 return $dir;
00152 }
00153
00162 public static function lookup($path) {
00163
00164 if(!Snap2::checkInit()) return null;
00165
00166
00167
00168 if(!preg_match('/^\s*((\/[a-zA-Z0-9\$\-_.]*)+\/)\s*$/', $path, $matches)) {
00169 self::setStaticError('Invalid path: \'' . $path . '\'');
00170 return null;
00171 }
00172
00173
00174 $path = $matches[1];
00175
00176
00177
00178
00179 $dir = SnapCache::getByPath('Directory', $path);
00180 if($dir != null)
00181 return $dir;
00182
00183
00184 $pathParts = explode('/', $path);
00185
00186
00187
00188
00189 $query = 'SELECT ' . implode(',', self::$ATTRIBUTES) . ' FROM Directory';
00190 $where = ' WHERE ';
00191 for($i = 1, $j = count($pathParts) - 2; $i < count($pathParts) - 1; $i++, $j--) {
00192 $query .= ' LEFT JOIN DirectoryLink AS DirectoryLink' . $i . ' ON DirectoryLink' . $i
00193 . '.childId = ' . ($i == 1 ? 'Directory.id' : ('DirectoryLink' . ($i - 1) . '.parentId'));
00194 $where .= ' DirectoryLink' . $j . '.shortName = BINARY \'' . $pathParts[$i] . '\' AND ';
00195 }
00196 $query .= rtrim($where, ' AND ');
00197 $result = SnapDBI::query($query, true);
00198 if($result === false)
00199 return null;
00200
00201 if(count($result) == 0)
00202 return null;
00203 else if(count($result) != 1)
00204 self::setStaticWarning('Multiple results for path \'' . $path . '\'! Run Snap consistency check please.');
00205
00206 // now we get to check if we already have this directory in the cache, but with a different
00207 // path associated with it
00208 $dir = SnapCache::getById('Directory', $result[0]['id']);
00209 if($dir != null) {
00210 // ...and make sure to add this new path to the cache
00211 SnapCache::putByPath('Directory', $path, $dir);
00212 return $dir;
00213 }
00214
00215 // fill in the directory object with info from the query
00216 $dir = new SnapDirectory();
00217 foreach(self::$ATTRIBUTES as $a)
00218 $dir->set($a, $result[0][$a]);
00219
00220 // and save in the cache, by id and by path
00221 SnapCache::putById('Directory', $dir->getId(), $dir);
00222 SnapCache::putByPath('Directory', $path, $dir);
00223
00224 return $dir;
00225 }
00226
00227 /*
00228 * Capability methods
00229 */
00230
00254 public function canCreateDirectory($shortName = false) {
00255 if($shortName !== false) {
00256 if($shortName == '' || !preg_match('/^[a-zA-Z0-9\_\-\.]+$/', $shortName)) {
00257 $this->setReason("Invalid shortName: '$shortName'");
00258 return false;
00259 }
00260 $shortName = substr($shortName, 0, 64);
00261
00262 $query = 'SELECT shortName FROM DirectoryLink WHERE '
00263 . 'DirectoryLink.parentId = ' . $this->getId() . ' AND shortName = BINARY \'' . $shortName . '\'';
00264
00265 if(!SnapDBI::query($query))
00266 return false;
00267
00268
00269
00270 if(SnapDBI::getNumRows() != 0) {
00271 SnapDBI::freeResult();
00272 $this->setReason("Subdirectory '$shortName' already exists!");
00273 return false;
00274 }
00275 SnapDBI::freeResult();
00276 }
00277
00278 if(!$this->getPermission()->mayCreateDirectory()) {
00279 $this->setReason('Permission denied');
00280 return false;
00281 }
00282
00283 return true;
00284 }
00285
00314 public function canCreateResource($shortName = false, $contentType = SnapResource::TYPE_STDXML) {
00315 if($shortName !== false) {
00316 if($shortName == '' || !preg_match('/^[a-zA-Z0-9\_\-\.]+$/', $shortName)) {
00317 $this->setReason("Invalid shortName: '$shortName'");
00318 return false;
00319 }
00320 $shortName = substr($shortName, 0, 64);
00321
00322 $query = 'SELECT shortName FROM ResourceLink WHERE '
00323 . 'ResourceLink.parentId = ' . $this->getId() . ' AND shortName = BINARY \'' . $shortName . '\'';
00324 if(!SnapDBI::query($query))
00325 return false;
00326
00327 if(SnapDBI::getNumRows() != 0) {
00328 SnapDBI::freeResult();
00329 $this->setReason("Subresource '$shortName' already exists!");
00330 return false;
00331 }
00332 SnapDBI::freeResult();
00333 }
00334
00335 if(!ctype_digit((string) $contentType) || !SnapContent::isValidType($contentType)) {
00336 $this->setReason('Invalid content type: ' . $contentType);
00337 return false;
00338 }
00339
00340 if(!$this->getPermission()->mayCreateResource()) {
00341 $this->setReason('Permission denied');
00342 return false;
00343 }
00344
00345 return true;
00346 }
00347
00348
00349
00350
00351
00366 public function createDirectory($shortName, $name = "", $description = "") {
00367 if(!$this->checkValid()) return false;
00368
00369
00370
00371 if(!SnapDBI::startTransaction())
00372 return null;
00373
00374 if($name == "")
00375 $name = $shortName;
00376
00377 if($shortName === false || !$this->canCreateDirectory($shortName)) {
00378 $this->setError('Cannot create directory because '. $this->getReason());
00379 SnapDBI::cancelTransaction();
00380 return false;
00381 }
00382
00383 $name = mysql_escape_string($name);
00384 $description = mysql_escape_string($description);
00385
00386
00387 $query = "INSERT INTO Directory (name, description, created, canonParentId, refCount) VALUES "
00388 . "('$name', '$description', CURRENT_TIMESTAMP, " . $this->getId() . ", 0)";
00389 if(!SnapDBI::query($query)) {
00390 SnapDBI::cancelTransaction();
00391 return null;
00392 }
00393 $id = SnapDBI::getInsertId();
00394
00395
00396 if(!SnapDirectoryUserPermission::copy($this->getId(), $id) ||
00397 !SnapDirectoryGroupPermission::copy($this->getId(), $id))
00398 {
00399 SnapDBI::cancelTransaction();
00400 return null;
00401 }
00402
00403 $dir = new SnapDirectory();
00404 $dir->set('id', $id);
00405
00406
00407
00408 if(!$dir->mklink($this->getId(), $shortName, true))
00409 return null;
00410
00411
00412 SnapDBI::commitTransaction();
00413
00414
00415 SnapCache::putById('Directory', $id, $dir);
00416
00417 return $dir;
00418 }
00419
00434 public function createResource($shortName, $contentType = SnapResource::TYPE_STDXML, $name = "", $description = "") {
00435 if(!$this->checkValid()) return false;
00436
00437 if(!SnapDBI::startTransaction())
00438 return null;
00439
00440 if($name == "")
00441 $name = $shortName;
00442
00443 if($shortName === false || !$this->canCreateResource($shortName, $contentType)) {
00444 $this->setError('Cannot create resource because ' . $this->getReason());
00445 SnapDBI::cancelTransaction();
00446 return false;
00447 }
00448
00449 $name = mysql_escape_string($name);
00450 $description = mysql_escape_string($description);
00451
00452 $query = "INSERT INTO Resource (name, description, created, canonParentId, refCount, contentType, nextOrdinal) VALUES "
00453 . "('$name', '$description', CURRENT_TIMESTAMP, " . $this->getId() . ", 0, '$contentType', 1)";
00454 if(!SnapDBI::query($query)) {
00455 SnapDBI::cancelTransaction();
00456 return null;
00457 }
00458 $id = SnapDBI::getInsertId();
00459
00460 if(!SnapResourceUserPermission::copy($this->getId(), $id) ||
00461 !SnapResourceGroupPermission::copy($this->getId(), $id))
00462 {
00463 SnapDBI::cancelTransaction();
00464 return null;
00465 }
00466
00467 $res = new SnapResource();
00468 $res->set('id', $id);
00469
00470 if(!$res->mklink($this->getId(), $shortName, true))
00471 return null;
00472
00473 SnapDBI::commitTransaction();
00474
00475 SnapCache::putById('Resource', $id, $res);
00476
00477 return $res;
00478 }
00479
00501 public function listContents($constraint = array(), $limit = array(), $order = array(), $count = false) {
00502 if(!$this->checkValid()) return false;
00503
00504 $canonPath = $this->getCanonicalPath();
00505
00506 if(!$count) {
00507
00508
00509
00510
00511 $numNulls = count(SnapResource::$ATTRIBUTES);
00512 $nulls = "";
00513
00514
00515 for($i = count(self::$ATTRIBUTES); $i < $numNulls; $i++)
00516 $nulls .= "NULL AS " . SnapResource::$ATTRIBUTES[$i] . ',';
00517 $nulls = rtrim($nulls, ',');
00518 }
00519
00520 if($count) {
00521 $dirAttrs = 'COUNT(*) AS dirCount, NULL AS resCount';
00522 $resAttrs = 'NULL AS dirCount, COUNT(*) AS resCount';
00523 }
00524 else {
00525 $dirAttrs = implode(',', array('parentId', 'childId', 'shortName', 'ordinal'))
00526 . ',' . implode(',', self::$ATTRIBUTES) . ',' . $nulls;
00527 $resAttrs = implode(',', array('parentId', 'childId', 'shortName', 'ordinal'))
00528 . ',' . implode(',', SnapResource::$ATTRIBUTES);
00529 }
00530
00531
00532
00533
00534 if($count) {
00535 $query = 'SELECT * FROM (SELECT ' . $dirAttrs
00536 . ',\'Directory\' AS fileType FROM Directory '
00537 . 'LEFT JOIN DirectoryLink ON DirectoryLink.childId = Directory.id '
00538 . 'WHERE DirectoryLink.parentId = ' . $this->getId()
00539 . ' AND DirectoryLink.parentId <> DirectoryLink.childId '
00540 . 'UNION SELECT ' . $resAttrs . ',\'Resource\' AS fileType FROM Resource '
00541 . 'LEFT JOIN ResourceLink ON ResourceLink.childId = Resource.id '
00542 . 'WHERE ResourceLink.parentId = ' . $this->getId() . ') AS t1 ';
00543 }
00544 else {
00545 $query = 'SELECT * FROM (SELECT ' . $dirAttrs
00546 . ',\'Directory\' AS fileType, path FROM Directory '
00547 . 'LEFT JOIN DirectoryLink ON DirectoryLink.childId = Directory.id '
00548 . 'LEFT JOIN PathCache ON PathCache.directoryId = Directory.id '
00549 . 'WHERE DirectoryLink.parentId = ' . $this->getId()
00550 . ' AND DirectoryLink.parentId <> DirectoryLink.childId GROUP BY Directory.id '
00551 . 'UNION SELECT ' . $resAttrs . ',\'Resource\' AS fileType, path FROM Resource '
00552 . 'LEFT JOIN ResourceLink ON ResourceLink.childId = Resource.id '
00553 . 'LEFT JOIN PathCache ON PathCache.resourceId = Resource.id '
00554 . 'WHERE ResourceLink.parentId = ' . $this->getId() . ' GROUP BY Resource.id) AS t1 ';
00555 }
00556
00557 $validAttrs = array_merge(self::$ATTRIBUTES, SnapResource::$ATTRIBUTES,
00558 array('parentId', 'childId', 'shortName', 'ordinal', 'fileType'));
00559
00560
00561 $where = SnapDBI::criteriaToString($constraint, $validAttrs);
00562 if($where === false) return null;
00563 else if($where != '') $query .= ' WHERE ' . $where;
00564
00565 if(!$count) {
00566 $order = SnapDBI::orderToString($order, $validAttrs);
00567 if($order === false) return null;
00568 else if($order != '') $query .= ' ' . $order;
00569
00570 $limit = SnapDBI::limitToString($limit);
00571 if($limit === false) return null;
00572 else if($limit != '') $query .= ' ' . $limit;
00573 }
00574
00575 if(!SnapDBI::query($query)) {
00576 print_r(SnapDBI::getStaticError());
00577 $this->setError("Invalid query");
00578 return null;
00579 }
00580
00581 if($count) {
00582 $dirCount = 0;
00583 $resCount = 0;
00584 while($row = SnapDBI::getRow()) {
00585 if($row['fileType'] == 'Directory')
00586 $dirCount = $row['dirCount'];
00587 else
00588 $resCount = $row['resCount'];
00589 }
00590 SnapDBI::freeResult();
00591 $res = array('directories' => $dirCount, 'resources' => $resCount);
00592 return $res;
00593 }
00594
00595 $ret = array();
00596 while($row = SnapDBI::getRow()) {
00597 if($row['fileType'] == 'Directory') {
00598 $obj = SnapCache::getById('Directory', $row['id']);
00599 if($obj == null) {
00600 $obj = new SnapDirectory();
00601 foreach(self::$ATTRIBUTES as $a)
00602 $obj->set($a, $row[$a]);
00603 SnapCache::putById('Directory', $row['id'], $obj);
00604 SnapCache::putByPath('Directory', $canonPath . $row['shortName'] . '/', $obj);
00605 }
00606 }
00607 else if($row['fileType'] == 'Resource') {
00608 $obj = SnapCache::getById('Resource', $row['id']);
00609 if($obj == null) {
00610 $obj = new SnapResource();
00611 foreach(SnapResource::$ATTRIBUTES as $a)
00612 $obj->set($a, $row[$a]);
00613 SnapCache::putById('Resource', $row['id'], $obj);
00614 SnapCache::putByPath('Resource', $canonPath . $row['shortName'], $obj);
00615 }
00616 }
00617 $ret[] = $obj;
00618 }
00619 SnapDBI::freeResult();
00620
00621 SnapPermissionCache::warmCache('Directory', $this->getId(), 'Directory');
00622 SnapPermissionCache::warmCache('Directory', $this->getId(), 'Resource');
00623
00624 return $ret;
00625 }
00626
00636 public function countContents($constraint = array(), $limit = array(), $order = array()) {
00637 return $this->listContents($constraint, $limit, $order, true);
00638
00639
00640
00641
00642
00643
00644
00645
00646
00647
00648
00649
00650
00651
00652
00653
00654
00655
00656
00657
00658
00659
00660
00661
00662
00663
00664
00665
00666
00667
00668
00669
00670
00671
00672
00673
00674
00675
00676
00677
00678
00679
00680
00681
00682
00683
00684
00685
00686 }
00687 }
00688
00689 ?>