00001 <?php
00002
00035 abstract class SnapFile extends SnapObject {
00036
00038 const MOVE_UP = -1;
00040 const MOVE_DOWN = -2;
00042 const MOVE_TOP = -3;
00044 const MOVE_BOTTOM = -4;
00045
00046 private $canonicalShortName = null;
00047
00052 public function __construct($type, $fields, $attributes, $id) {
00053 $myfields = $fields;
00054 $myfields['parents'] = array($this, 'populate');
00055 $myfields['ordinals'] = array($this, 'populate');
00056 $myfields['shortNames'] = array($this, 'populate');
00057 $myfields['canonicalPath'] = array($this, 'loadCanonicalPath');
00058 $myfields['canonicalParent'] = array($this, 'loadCanonicalParent');
00059 $myfields['permission'] = array($this, 'loadPermission');
00060 $myfields['metadata'] = array($this, 'loadMetadata');
00061 parent::__construct($type, $myfields, $attributes);
00062 if($id != "") {
00063 $this->set('id', $id);
00064 $this->populate();
00065 }
00066 $this->setSave('canonicalParent', false);
00067 $this->setSave('permission', false);
00068 }
00069
00070
00071
00072
00073
00079 protected function populate() {
00080 if(!$this->checkValid()) return false;
00081
00082 $id = $this->get('id');
00083
00084 if(!ctype_digit((string) $id) || $id < 1) {
00085 $this->setError("Invalid ID in populate: '$id'");
00086 $this->setValid(false);
00087 return false;
00088 }
00089
00090 $attributes = $this->getAttributes();
00091 $type = $this->getType();
00092 $linkTable = $type . "Link";
00093
00094 $childAttrs = "";
00095 foreach($attributes as $a)
00096 $childAttrs .= $type. ".$a,";
00097 $query = "SELECT $childAttrs shortName,parentId,childId,ordinal FROM $type "
00098 . "LEFT JOIN $linkTable ON $linkTable.childId = $type.id "
00099 . "WHERE $type" . ".id = $id";
00100 $result = SnapDBI::query($query, true);
00101 if($result === false) {
00102 $this->setError("Could not populate due to DB error");
00103 return false;
00104 }
00105
00106 if(count($result) == 0) {
00107 $this->setValid(false);
00108 return false;
00109 }
00110 foreach($attributes as $a)
00111 $this->set($a, $result[0][$a]);
00112
00113 $parents = array();
00114 $ordinals = array();
00115 $shortNames = array();
00116
00117 foreach($result as $r) {
00118 $parentId = $r['parentId'];
00119 $shortNames[$parentId] = $r['shortName'];
00120 $ordinals[$parentId] = $r['ordinal'];
00121 if($parentId == $r['childId'] && $this->getType() == 'Directory')
00122 continue;
00123 $parents[$parentId] = false;
00124 }
00125
00126 $this->set('parents', $parents);
00127 $this->set('ordinals', $ordinals);
00128 $this->set('shortNames', $shortNames);
00129
00130 SnapCache::updateById($this->getType(), $this->getId());
00131
00132 $this->setValid(true);
00133
00134 return true;
00135 }
00136
00142 protected function loadCanonicalParent() {
00143 $cparent = $this->get('canonParentId');
00144 if($cparent == '')
00145 return false;
00146 $this->set('canonicalParent', SnapDirectory::retrieve($cparent));
00147 SnapCache::updateById($this->getType(), $this->getId());
00148 return true;
00149 }
00150
00156 protected function loadCanonicalPath() {
00157
00158 $cachedPath = null;
00159 if(!$cachedPath) {
00160 $par = $this->getCanonicalParent();
00161 if(!$par) {
00162
00163 switch($this->getId()) {
00164 case Snap2::ROOT_DIR_ID:
00165 $parPath = '//';
00166 break;
00167 case Snap2::DELETED_DIR_ID:
00168 $parPath = '/DELETED/';
00169 break;
00170 default:
00171 $this->setError('No canonical parent for ' . $this->getType() . ' with ID '
00172 . $this->getId());
00173 return false;
00174 }
00175 $this->set('canonicalPath', $parPath);
00176 return true;
00177 }
00178
00179 $paths = SnapCache::getPathsById($this->getType(), $this->getId());
00180 if($paths && isset($paths[0])) {
00181 return $parPath = $paths[0];
00182 }
00183 else {
00184 $parPath = $par->getCanonicalPath();
00185 $parPath .= $this->getCanonicalShortName();
00186 if($this->getType() == 'Directory')
00187 $parPath .= '/';
00188 SnapCache::putByPath($this->getType(), $parPath, $this, true);
00189 }
00190 }
00191 else
00192 $parPath = $cachedPath;
00193
00194 $this->set('canonicalPath', $parPath);
00195 SnapCache::updateById($this->getType(), $this->getId());
00196
00197 return true;
00198 }
00199
00205 protected function loadPermission() {
00206 $key = $this->getType() . $this->getId();
00207 $perm = SnapCache::getById('Permission', $key);
00208 if(!$perm) {
00209 $perm = new SnapPermission($this);
00210 SnapCache::putById('Permission', $key, $perm);
00211 }
00212
00213 $this->set('permission', $perm);
00214 SnapCache::updateById($this->getType(), $this->getId());
00215
00216 return true;
00217 }
00218
00226 protected function loadMetadata() {
00227 $key = $this->getType() . $this->getId();
00228 $md = SnapCache::getById('Metadata', $key);
00229 if(!$md) {
00230 $md = new SnapMetadata($this);
00231 SnapCache::putById('Metadata', $key, $md);
00232 }
00233
00234 $this->set('metadata', $md);
00235 SnapCache::updateById($this->getType(), $this->getId());
00236
00237 return true;
00238 }
00239
00248 protected function setCanonicalShortName($csn) {
00249 $this->canonicalShortName = $csn;
00250 }
00251
00252
00253
00254
00255
00264 private function checkForCycles($potLink) {
00265 $seenAlready = array($potLink->getId() => true);
00266 if($this->getType() == 'Directory')
00267 $seenAlready[$this->getId()] = true;
00268
00269 $cur = $potLink;
00270 while($cur->getId() != 1 && $cur->getId() != 2) {
00271 $cur = $cur->getCanonicalParent();
00272 if(isset($seenAlready[$cur->getId()]))
00273 return false;
00274 $seenAlready[$cur->getId()] = true;
00275 }
00276
00277 return true;
00278 }
00279
00294 protected function mklink($targetId, $name, $first = false) {
00295 if(!$this->checkValid()) return false;
00296
00297 $type = $this->getType();
00298 $id = $this->getId();
00299 $linkTable = $type . "Link";
00300
00301 if(!SnapDBI::startTransaction())
00302 return false;
00303
00304 if(!$first) {
00305 $parents = $this->get('parents');
00306 if(isset($parents[$targetId])) {
00307 $this->setError('Link to \'' . $targetId . '\' already exists');
00308 SnapDBI::cancelTransaction();
00309 return false;
00310 }
00311 }
00312
00313 $tgt = SnapDirectory::retrieve($targetId);
00314 if(!$this->checkForCycles($tgt)) {
00315 $this->setError('Creating this link would create a cycle; operation aborted');
00316 SnapDBI::cancelTransaction();
00317 return false;
00318 }
00319
00320 $query = "UPDATE $type SET refCount=refCount+1 WHERE id=$id";
00321 if(!SnapDBI::query($query)) {
00322 $this->setError("Failed to update reference count");
00323 SnapDBI::cancelTransaction();
00324 return false;
00325 }
00326
00327 $query = "SELECT COUNT(*) FROM $linkTable WHERE parentId = $targetId";
00328 if($this->getType() == 'Directory')
00329 $query .= " AND parentId <> childId";
00330 if(!SnapDBI::query($query)) {
00331 $this->setError("Failed to get next ordinal");
00332 SnapDBI::cancelTransaction();
00333 return false;
00334 }
00335 $ordinal = SnapDBI::firstRow();
00336 $ordinal = $ordinal['COUNT(*)'] + 1;
00337 SnapDBI::freeResult();
00338
00339 $query = "INSERT INTO $linkTable SET parentId=$targetId,childId=$id,shortName='$name',ordinal=$ordinal";
00340 if(!SnapDBI::query($query)) {
00341 $this->setError("Failed to add link to linkage table");
00342 SnapDBI::cancelTransaction();
00343 return false;
00344 }
00345
00346 $parents = $this->get('parents');
00347 $ordinals = $this->get('ordinals');
00348 $shortNames = $this->get('shortNames');
00349
00350 $oldParents = $parents;
00351 $oldOrdinals = $ordinals;
00352 $oldShortNames = $shortNames;
00353
00354 $parents[$targetId] = false;
00355 $ordinals[$targetId] = $ordinal;
00356 $shortNames[$targetId] = $name;
00357
00358 $this->set('parents', $parents);
00359 $this->set('ordinals', $ordinals);
00360 $this->set('shortNames', $shortNames);
00361
00362 $paths = $this->getPaths(-$targetId);
00363 if(!SnapPathCache::addPathSet($paths, $this->getId(), $this->getType())) {
00364 $this->set('parents', $oldParents);
00365 $this->set('shortNames', $oldShortNames);
00366 $this->set('ordinals', $oldOrdinals);
00367 SnapDBI::cancelTransaction();
00368 return false;
00369 }
00370
00371 SnapDBI::commitTransaction();
00372
00373 SnapCache::updateById($this->getType(), $this->getId());
00374
00375 return true;
00376 }
00377
00386 protected function rmlink($targetId) {
00387 if(!$this->checkValid()) return false;
00388
00389 $type = $this->getType();
00390 $id = $this->getId();
00391 $linkTable = $type . "Link";
00392
00393 if(!SnapDBI::startTransaction())
00394 return false;
00395
00396 $oldOrd = $this->getOrdinal(-$targetId);
00397 $oldParentIds = $this->getParents();
00398 $canonPath = $this->getCanonicalPath();
00399 $oldPaths = $this->getPaths(-$targetId);
00400
00401 $query = "DELETE FROM $linkTable WHERE parentId=$targetId AND childId=$id";
00402 if(!SnapDBI::query($query)) {
00403 $this->setError("Could not remove linkage information");
00404 SnapDBI::cancelTransaction();
00405 return false;
00406 }
00407
00408 $query = "UPDATE $linkTable SET ordinal=ordinal-1 WHERE parentId=$targetId AND ordinal > $oldOrd";
00409 if(!SnapDBI::query($query)) {
00410 $this->setError("Could not update ordinals in parent directory");
00411 SnapDBI::cancelTransaction();
00412 return false;
00413 }
00414
00415 $query = "UPDATE $type SET refCount=refCount-1 WHERE id=$id";
00416 if(!SnapDBI::query($query)) {
00417 $this->setError("Could not decrement reference count");
00418 SnapDBI::cancelTransaction();
00419 return false;
00420 }
00421
00422 $query = "SELECT refCount FROM $type WHERE id=$id";
00423 if(!SnapDBI::query($query)) {
00424 $this->setError("Could not read back reference count");
00425 SnapDBI::cancelTransaction();
00426 return false;
00427 }
00428
00429 $tmp = SnapDBI::firstRow();
00430 $refCount = $tmp['refCount'];
00431 SnapDBI::freeResult();
00432
00433 if($refCount == 0) {
00434 if($this->isCanonChildOf(Snap2::DELETED_DIR_ID)) {
00435 if(!$this->doDestroy()) {
00436 SnapDBI::cancelTransaction();
00437 return false;
00438 }
00439 $query = 'DELETE FROM ' . $type . ' WHERE id = ' . $id;
00440 if(!SnapDBI::query($query)) {
00441 $this->setError("Could not delete actual object");
00442 SnapDBI::cancelTransaction();
00443 return false;
00444 }
00445 SnapPathCache::removePathSet($this->getPaths(-$targetId));
00446 SnapDBI::commitTransaction();
00447 SnapCache::deleteById($type, $id);
00448 $this->setValid(false);
00449 SnapDBI::query($query);
00450 }
00451 else {
00452 $oldParentId = array_shift($oldParentIds);
00453 $query = 'UPDATE ' . $this->getType() . ' SET oldParentId = ' . $oldParentId
00454 . ' WHERE id = ' . $this->getId();
00455 if(!SnapDBI::query($query)) {
00456 SnapDBI::cancelTransaction();
00457 return false;
00458 }
00459 $this->set('oldParentId', $oldParentId);
00460 if(!$this->mklink(Snap2::DELETED_DIR_ID, $this->getCanonicalShortName() . '$' . substr($this->getType(), 0, 1)
00461 . $this->getId()))
00462 {
00463 SnapDBI::cancelTransaction();
00464 return false;
00465 }
00466 if(!$this->chcanonparent(Snap2::DELETED_DIR_ID)) {
00467 SnapDBI::cancelTransaction();
00468 return false;
00469 }
00470 $this->set('canonParentId', Snap2::DELETED_DIR_ID);
00471 $this->unsetField('canonicalParent');
00472 SnapCache::deleteByPath($this->getType(), $canonPath);
00473 $this->unsetField('canonicalPath');
00474 if($this->getType() == 'Directory') {
00475 $isEnabled = SnapCache::usingCache();
00476 Snap2::setEnableCache(false, false);
00477 $this->recReparentPseudoOrphans($this->getId());
00478 if($isEnabled)
00479 Snap2::setEnableCache(true, false);
00480 }
00481 SnapDBI::commitTransaction();
00482 SnapCache::updateById($this->getType(), $this->getId());
00483 }
00484 }
00485 else if($refCount < 0) {
00486 $this->setError("Reference count is negative: run snap2 consistency check!");
00487 SnapDBI::cancelTransaction();
00488 return false;
00489 }
00490 else {
00491 $cpId = $this->get('canonParentId');
00492 // have to change canonical parent because this link (which is the canonical parent link)
00493 // is going away
00494 if($cpId == $targetId) {
00495 $parents = $this->get('parents');
00496 reset($parents);
00497 $newCanon = key($parents);
00498 if($newCanon == $targetId) {
00499 next($parents);
00500 $newCanon = key($parents);
00501 }
00502 $cparent = $this->get('canonicalPath');
00503 // copied mostly from chcanonparent() [it would be too hard to call that function from here]
00504 $query = "UPDATE $type SET canonParentId = $newCanon WHERE id = $id";
00505 if(!SnapDBI::query($query)) {
00506 $this->setError("Could not change canonical parent");
00507 SnapDBI::cancelTransaction();
00508 return false;
00509 }
00510 $this->set('canonParentId', $newCanon);
00511 $this->unsetField('canonicalParent');
00512 SnapCache::deleteByPath($this->getType(), $cparent);
00513 $this->unsetField('canonicalPath');
00514 # $noPCUpdate = true;
00515 }
00516
00517 SnapPathCache::updatePathSet($this->getPaths(-$targetId), $oldPaths);
00518 if(SnapDBI::commitTransaction() === false)
00519 return false;
00520
00521 if($this->isLoaded('parents')) {
00522 $parents = $this->get('parents');
00523 $ordinals = $this->get('ordinals');
00524 $shortNames = $this->get('shortNames');
00525 unset($parents[$targetId]);
00526 unset($shortNames[$targetId]);
00527 unset($ordinals[$targetId]);
00528 $this->set('parents', $parents);
00529 $this->set('ordinals', $ordinals);
00530 $this->set('shortNames', $shortNames);
00531 }
00532
00533 SnapCache::deleteById($type, $id);
00534 SnapCache::putById($type, $id, $this);
00535 }
00536
00537 if(!isset($noPCUpdate))
00538 SnapPathCache::removePathSet($oldPaths);
00539
00540 return true;
00541 }
00542
00543 // go through and change the canonical parent of files below this one
00544 // that have links outside the /DELETED/ directory
00553 private function recReparentPseudoOrphans($parentId) {
00554 $query = 'SELECT childId FROM DirectoryLink WHERE parentId = ' . $parentId;
00555 $results = SnapDBI::query($query, true);
00556 if($results === false) {
00557 SnapDBI::cancelTransaction();
00558 return false;
00559 }
00560
00561 foreach($results as $child) {
00562 if(!$this->recReparentPseudoOrphans($child['childId']))
00563 return false;
00564 }
00565
00566 $query = <<<END_QUERY
00567 SELECT Directory.id, DirectoryLink.parentId, DirectoryLink.shortName FROM DirectoryLink
00568 LEFT JOIN Directory ON Directory.id = DirectoryLink.childId
00569 WHERE DirectoryLink.childId IN(
00570 SELECT childId FROM DirectoryLink
00571 WHERE refCount > 1 AND DirectoryLink.parentId = $parentId
00572 )
00573 AND DirectoryLink.parentId != $parentId
00574 GROUP BY Directory.id;
00575 END_QUERY;
00576
00577 $results = SnapDBI::query($query, true);
00578 if($results === false) {
00579 SnapDBI::cancelTransaction();
00580 return false;
00581 }
00582
00583 if(count($results) > 0)
00584 foreach($results as $row) {
00585 $parent = SnapDirectory::retrieve($row['parentId']);
00586 $newPath = $parent->getCanonicalPath() . $row['shortName'] . '/';
00587 $query = 'UPDATE Directory SET canonParentId = ' . $row['parentId'] . ' WHERE id = ' . $row['id'];
00588 $query2 = 'REPLACE INTO PathCache (path, directoryId) SELECT \'' . $newPath
00589 . '\', directoryId FROM PathCache WHERE path LIKE BINARY \'' . $parent->getCanonicalPath()
00590 . '%\' AND directoryId = ' . $row['id'];
00591 if(!SnapDBI::query($query) || !SnapDBI::query($query2)) {
00592 SnapDBI::cancelTransaction();
00593 return false;
00594 }
00595 }
00596
00597 $query = <<<END_QUERY
00598 SELECT Resource.id, ResourceLink.parentId FROM ResourceLink LEFT JOIN Resource ON Resource.id = ResourceLink.childId
00599 WHERE ResourceLink.childId IN(
00600 SELECT childId FROM ResourceLink
00601 WHERE refCount > 1 AND ResourceLink.parentId = $parentId
00602 )
00603 AND ResourceLink.parentId != $parentId
00604 GROUP BY Resource.id;
00605 END_QUERY;
00606
00607 $results = SnapDBI::query($query, true);
00608 if($results === false) {
00609 SnapDBI::cancelTransaction();
00610 return false;
00611 }
00612
00613 if(count($results) > 0)
00614 foreach($results as $row) {
00615 $parent = SnapResource::retrieve($row['parentId']);
00616 $newPath = $parent->getCanonicalPath() . $row['shortName'];
00617 $query = 'UPDATE Resource SET canonParentId = ' . $row['parentId'] . ' WHERE id = ' . $row['id'];
00618 $query2 = 'UPDATE PathCache SET path = \'' . $newPath . '\' WHERE path LIKE BINARY \''
00619 . $parent->getCanonicalPath() . '%\' AND resourceId = ' . $row['id'];
00620 if(!SnapDBI::query($query) || !SnapDBI::query($query2)) {
00621 SnapDBI::cancelTransaction();
00622 return false;
00623 }
00624 }
00625
00626 return true;
00627 }
00628
00633 protected abstract function doDestroy();
00634
00641 protected function chcanonparent($newParent) {
00642 if(!$this->checkValid()) return false;
00643
00644 $canonPath = $this->getCanonicalPath();
00645
00646
00647 $parents = $this->get('parents');
00648 if(!isset($parents[$newParent])) {
00649 $this->setError('Cannot change canonical parent to \'' . $newParent
00650 . '\' because it is not an actual parent of this object');
00651 return false;
00652 }
00653
00654 if(!SnapDBI::startTransaction())
00655 return false;
00656
00657 $query = "UPDATE " . $this->getType() . " SET canonParentId = '$newParent' WHERE id = "
00658 . $this->getId();
00659 if(!SnapDBI::query($query)) {
00660 $this->setError("Could not change canonical parent ID in DB");
00661 SnapDBI::cancelTransaction();
00662 return false;
00663 }
00664
00665 SnapDBI::commitTransaction();
00666
00667 $this->set('canonParentId', $newParent);
00668 $this->unsetField('canonicalParent');
00669 SnapCache::deleteById($this->getType(), $this->getId());
00670 $this->unsetField('canonicalPath');
00671 SnapCache::putById($this->getType(), $this->getId(), $this);
00672
00673 return true;
00674 }
00675
00681 protected function renamelink($targetId, $newName) {
00682 if(!$this->checkValid()) return false;
00683
00684 $type = $this->getType();
00685 $id = $this->getId();
00686 $linkTable = $type . "Link";
00687 $otherLinkTable = ($type == 'Directory' ? 'ResourceLink' : 'DirectoryLink');
00688
00689 $oldPaths = $this->getPaths(-$targetId);
00690
00691 if(!SnapDBI::startTransaction())
00692 return false;
00693
00694 $query = "SELECT shortName FROM $linkTable WHERE parentId=$targetId AND shortName= BINARY '$newName' UNION "
00695 . "SELECT shortName FROM $otherLinkTable WHERE parentId=$targetId AND shortName= BINARY '$newName'";
00696 if(!SnapDBI::query($query)) {
00697 $this->setError("Could not check for conflicts");
00698 SnapDBI::cancelTransaction();
00699 return false;
00700 }
00701
00702 if(SnapDBI::getNumRows() > 0) {
00703 $this->setError("'$newName' already exists in the parent directory");
00704 SnapDBI::freeResult();
00705 SnapDBI::cancelTransaction();
00706 return false;
00707 }
00708 SnapDBI::freeResult();
00709
00710 $query = "UPDATE $linkTable SET shortName='$newName' WHERE parentId=$targetId AND childId=$id";
00711 if(!SnapDBI::query($query)) {
00712 $this->setError("Could not set short name");
00713 SnapDBI::cancelTransaction();
00714 return false;
00715 }
00716
00717 $this->unsetField('shortNames');
00718 $newPaths = $this->getPaths(-$targetId);
00719
00720 SnapPathCache::updatePathSet($newPaths, $oldPaths);
00721
00722 SnapDBI::commitTransaction();
00723
00724 if($this->isLoaded('shortNames')) {
00725 $shortNames = $this->get('shortNames');
00726 $shortNames[$targetId] = $newName;
00727 $this->set('shortNames', $shortNames);
00728 }
00729
00730 SnapCache::deleteById($type, $id);
00731 if($this->isLoaded('canonicalPath'))
00732 SnapCache::deleteByPath($type, $this->get('canonicalPath'));
00733 $this->unsetField('canonicalPath');
00734 $this->unsetField('canonicalParent');
00735 SnapCache::putById($type, $id, $this);
00736
00737 // if($targetId == $this->get('canonParentId')) {
00738 // $this->pathCacheUpdate($oldPaths);
00739 // }
00740
00741 return true;
00742 }
00743
00752 protected function chorder($parentId, $newOrdinal) {
00753 if(!$this->checkValid()) return false;
00754
00755 $type = $this->getType();
00756 $id = $this->getId();
00757 $linkTable = $type . 'Link';
00758 $curOrdinal = $this->getOrdinal(-$parentId);
00759
00760 if(!SnapDBI::startTransaction())
00761 return false;
00762
00763 $par = $this->getParent(-$parentId);
00764 $tp = substr($type, 0, 3);
00765 $maxOrdinal = $par->get('max' . $tp . 'Ordinal');
00766
00767 if($newOrdinal < 0 || $newOrdinal > $maxOrdinal) {
00768 $this->setError('Invalid ordinal (less than zero or greater than number of items in parent)');
00769 SnapDBI::cancelTransaction();
00770 return false;
00771 }
00772
00773 if($newOrdinal < $curOrdinal)
00774 $query = "UPDATE $linkTable SET ordinal=ordinal+1 WHERE parentId=$parentId "
00775 . "AND ordinal>=$newOrdinal AND ordinal<=$curOrdinal";
00776 else
00777 $query = "UPDATE $linkTable SET ordinal=ordinal-1 WHERE parentId=$parentId "
00778 . "AND ordinal>=$curOrdinal AND ordinal<=$newOrdinal";
00779
00780 if(!SnapDBI::query($query)) {
00781 $this->setError('Error fixing up existing ordinals');
00782 SnapDBI::cancelTransaction();
00783 return false;
00784 }
00785
00786 $query = "UPDATE $linkTable SET ordinal=$newOrdinal WHERE parentId=$parentId AND childId=$id";
00787 if(!SnapDBI::query($query)) {
00788 $this->setError('Error setting new ordinal');
00789 SnapDBI::cancelTransaction();
00790 return false;
00791 }
00792
00793 $query = "SELECT childId, ordinal FROM $linkTable WHERE parentId=$parentId";
00794 if(!($result = SnapDBI::query($query, true))) {
00795 $this->setError('Error retrieving other children');
00796 SnapDBI::cancelTransaction();
00797 return false;
00798 }
00799
00800 SnapDBI::commitTransaction();
00801
00802 $ordinals = $this->get('ordinals');
00803 $ordinals[$parentId] = $newOrdinal;
00804 $this->set('ordinals', $ordinals);
00805
00806 // XXX: could probably be replaced by having better cache hashes
00807 foreach($result as $r) {
00808 $obj = SnapCache::getById('Directory', $r['childId']);
00809 if(!$obj) {
00810 $obj = SnapCache::getById('Resource', $r['childId']);
00811 if(!$obj)
00812 continue;
00813 }
00814 $ordinals = $obj->get('ordinals');
00815 $ordinals[$parentId] = $r['ordinal'];
00816 $obj->set('ordinals', $ordinals);
00817 }
00818
00819 SnapCache::updateById($this->getType(), $this->getId());
00820
00821 return true;
00822 }
00823
00833 private function convertParent($parent) {
00834 if(is_object($parent) && $parent instanceof SnapDirectory) {
00835 $parents = $this->get('parents');
00836 if(!isset($parents[$parent->getId()])) {
00837 $this->setError('Directory \'' . $parent->getId() . '\' is not parent of this object');
00838 return false;
00839 }
00840 $parentId = $parent->getId();
00841 }
00842 else if((is_numeric($parent) || is_string($parent))
00843 && (ctype_digit((string) $parent) || $parent < 0))
00844 {
00845 if($parent < 0)
00846 $parentId = -$parent;
00847 else {
00848 $parents = $this->get('parents');
00849 $pkeys = array_keys($parents);
00850 if($parent >= count($pkeys)) {
00851 $this->setError('Invalid parent index: ' . $parent);
00852 return false;
00853 }
00854 $parentId = $pkeys[$parent];
00855 }
00856 }
00857 else if(is_string($parent)) {
00858 $obj = SnapDirectory::lookup($parent);
00859 if($obj == null) {
00860 $this->setError('No such parent directory: \'' . $parent . '\'');
00861 return false;
00862 }
00863
00864 $parents = $this->get('parents');
00865 if(!isset($parents[$obj->getId()])) {
00866 $this->setError('Directory \'' . $parent . '\' is not parent of this object');
00867 return false;
00868 }
00869 $parentId = $obj->getId();
00870 }
00871 else {
00872 $this->setError('Invalid parent specified');
00873 return false;
00874 }
00875
00876 return $parentId;
00877 }
00878
00879 /*
00880 * Ability-checking Methods
00881 */
00882
00884 protected function _canLinkTo($dest, $shortName = '') {
00885 if($this->getType() == 'Directory' &&
00886 ($this->getId() == Snap2::ROOT_DIR_ID || $this->getId() == Snap2::DELETED_DIR_ID)) {
00887 $this->setReason('Cannot create links from root or deleted directory');
00888 return false;
00889 }
00890
00891 if($shortName == "") {
00892 $cparent = $this->getCanonicalParent();
00893 $shortName = $this->getShortName(-$cparent->getId());
00894 }
00895
00896 $dest = self::convertReference($dest, 'Directory');
00897 if($dest == null)
00898 return false;
00899
00900 if($this->isCanonChildOf(Snap2::DELETED_DIR_ID)) {
00901 $this->setReason('Cannot link from files in /DELETED/');
00902 return false;
00903 }
00904
00905 if($dest->isCanonChildOf(Snap2::DELETED_DIR_ID)) {
00906 $this->setReason('Cannot create links into /DELETED/');
00907 return false;
00908 }
00909
00910 if(!preg_match('/^[a-zA-Z0-9\-_.]+$/', $shortName)) {
00911 $this->setReason('Link name must consist of one or more alphanumeric characters, \'_ \', \'-\', or \'.\' ');
00912 return false;
00913 }
00914
00915 $query = 'SELECT COUNT(*) FROM ' . $this->getType() . 'Link WHERE parentId = '
00916 . $dest->getId() . ' AND shortName = BINARY \'' . $shortName . '\'';
00917 if(!($result = SnapDBI::query($query, true))) {
00918 $this->setError('Error checking for conflicting names');
00919 return false;
00920 }
00921
00922 if($result[0]['COUNT(*)'] != 0) {
00923 $this->setReason('Link with name \'' . $shortName . '\' already exists!');
00924 return false;
00925 }
00926
00927 if(!$this->getPermission()->mayLinkTo($dest)) {
00928 $this->setReason('Permission denied');
00929 return false;
00930 }
00931
00932 return array($dest, $shortName);
00933 }
00934
00936 protected function _canUnlinkFrom($src) {
00937 if($this->getType() == 'Directory' &&
00938 ($this->getId() == Snap2::ROOT_DIR_ID || $this->getId() == Snap2::DELETED_DIR_ID)) {
00939 $this->setReason('Cannot remove links belonging to root or deleted directory');
00940 return false;
00941 }
00942
00943 $parentId = $this->convertParent($src);
00944 if(!$parentId) {
00945 $this->setReason('Invalid link specified');
00946 return false;
00947 }
00948
00949 if($this->isCanonChildOf(Snap2::DELETED_DIR_ID)) {
00950 $this->setReason('Cannot unlink from /DELETED/ or any of its derivatives');
00951 return false;
00952 }
00953
00954 if($parentId == Snap2::DELETED_DIR_ID) {
00955 $this->setReason('Cannot remove link to /DELETED/; use delete() instead');
00956 return false;
00957 }
00958
00959 $parents = $this->getParents();
00960 if(count($parents) == 1) {
00961 $this->setReason('Removing this link would delete the file; use delete() instead');
00962 return false;
00963 }
00964
00965 if(!$this->getPermission()->mayUnlinkFrom($parentId)) {
00966 $this->setReason('Permission denied');
00967 return false;
00968 }
00969
00970 if($parentId == $this->get('canonParentId')) {
00971 $newParentId = $parents[0];
00972 if($newParentId == $parentId)
00973 $newParentId = $parents[1];
00974 if(!$this->getPermission()->maySetCanonicalParent($newParentId)) {
00975 $this->setReason('Permission denied');
00976 return false;
00977 }
00978 }
00979
00980 return array($parentId);
00981 }
00982
00984 protected function _canRenameAt($parent, $newName = false) {
00985 if($this->getType() == 'Directory' &&
00986 ($this->getId() == Snap2::ROOT_DIR_ID || $this->getId() == Snap2::DELETED_DIR_ID)) {
00987 $this->setReason('Cannot rename root or deleted directory');
00988 return false;
00989 }
00990
00991 $parentId = $this->convertParent($parent);
00992 if(!$parentId) {
00993 $this->setReason('Invalid parent: ' . $parent);
00994 return false;
00995 }
00996
00997 if($parentId == Snap2::DELETED_DIR_ID) {
00998 $this->setReason('Cannot rename link to /DELETED/');
00999 return false;
01000 }
01001
01002 if($this->isCanonChildOf(Snap2::DELETED_DIR_ID)) {
01003 $this->setReason('Cannot rename /DELETED/ or any of its derivatives');
01004 return false;
01005 }
01006
01007 if($newName !== false) {
01008 if(!preg_match('/^[a-zA-Z0-9\-_.]+$/', $newName)) {
01009 $this->setReason('Link name must consist of one or more alphanumeric characters, \'_ \', \'-\' or \'.\' ');
01010 return false;
01011 }
01012 }
01013
01014 if(!$this->getPermission()->mayRenameAt($parentId)) {
01015 $this->setReason('Permission denied');
01016 return false;
01017 }
01018
01019 return array($parentId, $newName);
01020 }
01021
01023 protected function _canSetCanonicalParent($newParent) {
01024 if($this->getType() == 'Directory' &&
01025 ($this->getId() == Snap2::ROOT_DIR_ID || $this->getId() == Snap2::DELETED_DIR_ID)) {
01026 $this->setReason('Cannot reparent root or deleted directory');
01027 return false;
01028 }
01029
01030 $id = $this->convertParent($newParent);
01031 if(!$id)
01032 return false;
01033
01034 if($this->isCanonChildOf(Snap2::DELETED_DIR_ID)) {
01035 $this->setReason('Cannot set /DELETED/ as the canonical parent, or any of its derivatives');
01036 return false;
01037 }
01038
01039 if(!$this->getPermission()->maySetCanonicalParent($id)) {
01040 $this->setReason('Permission denied');
01041 return false;
01042 }
01043
01044 return array($id);
01045 }
01046
01048 protected function _canMove($newParent, $newName = false, $removeOldLink = false) {
01049 if($this->getType() == 'Directory' &&
01050 ($this->getId() == Snap2::ROOT_DIR_ID || $this->getId() == Snap2::DELETED_DIR_ID)) {
01051 $this->setReason('Cannot move root or deleted directory');
01052 return false;
01053 }
01054
01055 $newParent = self::convertReference($newParent, 'Directory');
01056 if(!$newParent)
01057 return false;
01058 $newParentId = $newParent->getId();
01059
01060 if($newName !== false) {
01061 if(!preg_match('/^[a-zA-Z0-9\-_.]+$/', $newName)) {
01062 $this->setReason('Link name must consist of one or more alphanumeric characters, \'_ \', \'-\' or \'.\' ');
01063 return false;
01064 }
01065
01066 $query = 'SELECT COUNT(*) FROM ' . $this->getType() . 'Link WHERE parentId = '
01067 . $newParentId . ' AND shortName = BINARY \'' . $newName . '\'';
01068 if(!($result = SnapDBI::query($query, true))) {
01069 $this->setError('Error checking for conflicting names');
01070 return false;
01071 }
01072
01073 if($result[0]['COUNT(*)'] != 0) {
01074 $this->setReason('Link with name \'' . $shortName . '\' already exists!');
01075 return false;
01076 }
01077 }
01078
01079 if($this->isCanonChildOf(Snap2::DELETED_DIR_ID)) {
01080 $this->setReason('Cannot set /DELETED/ as the canonical parent, or any of its derivatives');
01081 return false;
01082 }
01083
01084 $oldParent = $this->get('canonicalParent');
01085 $oldParentId = $oldParent->getId();
01086
01087 if(!$this->getPermission()->mayMove($newParent)) {
01088 $this->setReason('Permission denied');
01089 return false;
01090 }
01091
01092 return array('newParentId' => $newParentId, 'newName' => $newName, 'oldParentId' => $oldParentId);
01093 }
01094
01096 /* XXX: instead of checking parentage, just check the link flags to see if item is deleted
01097 * because file could be a child of a deleted directory/resource
01098 */
01099 protected function _canDelete() {
01100 if($this->getType() == 'Directory' &&
01101 ($this->getId() == Snap2::ROOT_DIR_ID || $this->getId() == Snap2::DELETED_DIR_ID)) {
01102 $this->setReason('Cannot delete root or deleted directory');
01103 return false;
01104 }
01105
01106 // XXX: fix this here
01107 if($this->getCanonParentId() == Snap2::DELETED_DIR_ID) {
01108 // $parents = $this->get('parents');
01109 // if(count($parents) == 1 && isset($parents[Snap2::DELETED_DIR_ID])) {
01110 $this->setReason("File has already been deleted!");
01111 return false;
01112 }
01113
01114 if(!$this->getPermission()->mayDelete()) {
01115 $this->setReason('Permission denied');
01116 return false;
01117 }
01118
01119 return true; //array($parents);
01120 }
01121
01123 protected function _canDestroy() {
01124 if($this->getType() == 'Directory' &&
01125 ($this->getId() == Snap2::ROOT_DIR_ID || $this->getId() == Snap2::DELETED_DIR_ID)) {
01126 $this->setReason('Cannot destroy root or deleted directory');
01127 return false;
01128 }
01129
01130 if(!$this->isCanonChildOf(Snap2::DELETED_DIR_ID)) {
01131 $this->setReason('Cannot destroy file that has not been deleted first');
01132 return false;
01133 }
01134
01135 $parents = $this->get('parents');
01136 if(count($parents) != 1) {// || !isset($parents[Snap2::DELETED_DIR_ID])) {
01137 $this->setError('Snap2 Internal Error: supposedly deleted object has links outside /DELETED/! Not proceeding with delete');
01138 return false;
01139 }
01140
01141 if(!$this->getPermission()->mayDestroy()) {
01142 $this->setReason('Permission denied');
01143 return false;
01144 }
01145
01146 return true;
01147 }
01148
01150 protected function _canRestore($newParent = '', $as = '') {
01151 if($this->getType() == 'Directory' &&
01152 ($this->getId() == Snap2::ROOT_DIR_ID || $this->getId() == Snap2::DELETED_DIR_ID)) {
01153 $this->setReason('Cannot restore root or deleted directory');
01154 return false;
01155 }
01156
01157 if(!$this->isCanonChildOf(Snap2::DELETED_DIR_ID)) {
01158 $this->setReason('Cannot restore file that has not been deleted');
01159 return false;
01160 }
01161
01162 $newName = $this->getCanonicalShortName();
01163 if(preg_match('/^(.*)\$[RD][0-9]+$/', $newName, $matches))
01164 $newName = $matches[1];
01165
01166 if($newParent == '') {
01167 $newParent = $this->get('oldParentId');
01168 if(!$newParent && $this->getCanonicalParent()->getId() != Snap2::DELETED_DIR_ID)
01169 $newParent = 1;
01170 $msg = 'Attempt to restore to a non-existent directory and no alternative given';
01171 }
01172 else {
01173 $msg = 'Alternative restore directory does not exist or is invalid';
01174 }
01175
01176 $newParent = self::convertReference($newParent, 'Directory');
01177 if(!$newParent) {
01178 $this->setError($msg);
01179 return false;
01180 }
01181
01182 if($as != '') {
01183 $newName = $as;
01184 }
01185
01186 if(!$this->getPermission()->mayRestore()) {
01187 $this->setReason('Permission denied');
01188 return false;
01189 }
01190
01191 return array($newParent, $newName);
01192 }
01193
01195 protected function _canReorderIn($parent, $newOrdinal) {
01196 if($this->getType() == 'Directory' &&
01197 ($this->getId() == Snap2::ROOT_DIR_ID || $this->getId() == Snap2::DELETED_DIR_ID)) {
01198 $this->setReason('Cannot reorder root or deleted directory');
01199 return false;
01200 }
01201
01202 $parentId = $this->convertParent($parent);
01203 if(!$parentId)
01204 return false;
01205
01206 $par = $this->getParent(-$parentId);
01207 $tp = substr($this->getType(), 0, 3);
01208 $maxOrdinal = $par->get('max' . $tp . 'Ordinal');
01209
01210 switch($newOrdinal) {
01211 case self::MOVE_UP:
01212 $newOrdinal = $this->getOrdinal(-$parentId) - 1;
01213 break;
01214 case self::MOVE_DOWN:
01215 $newOrdinal = $this->getOrdinal(-$parentId) + 1;
01216 break;
01217 case self::MOVE_TOP:
01218 $newOrdinal = 1;
01219 break;
01220 case self::MOVE_BOTTOM:
01221 $newOrdinal = $maxOrdinal;
01222 break;
01223 }
01224 if($newOrdinal < 0 || $newOrdinal > $maxOrdinal) {
01225 $this->setReason('New ordinal is outside of allowed range');
01226 return false;
01227 }
01228
01229 if(!$this->getPermission()->mayReorderIn($parent)) {
01230 $this->setReason('Permission denied');
01231 return false;
01232 }
01233
01234 return array($parentId, $newOrdinal);
01235 }
01236
01238 protected function _canChangeMetadata() {
01239 if($this->getType() == 'Directory' &&
01240 ($this->getId() == Snap2::ROOT_DIR_ID || $this->getId() == Snap2::DELETED_DIR_ID)) {
01241 $this->setReason('Cannot change metadata of root or deleted directory');
01242 return false;
01243 }
01244
01245 if(!$this->getPermission()->mayChangeMetadata()) {
01246 $this->setReason('Permission denied');
01247 return false;
01248 }
01249
01250 return true;
01251 }
01252
01253 /*
01254 * Public API methods
01255 */
01256
01273 public function linkTo($dest, $shortName = "") {
01274 if(!($ret = $this->_canLinkTo($dest, $shortName))) {
01275 $this->setError('Failed to link: ' . $this->getReason());
01276 return false;
01277 }
01278 $dest = $ret[0];
01279 $shortName = $ret[1];
01280 return $this->mklink($dest->getId(), $shortName);
01281 }
01282
01297 public function unlinkFrom($src) {
01298 if(!($ret = $this->_canUnlinkFrom($src))) {
01299 $this->setError('Failed to unlink: '. $this->getReason());
01300 return false;
01301 }
01302 $parentId = $ret[0];
01303 return $this->rmlink($parentId);
01304 }
01305
01319 public function renameAt($parent, $newName) {
01320 if(!($ret = $this->_canRenameAt($parent, $newName))) {
01321 $this->setError('Failed to rename: '. $this->getReason());
01322 return null;
01323 }
01324 $parentId = $ret[0];
01325 $newName = $ret[1];
01326 return $this->renamelink($parentId, $newName);
01327 }
01328
01341 public function setCanonicalParent($newParent) {
01342 if(!($ret = $this->_canSetCanonicalParent($newParent))) {
01343 $this->setError('Failed to reparent: '. $this->getReason());
01344 return false;
01345 }
01346 $id = $ret[0];
01347 return $this->chcanonparent($id);
01348 }
01349
01364 public function move($newParent, $newName = '', $removeOldLink = false) {
01365 if($newName === false || $newName == '')
01366 $newName = $this->getShortName(-$this->get('canonParentId'));
01367
01368 if(!($ret = $this->_canMove($newParent, $newName, $removeOldLink))) {
01369 $this->setError('Failed to move: ' . $this->getReason());
01370 return false;
01371 }
01372
01373 if(!SnapDBI::startTransaction())
01374 return false;
01375 if(!$this->mklink($ret['newParentId'], $ret['newName'])) {
01376 SnapDBI::cancelTransaction();
01377 $this->setError('Could not create new link');
01378 return false;
01379 }
01380 if(!$this->chcanonparent($ret['newParentId'])) {
01381 SnapDBI::cancelTransaction();
01382 $this->setError('Could not change parent');
01383 return false;
01384 }
01385 if($removeOldLink && !$this->rmlink($ret['oldParentId'])) {
01386 SnapDBI::cancelTransaction();
01387 $this->setError('Could not remove old link');
01388 return false;
01389 }
01390 SnapDBI::commitTransaction();
01391 return true;
01392 }
01393
01402 public function delete() {
01403 if(!$this->_canDelete()) {
01404 $this->setError('Failed to delete: '. $this->getReason());
01405 return false;
01406 }
01407
01408 $parents = $this->get('parents');
01409
01410 if(!SnapDBI::startTransaction())
01411 return false;
01412
01413 foreach($parents as $parent => $dummy) {
01414 if(!$this->rmlink($parent)) {
01415 $this->setError('Could not remove link from parent \'' . Snap2::DELETED_DIR_ID . '\'');
01416 SnapDBI::cancelTransaction();
01417 return false;
01418 }
01419 }
01420
01421 SnapDBI::commitTransaction();
01422
01423 return true;
01424 }
01425
01438 public function destroy() {
01439 if(!$this->_canDestroy()) {
01440 $this->setError('Failed to destroy: ' . $this->getReason());
01441 return false;
01442 }
01443 $oldParent = $this->getParent(0);
01444 if(!$this->rmlink($oldParent->getId())) {
01445 SnapResource::_cancelDestroy();
01446 return false;
01447 }
01448 SnapResource::_destroyCMs();
01449 return true;
01450 }
01451
01464 public function restore($newParent = '', $newName = '') {
01465 if(!($ret = $this->_canRestore($newParent, $newName))) {
01466 $this->setError('Failed to restore: ' . $this->getReason());
01467 return false;
01468 }
01469
01470 $newParent = $ret[0];
01471 $newName = $ret[1];
01472
01473 SnapDBI::startTransaction();
01474
01475 $query = 'UPDATE ' . $this->getType() . ' SET oldParentId = NULL WHERE id = ' . $this->getId();
01476 if(!SnapDBI::query($query)) {
01477 $this->setError('Error updating database');
01478 SnapDBI::cancalTransaction();
01479 return false;
01480 }
01481
01482 $oldParent = $this->getParent(0);
01483
01484 if(!$this->mklink($newParent->getId(), $newName)) {
01485 $this->setError('Could not create link into root directory');
01486 SnapDBI::cancelTransaction();
01487 return false;
01488 }
01489
01490 if(!$this->rmlink($oldParent->getId())) {
01491 $this->setError('Could not remove link from DELETED directory');
01492 SnapDBI::cancelTransaction();
01493 return false;
01494 }
01495
01496 SnapDBI::commitTransaction();
01497
01498 SnapCache::updateById($this->getType(), $this->getId());
01499
01500 return true;
01501 }
01502
01521 public function reorderIn($parent, $newOrdinal) {
01522 if(!($ret = $this->_canReorderIn($parent, $newOrdinal))) {
01523 $this->setError('Failed to reorder: ' . $this->getReason());
01524 return false;
01525 }
01526 $parentId = $ret[0];
01527 $newOrdinal = $ret[1];
01528 return $this->chorder($parentId, $newOrdinal);
01529 }
01530
01538 public function setName($newName) {
01539 if(!$this->_canChangeMetadata()) {
01540 $this->setError('Failed to change name: '. $this->getReason());
01541 return false;
01542 }
01543 if(!SnapDBI::startTransaction())
01544 return false;
01545 $newName = mysql_escape_string($newName);
01546 $query = 'UPDATE ' . $this->getType() . ' SET name = \'' . $newName . '\' WHERE '
01547 . ' id = ' . $this->getId();
01548 if(!SnapDBI::query($query)) {
01549 $this->setError('Could not update name for ' . $this->getType() . ' with ID ' . $this->getId());
01550 SnapDBI::cancelTransaction();
01551 return false;
01552 }
01553 SnapDBI::commitTransaction();
01554 $this->set('name', $newName);
01555 SnapCache::updateById($this->getType(), $this->getId());
01556 return true;
01557 }
01558
01566 public function setDescription($newDesc) {
01567 if(!$this->_canChangeMetadata()) {
01568 $this->setError('Failed to change description: '. $this->getReason());
01569 return false;
01570 }
01571 if(!SnapDBI::startTransaction())
01572 return false;
01573 $newDesc = mysql_escape_string($newDesc);
01574 $query = 'UPDATE ' . $this->getType() . ' SET description = \'' . $newDesc . '\' WHERE '
01575 . ' id = ' . $this->getId();
01576 if(!SnapDBI::query($query)) {
01577 $this->setError('Could not update description for ' . $this->getType() . ' with ID ' . $this->getId());
01578 SnapDBI::cancelTransaction();
01579 return false;
01580 }
01581 SnapDBI::commitTransaction();
01582 $this->set('description', $newDesc);
01583 SnapCache::updateById($this->getType(), $this->getId());
01584 return true;
01585 }
01586
01587 /*
01588 * Query functions
01589 */
01590
01600 public function __call($name, $args) {
01601 if(substr($name, 0, 3) == 'can') {
01602 if(!method_exists($this, '_' . $name)) {
01603 $this->setError('Invalid dynamic function call: \'' . $name . '\'');
01604 return null;
01605 }
01606 if(!$this->checkValid()) return false;
01607 return call_user_func_array(array($this, '_' . $name), $args) ? true : false;
01608 }
01609 else
01610 return parent::__call($name, $args);
01611 }
01612
01622 public function getShortName($parent) {
01623 if(!$this->checkValid()) return false;
01624 $parentId = $this->convertParent($parent);
01625 if($parentId === false)
01626 return false;
01627 $shortNames = $this->get('shortNames');
01628 return $shortNames[$parentId];
01629 }
01630
01639 public function getOrdinal($parent) {
01640 if(!$this->checkValid()) return false;
01641 $parentId = $this->convertParent($parent);
01642 if($parentId === false)
01643 return false;
01644 $ordinals = $this->get('ordinals');
01645 return $ordinals[$parentId];
01646 }
01647
01657 public function getParent($idx) {
01658 if(!$this->checkValid()) return false;
01659 $parents = $this->get('parents');
01660 $pkeys = array_keys($parents);
01661 if($idx < 0) {
01662 $pId = -$idx;
01663 if(!isset($parents[$pId])) {
01664 $this->setError('No such parent with ID \'' . $pId . '\'');
01665 return false;
01666 }
01667 }
01668 else if($idx >= count($pkeys)) {
01669 $this->setError('No such parent with index \'' . $idx . '\'');
01670 return false;
01671 }
01672 else
01673 $pId = $pkeys[$idx];
01674 if(!is_object($parents[$pId]))
01675 $parents[$pId] = SnapDirectory::retrieve($pId);
01676
01677 $this->set('parents', $parents);
01678
01679 return $parents[$pId];
01680 }
01681
01686 public function getParents() {
01687 if(!$this->checkValid()) return false;
01688 return array_keys($this->get('parents'));
01689 }
01690
01695 public function getParentCount() {
01696 if(!$this->checkValid()) return false;
01697 return count($this->get('parents'));
01698 }
01699
01706 public function getCanonicalShortName() {
01707 if(!$this->checkValid()) return false;
01708 if($this->canonicalShortName != null)
01709 return $this->canonicalShortName;
01710 $cparent = $this->get('canonParentId');
01711 if($cparent == '')
01712 return false;
01713 $this->canonicalShortName = $this->getShortName(-$cparent);
01714 return $this->canonicalShortName;
01715 }
01716
01725 public function getPermission() {
01726 if(!$this->checkValid()) return false;
01727 $perm = $this->get('permission');
01728 return $perm;
01729 }
01730
01744 public function isChildOf($what, $strict = false) {
01745 if(!$this->checkValid()) return false;
01746
01747 $whatObj = self::convertReference($what, 'Directory');
01748 if(!$whatObj) {
01749 return false;
01750 }
01751
01752 if($whatObj->getType() == $this->getType() && $whatObj->getId() == $this->getId()) {
01753 return true;
01754 }
01755
01756 if($this->getType() == 'Directory' &&
01757 ($this->getId() == Snap2::ROOT_DIR_ID || $this->getId() == Snap2::DELETED_DIR_ID))
01758 return false;
01759
01760 if($strict) {
01761 $canonPar = $this->getCanonicalParent();
01762 if($canonPar && $canonPar->getId() == $whatObj->getId())
01763 return true;
01764 return $canonPar->isChildOf($whatObj, true);
01765 }
01766 else {
01767 $parents = $this->get('parents');
01768 if(isset($parents[$whatObj->getId()]))
01769 return true;
01770
01771
01772 $this->setWarning('isChildOf() not implemented properly for non-strict mode');
01773 return false;
01774 }
01775 }
01776
01782 public function isCanonChildOf($what) {
01783 return $this->isChildOf($what, true);
01784 }
01785
01793 public function getAllPaths() {
01794 if(!$this->checkValid()) return null;
01795
01796 $paths = array();
01797
01798 if($this->getType() == 'Resource' || $this->getId() > 2) {
01799 $parents = $this->getParents();
01800 foreach($parents as $idx => $id) {
01801 $p = $this->getParent($idx);
01802 if($p === null) {
01803 $this->setError('Got a null parent for ID: ' . $id . ' (my ID: ' . $this->getId() . ')');
01804 continue;
01805 }
01806 $pPaths = $p->getAllPaths();
01807 foreach($pPaths as $pp)
01808 $paths[$pp] = $id;
01809 }
01810 }
01811 else if($this->getType() == 'Directory' && $this->getId() == 1)
01812 return array('//');
01813 else if($this->getType() == 'Directory' && $this->getId() == 2)
01814 return array('/DELETED/');
01815
01816 $finalPaths = array();
01817
01818 foreach($paths as $path => $id) {
01819 $path .= $this->getShortName(-$id);
01820 if($this->getType() == 'Directory')
01821 $path .= '/';
01822 $finalPaths[] = $path;
01823 }
01824
01825 return $finalPaths;
01826 }
01827
01836 public function getPaths($ref) {
01837 if(!$this->checkValid()) return null;
01838
01839 $refObj = $this->getParent($ref);
01840 if(!$refObj)
01841 return false;
01842
01843 $paths = $refObj->getAllPaths();
01844 $isDir = $this->getType() == 'Directory' ? '/' : '';
01845 for($i = 0; $i < count($paths); $i++)
01846 $paths[$i] .= $this->getShortName(-$refObj->getId()) . $isDir;
01847
01848 return $paths;
01849 }
01850
01858 public static function lookup($path) {
01859 if(substr($path, -1) == '/')
01860 return SnapDirectory::lookup($path);
01861 else
01862 return SnapResource::lookup($path);
01863 }
01864
01873 public function addStylesheet($pathOrObj) {
01874 $md = $this->getMetadata();
01875 if(is_object($pathOrObj) && $pathOrObj instanceof SnapResource) {
01876 $path = $pathOrObj->getCanonicalPath();
01877 $obj = $pathOrObj;
01878 }
01879 else {
01880 $obj = SnapResource::lookup($pathOrObj);
01881 $path = $pathOrObj;
01882 }
01883
01884 if(!$obj) {
01885 $this->setError('No such resource or not a resource: \'' . $path . '\'');
01886 return false;
01887 }
01888 if($obj->getContentType() != SnapResource::TYPE_CSS) {
01889 $this->setError('Stylesheet must be a CSS resource');
01890 return false;
01891 }
01892
01893 $stylesheets = $md->retrieve('stylesheet');
01894 if($stylesheets != '') {
01895 $stylesheets = explode(';', $stylesheets);
01896 if(in_array($path, $stylesheets)) {
01897 $this->setError('Stylesheet has already been added!');
01898 return false;
01899 }
01900 }
01901 else
01902 $stylesheets = array();
01903 $stylesheets[] = $path;
01904 $stylesheets = implode(';', $stylesheets);
01905
01906 if(!$md->put('stylesheet', $stylesheets)) {
01907 $this->getErrorFrom($md);
01908 return false;
01909 }
01910
01911 if(!$md->commit()) {
01912 $this->getErrorFrom($md);
01913 return false;
01914 }
01915 else
01916 return true;
01917 }
01918
01927 public function removeStylesheets($which = null) {
01928 $md = $this->getMetadata();
01929 if(!$which) {
01930 if(!$md->remove('stylesheet')){
01931 $this->getErrorFrom($md);
01932 return false;
01933 }
01934 }
01935 else {
01936 if(!is_array($which))
01937 $which = array($which);
01938 $existing = $md->retrieve('stylesheet');
01939 $existingSS = explode(';', $existing);
01940 $out = array();
01941 foreach($existingSS as $ss) {
01942 if(!in_array($ss, $which))
01943 $out[] = $ss;
01944 }
01945 $out = implode(';', $out);
01946 $md->put('stylesheet', $out);
01947 }
01948
01949 if(!$md->commit()) {
01950 $this->getErrorFrom($md);
01951 return false;
01952 }
01953 else
01954 return true;
01955 }
01956
01965 public function getStylesheets($onlyThis = false) {
01966 $md = $this->getMetadata();
01967
01968 $parStylesheets = array();
01969 if(!$onlyThis) {
01970 $par = $this->getCanonicalParent();
01971 if($par)
01972 $parStylesheets = $par->getStylesheets();
01973 }
01974
01975 $ssPath = $md->retrieve('stylesheet');
01976 if($ssPath != '') {
01977 $paths = explode(';', $ssPath);
01978 $stylesheets = array();
01979 foreach($paths as $path) {
01980 $ss = SnapResource::lookup($path);
01981 if(!$ss || $ss->getContentType() != SnapResource::TYPE_CSS)
01982 $this->setWarning('Stylesheet is not valid or does not exist; removing association');
01983 else
01984 $stylesheets[$path] = $ss;
01985 }
01986 $outPaths = implode(';', array_keys($stylesheets));
01987 if(Snap2::isAdmin() && $outPaths != $paths) {
01988 if($outPaths == '')
01989 $md->remove('stylesheet');
01990 else
01991 $md->put('stylesheet', $outPaths);
01992 $md->commit();
01993 }
01994 return array_merge($parStylesheets, $stylesheets);
01995 }
01996 else
01997 return $parStylesheets;
01998 }
01999 }
02000
02001 ?>