1 <?php 2 /* Copyright (c) 2012, Geert Bergman (geert@scrivo.nl) 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, 9 * this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright notice, 11 * this list of conditions and the following disclaimer in the documentation 12 * and/or other materials provided with the distribution. 13 * 3. Neither the name of "Scrivo" nor the names of its contributors may be 14 * used to endorse or promote products derived from this software without 15 * specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 * POSSIBILITY OF SUCH DAMAGE. 28 * 29 * $Id: Page.php 866 2013-08-25 16:22:35Z geert $ 30 */ 31 32 /** 33 * Implementation of the \Scrivo\Page class. 34 */ 35 36 namespace Scrivo; 37 38 /** 39 * A Scrivo Page is most essential Scrivo entity. A Scrivo page models a HTML 40 * Page (most of the times). The idea is that a Web site is a set of pages. The 41 * dynamic content that should be presented on these web pages is contained by 42 * Scrivo Page objects. 43 * 44 * Therefore a Scrivo page contains some of the standard fields that are 45 * typical for a HTML page suchs as the page title, description and keywords 46 * along with some managerial (CMS) information as online and ofline date. 47 * 48 * What other content should be displayed on the page is determined by its 49 * page defintion. Each page is constructed using a a page defintion and this 50 * defintion holds the defintions for all other data (properties, texts, 51 * lists, applications) that can added to the page. 52 * 53 * The Scrivo editor interface provides the means to the editor to create 54 * pages and fill in the page properties. 55 * 56 * How the content that is assigned to the page should be displayed on the 57 * page is determined by the page template. The page defintion has a member 58 * that refers to the page template: a user defined script that renders the 59 * actual page. 60 * 61 * @property-read int $id The page id (DB key). 62 * @property-read \Scrivo\PageDefinition $definition The page definition of 63 * this page. 64 * @property-read booleand $isOnline If this page is online or not. 65 * @property-read \Scrivo\Language $language The main language for the 66 * page (<html lang>). 67 * @property-read \Scrivo\PropertySet $properties The page properties as a 68 * PHP object in which the members correspond with the PHP selector names. 69 * @property-read \Scrivo\RoleSet $roles The attached roles. 70 * @property-read \Scrivo\PageSet $children The child pages of this page. 71 * @property-read \Scrivo\PageSet $navigableChildren The navigable child 72 * pages of this page. 73 * @property-read \Scrivo\PageSet $path The parent pages of this page. 74 * @property-read \DateTime $dateCreated The date/time that this page was 75 * created. 76 * @property-read \DateTime $dateModified The last date/time that this page 77 * was modified. 78 * @property \Scrivo\Context $context A Scrivo context of this page. 79 * @property boolean $hasStaging Setting to indicate if the page can be staged. 80 * @property int $parentId The id of the parent page. 81 * @property int $type The page type: one out of the Page::TYPE_* constant 82 * values. 83 * @property \Scrivo\String $title The page title (<title>). 84 * @property \Scrivo\String $description The page description 85 * (<description>). 86 * @property \Scrivo\String $keywords The keywords for this page 87 * (<keywords>). 88 * @property \Scrivo\String $javascript A javascript script for this page 89 * (<script>). 90 * @property \Scrivo\String $stylesheet Additional CSS syle rules for this 91 * page (<stylesheet>). 92 * @property \DateTime $dateOnline The date/time this page need to go online. 93 * @property \DateTime $dateOffline The date/time this page need to go offline. 94 * @property-write int $definitionId The id of the page template. 95 * @property-write int $languageId The id of the main language for the page 96 * (<html lang>). 97 */ 98 class Page { 99 100 /** 101 * Value indicating a navigation item (page that only functions as a node). 102 */ 103 const TYPE_NAVIGATION_ITEM = 0; 104 105 /** 106 * Value indicating a page that should be shown in the site menu. 107 */ 108 const TYPE_NAVIGABLE_PAGE = 1; 109 110 /** 111 * Value indicating a page that should not be shown in the site menu. 112 */ 113 const TYPE_NON_NAVIGABLE_PAGE = 2; 114 115 /** 116 * Value indicating an extra node to hold automatically generated pages 117 * that are linked to list items. 118 */ 119 const TYPE_SUB_FOLDER = 4; 120 121 /** 122 * Value indicating an application: a page that has no functionality as 123 * a page but hosts an application in the scrivo user interface. 124 */ 125 const TYPE_APPLICATION = 5; 126 127 /** 128 * The page id (DB key). 129 * @var int 130 */ 131 private $id = 0; 132 133 /** 134 * The current version of the page: -1: scratch version, 0 live version, 135 * 1 and up versions. 136 * @var int 137 */ 138 private $version = 0; 139 140 /** 141 * Setting to indicate if the page can be staged. 142 * @var boolean 143 */ 144 private $hasStaging = 0; 145 146 /** 147 * The id of the parent page. 148 * @var int 149 */ 150 private $parentId = 0; 151 152 /** 153 * The page type: one out of the Page::TYPE_* constant values. 154 * @var int 155 */ 156 private $type = 0; 157 158 /** 159 * The id of the page template. 160 * @var int 161 */ 162 private $definitionId = 0; 163 164 /** 165 * The id the main language for the page (<html lang>). 166 * @var int 167 */ 168 private $languageId = 0; 169 170 /** 171 * The page title (<title>). 172 * @var \Scrivo\String 173 */ 174 private $title = null; 175 176 /** 177 * The page description (<description>). 178 * @var \Scrivo\String 179 */ 180 private $description = null; 181 182 /** 183 * The keywords for this page (<keywords>). 184 * @var \Scrivo\String 185 */ 186 private $keywords = null; 187 188 /** 189 * A javascript script for this page (<script>). 190 * @var \Scrivo\String 191 */ 192 private $javascript = null; 193 194 /** 195 * Additional CSS syle rules for this page (<stylesheet>). 196 * @var \Scrivo\String 197 */ 198 private $stylesheet = null; 199 200 /** 201 * The date/time that this page was created. 202 * @var \DateTime 203 */ 204 private $dateCreated = null; 205 206 /** 207 * The last date/time that this page was modified. 208 * @var \DateTime 209 */ 210 private $dateModified = null; 211 212 /** 213 * The date/time this page need to go online. 214 * @var \DateTime 215 */ 216 private $dateOnline = null; 217 218 /** 219 * The date/time this page need to go offline. 220 * @var \DateTime 221 */ 222 private $dateOffline = null; 223 224 /** 225 * The page properties as a PHP object in which the members correspond 226 * with the PHP selector names. 227 * @var \Scrivo\PropertySet 228 */ 229 private $properties = null; 230 231 /** 232 * The child pages of this page. 233 * @var \Scrivo\PageSet 234 */ 235 private $children = null; 236 237 /** 238 * The parent pages of this page. 239 * @var \Scrivo\PageSet 240 */ 241 private $path = null; 242 243 /** 244 * The attached roles. 245 * @var \Scrivo\RoleSet 246 */ 247 private $roles = null; 248 249 /** 250 * A Scrivo context. 251 * @var \Scrivo\Context 252 */ 253 private $context = null; 254 255 /** 256 * Create an empty page object. 257 * 258 * @param \Scrivo\Context $context A Scrivo context. 259 */ 260 public function __construct(\Scrivo\Context $context=null) { 261 \Scrivo\ArgumentCheck::assertArgs(func_get_args(), array(null), 0); 262 263 if ($context) { 264 $this->id = 0; 265 $this->version = 0; 266 $this->hasStaging = false; 267 $this->parentId = 0; 268 $this->type = 0; 269 $this->definitionId = 0; 270 $this->languageId = 0; 271 $this->title = new \Scrivo\String(); 272 $this->description = new \Scrivo\String(); 273 $this->keywords = new \Scrivo\String(); 274 $this->javascript = new \Scrivo\String(); 275 $this->stylesheet = new \Scrivo\String(); 276 $this->dateCreated = new \DateTime("now"); 277 $this->dateModified = new \DateTime("now"); 278 $this->dateOnline = new \DateTime("now"); 279 $this->dateOffline = null; 280 281 $this->properties = null; 282 283 $this->roles = new \Scrivo\RoleSet(); 284 285 $this->context = $context; 286 } 287 } 288 289 /** 290 * Implementation of the readable properties using the PHP magic 291 * method __get(). 292 * 293 * @param string $name The name of the property to get. 294 * 295 * @return mixed The value of the requested property. 296 */ 297 public function __get($name) { 298 switch($name) { 299 case "id": return $this->id; 300 case "hasStaging": return $this->hasStaging; 301 case "parentId": return $this->parentId; 302 case "type": return $this->type; 303 case "definition": return $this->definitionId ? 304 \Scrivo\PageDefinition::fetch($this->context, $this->definitionId) 305 : new \Scrivo\PageDefinition($this->context); 306 case "language": return $this->languageId ? 307 \Scrivo\Language::fetch($this->context, $this->languageId) 308 : new \Scrivo\Language($this->context); 309 case "title": return $this->title; 310 case "description": return $this->description; 311 case "keywords": return $this->keywords; 312 case "javascript": return $this->javascript; 313 case "stylesheet": return $this->stylesheet; 314 case "dateCreated": return $this->dateCreated; 315 case "dateModified": return $this->dateModified; 316 case "dateOnline": return $this->dateOnline; 317 case "dateOffline": return $this->dateOffline; 318 case "isOnline": return $this->getIsOnline(); 319 case "properties": return $this->getProperties(); 320 case "roles": return $this->roles; 321 case "children": return $this->getChildren(); 322 case "navigableChildren": return $this->getNavigableChildren(); 323 case "path": return $this->getPath(); 324 case "context": return $this->context; 325 } 326 throw new \Scrivo\SystemException("No such property-get '$name'."); 327 } 328 329 /** 330 * Implementation of the writable properties using the PHP magic 331 * method __set(). 332 * 333 * @param string $name The name of the property to set. 334 * @param mixed $value The value of the property to set. 335 */ 336 public function __set($name, $value) { 337 switch($name) { 338 case "hasStaging": $this->setHasStaging($value); return; 339 case "parentId": $this->setParentPageId($value); return; 340 case "type": $this->setType($value); return; 341 case "definitionId": $this->setDefinitionId($value); return; 342 case "languageId": $this->setLanguageId($value); return; 343 case "title": $this->setTitle($value); return; 344 case "description": $this->setDescription($value); return; 345 case "keywords": $this->setKeywords($value); return; 346 case "javascript": $this->setJavascript($value); return; 347 case "stylesheet": $this->setStylesheet($value); return; 348 case "dateOnline": $this->setDateOnline($value); return; 349 case "dateOffline": $this->setDateOffline($value); return; 350 case "context": $this->setContext($value); return; 351 } 352 throw new \Scrivo\SystemException("No such property-set '$name'."); 353 } 354 355 /** 356 * Convenience method to set the fields of a page definition object from 357 * an array (result set row). 358 * 359 * @param \Scrivo\Context $context A Scrivo context. 360 * @param array $rd An array containing the field data using the database 361 * field names as keys. 362 */ 363 private function setFields(\Scrivo\Context $context, array $rd) { 364 365 $this->id = intval($rd["page_id"]); 366 $this->version = intval($rd["version"]); 367 $this->hasStaging = intval($rd["has_staging"]) == 1 ? true : false; 368 $this->parentId = intval($rd["parent_id"]); 369 $this->type = intval($rd["type"]); 370 $this->definitionId = intval($rd["page_definition_id"]); 371 $this->languageId = intval($rd["language_id"]); 372 $this->title = new \Scrivo\String($rd["title"]); 373 $this->description = new \Scrivo\String($rd["description"]); 374 $this->keywords = new \Scrivo\String($rd["keywords"]); 375 $this->javascript = new \Scrivo\String($rd["javascript"]); 376 $this->stylesheet = new \Scrivo\String($rd["stylesheet"]); 377 $this->dateCreated = new \DateTime($rd["date_created"]); 378 $this->dateModified = new \DateTime($rd["date_modified"]); 379 $this->dateOnline = new \DateTime($rd["date_online"]); 380 $this->dateOffline = $rd["date_offline"] == null 381 ? null : new \DateTime($rd["date_offline"]); 382 383 $this->context = $context; 384 } 385 386 /** 387 * Get this pages's property list. 388 * 389 * @return object This pages's property list. 390 */ 391 private function getProperties() { 392 if ($this->properties === null) { 393 self::selectProperties($this->context, array($this->id => $this)); 394 $this->context->cache[$this->id] = $this; 395 } 396 return $this->properties; 397 } 398 399 /** 400 * Get the child pages of this page. 401 * 402 * @return \Scrivo\PageSet The child pages of the page. 403 */ 404 private function getChildren() { 405 if ($this->children === null) { 406 $this->children = self::selectChildren($this); 407 $this->context->cache[$this->id] = $this; 408 } 409 return $this->children; 410 } 411 412 /** 413 * Get the navigable child pages of this page. 414 * 415 * @return \Scrivo\PageSet The navigable child pages of the page. 416 */ 417 private function getNavigableChildren() { 418 $res = array(); 419 foreach ($this->getChildren() as $chld) { 420 if ($chld->type === self::TYPE_NAVIGABLE_PAGE 421 || $chld->type === self::TYPE_NAVIGATION_ITEM) { 422 $res[] = $chld; 423 } 424 } 425 return $res; 426 } 427 428 /** 429 * Get the child pages of this page. 430 * 431 * @return \Scrivo\PageSet All pages above the current page. 432 */ 433 private function getPath() { 434 if ($this->path === null) { 435 $this->path = self::selectPath($this); 436 $this->context->cache[$this->id] = $this; 437 } 438 return $this->path; 439 } 440 441 /** 442 * Check if this page is online. 443 * 444 * @return boolean True if this page is online else false. 445 */ 446 private function getIsOnline() { 447 $n = new \DateTime(); 448 $online = true; 449 if ($n < $this->dateOnline) { 450 $online = false; 451 } else { 452 if ($this->dateOffline) { 453 if ($n > $this->dateOffline) { 454 $online = false; 455 } 456 } 457 } 458 return $online; 459 } 460 461 /** 462 * Set the setting to indicate if a page can be staged. 463 * 464 * @param boolean $hasStaging Setting to indicate if a page can be staged. 465 */ 466 private function setHasStaging($hasStaging) { 467 \Scrivo\ArgumentCheck::assertArgs(func_get_args(), array( 468 array(\Scrivo\ArgumentCheck::TYPE_BOOLEAN) 469 )); 470 $this->hasStaging = $hasStaging; 471 } 472 473 /** 474 * Set the id of the parent page. 475 * 476 * @param int $parentId The id of the parent page. 477 */ 478 private function setParentPageId($parentId) { 479 \Scrivo\ArgumentCheck::assertArgs(func_get_args(), array( 480 array(\Scrivo\ArgumentCheck::TYPE_INTEGER) 481 )); 482 $this->parentId = $parentId; 483 } 484 485 /** 486 * Set the page type: one out of the Page::TYPE_* constant values. 487 * 488 * @param int $type The page type: one out of the Page::TYPE_* constant 489 * values. 490 */ 491 private function setType($type) { 492 \Scrivo\ArgumentCheck::assertArgs(func_get_args(), array( 493 array(\Scrivo\ArgumentCheck::TYPE_INTEGER, array( 494 self::TYPE_NAVIGATION_ITEM, self::TYPE_NAVIGABLE_PAGE, 495 self::TYPE_NON_NAVIGABLE_PAGE, self::TYPE_SUB_FOLDER, 496 self::TYPE_APPLICATION)) 497 )); 498 $this->type = $type; 499 } 500 501 /** 502 * Set the id of the page template. 503 * 504 * @param int $definitionId The id ot the page template. 505 */ 506 private function setDefinitionId($definitionId) { 507 \Scrivo\ArgumentCheck::assertArgs(func_get_args(), array( 508 array(\Scrivo\ArgumentCheck::TYPE_INTEGER) 509 )); 510 if (!$this->definitionId) { 511 $this->definitionId = $definitionId; 512 } else { 513 throw new \Scrivo\SystemException("Can't reset the page template"); 514 } 515 } 516 517 /** 518 * Set the id the main language for the page (<html lang>). 519 * 520 * @param int $languageId The id the main language for the page 521 * (<html lang>). 522 */ 523 private function setLanguageId($languageId) { 524 \Scrivo\ArgumentCheck::assertArgs(func_get_args(), array( 525 array(\Scrivo\ArgumentCheck::TYPE_INTEGER) 526 )); 527 $this->languageId = $languageId; 528 } 529 530 /** 531 * Set The page title (<title>). 532 * 533 * @param \Scrivo\String $title The page title (<title>). 534 */ 535 private function setTitle(\Scrivo\String $title) { 536 $this->title = $title; 537 } 538 539 /** 540 * Set the page description (<description>). 541 * 542 * @param \Scrivo\String $description The page description 543 * (<description>). 544 */ 545 private function setDescription(\Scrivo\String $description) { 546 $this->description = $description; 547 } 548 549 /** 550 * Set the keywords for this page (<keywords>). 551 * 552 * @param \Scrivo\String $keywords The keywords for this page 553 * (<keywords>). 554 */ 555 private function setKeywords(\Scrivo\String $keywords) { 556 $this->keywords = $keywords; 557 } 558 559 /** 560 * Set a javascript script for this page (<script>). 561 * 562 * @param \Scrivo\String $javascript A javascript script for this 563 * page (<script>). 564 */ 565 private function setJavascript(\Scrivo\String $javascript) { 566 $this->javascript = $javascript; 567 } 568 569 /** 570 * Set additional CSS syle rules for this page (<stylesheet>). 571 * 572 * @param \Scrivo\String $stylesheet Additional CSS syle rules for this 573 * page (<stylesheet>). 574 */ 575 private function setStylesheet(\Scrivo\String $stylesheet) { 576 $this->stylesheet = $stylesheet; 577 } 578 579 /** 580 * Set the date/time this page needs to go online. 581 * 582 * @param \DateTime $dateOnline The date/time this page needs to go online. 583 */ 584 private function setDateOnline(\DateTime $dateOnline) { 585 $this->dateOnline = $dateOnline; 586 } 587 588 /** 589 * Set the date/time this page need to go offline. 590 * 591 * @param \DateTime $dateOffline The date/time this page need to go offline. 592 */ 593 private function setDateOffline(\DateTime $dateOffline=null) { 594 $this->dateOffline = $dateOffline; 595 } 596 597 /** 598 * Set the page context. 599 * 600 * @param \Scrivo\Context $context A Scrivo context. 601 */ 602 private function setContext(\Scrivo\Context $context) { 603 $this->context = $context; 604 } 605 606 /** 607 * Select the page properties from the database. 608 * 609 * @param \Scrivo\Context $context A valid Scrivo context. 610 * @param array $pages the set of pages for which to retrieve the 611 * properties. 612 */ 613 private static function selectProperties($context, array $pages) { 614 615 $ids = implode(",", array_keys($pages)); 616 617 $sth = $context->connection->prepare( 618 "SELECT P.page_id PAGE_ID, T.type, T.php_key, 619 IFNULL(D.value, '') value, '' VALUE2, 620 T.page_property_definition_id ID_DEF 621 FROM page P, page_property_definition T 622 LEFT JOIN page_property D ON ( 623 D.instance_id = :instId AND T.instance_id = :instId 624 AND T.page_property_definition_id = D.page_property_definition_id 625 AND D.page_id in ($ids) AND D.version = 0) 626 WHERE T.instance_id = :instId AND P.instance_id = :instId AND 627 P.page_definition_id = T.page_definition_id AND P.page_id in ($ids) 628 AND P.version = 0 629 UNION 630 SELECT P.page_id PAGE_ID, 'html_text_tab' type, 631 T.php_key, IFNULL(D.html, '') value, 632 IFNULL(D.raw_html, '') VALUE2, 633 T.page_definition_tab_id ID_DEF 634 FROM page P, page_definition_tab T 635 LEFT JOIN page_property_html D ON ( 636 D.instance_id = :instId AND T.instance_id = :instId 637 AND T.page_definition_tab_id = D.page_definition_tab_id 638 AND D.page_id in ($ids) AND D.version = 0) 639 WHERE T.instance_id = :instId AND P.instance_id = :instId AND 640 P.page_definition_id = T.page_definition_id AND P.page_id in ($ids) 641 AND P.version = 0 AND T.application_definition_id = 0 642 UNION 643 SELECT D.page_id PAGE_ID, 'application_tab' type, 644 T.php_key, A.type value, A.application_definition_id VALUE2, 645 T.page_definition_tab_id ID_DEF 646 FROM page D, page_definition_tab T, application_definition A 647 WHERE D.instance_id = :instId AND T.instance_id = :instId 648 AND A.instance_id = :instId 649 AND T.page_definition_id = D.page_definition_id 650 AND A.application_definition_id = T.application_definition_id 651 AND T.application_definition_id <> 0 652 AND D.page_id in ($ids) AND D.version = 0"); 653 654 $context->connection->bindInstance($sth); 655 656 $sth->execute(); 657 658 foreach (array_keys($pages) as $id) { 659 $pages[$id]->properties = new \Scrivo\PropertySet(); 660 } 661 662 while ($rd = $sth->fetch(\PDO::FETCH_ASSOC)) { 663 664 $pageId = intval($rd["PAGE_ID"]); 665 666 $li = PageProperty::create($pages[$pageId], $rd); 667 668 if (!$li->phpSelector->equals(new \Scrivo\String(""))) { 669 $pages[$pageId]->properties->{$li->phpSelector} = $li; 670 } 671 672 } 673 674 } 675 676 /** 677 * Select the roles for this page. 678 * 679 * @param \Scrivo\Context $context A valid Scrivo context. 680 * @param array $pages the set of pages for which to retrieve the 681 * properties. 682 */ 683 private static function selectRoles($context, array $pages) { 684 685 $ids = implode(",", array_keys($pages)); 686 687 $sth = $context->connection->prepare( 688 "SELECT page_id, role_id FROM object_role 689 WHERE instance_id = :instId AND page_id in ($ids)"); 690 691 $context->connection->bindInstance($sth); 692 693 $sth->execute(); 694 695 while ($rd = $sth->fetch(\PDO::FETCH_ASSOC)) { 696 697 $pages[intval($rd["page_id"])]->roles[] = 698 intval($rd["role_id"]); 699 700 } 701 702 } 703 704 /** 705 * Check if the page data can be inserted into the database. 706 * 707 * @throws \Scrivo\ApplicationException If the data is not accessible, 708 * one or more of the fields contain invalid data or some other business 709 * rule is not met. 710 */ 711 private function validateInsert() { 712 713 if ($this->parentId) { 714 715 // If there is parent page copy relevant properties for the parent 716 // page. 717 $parent = \Scrivo\Page::fetch($this->context, $this->parentId); 718 719 $this->context->checkPermission( 720 \Scrivo\AccessController::WRITE_ACCESS, $this->parentId); 721 722 if ($this->languageId === 0) { 723 $this->languageId = $parent->languageId; 724 } 725 726 $this->hasStaging == $parent->hasStaging; 727 728 } else { 729 730 // If we're trying to insert a new root, check if there there is 731 // none yet. 732 $this->context->checkPermission(\Scrivo\AccessController::WRITE_ACCESS); 733 734 $sth = $this->context->connection->prepare( 735 "SELECT COUNT(*) FROM page 736 WHERE instance_id = :instId AND parent_id = 0"); 737 738 $this->context->connection->bindInstance($sth); 739 740 $sth->execute(); 741 742 if ($sth->fetchColumn(0) > 0) { 743 throw new \Scrivo\SystemException( 744 "Trying to create a new root page"); 745 } 746 747 } 748 749 } 750 751 /** 752 * Insert a new page into the database. 753 * 754 * First the data fields of this page will be validated. If no id 755 * is set a new object id is generated. Then the data is inserted into to 756 * database. 757 * 758 * @throws \Scrivo\ApplicationException If one or more of the fields 759 * contain invalid data. 760 */ 761 public function insert() { 762 try { 763 $this->validateInsert(); 764 765 if (!$this->id) { 766 $this->id = $this->context->connection->generateId(); 767 } 768 769 $sth = $this->context->connection->prepare( 770 "INSERT INTO page ( 771 instance_id, page_id, version, has_staging, parent_id, 772 sequence_no, type, page_definition_id, language_id, title, 773 description, keywords, javascript, stylesheet, 774 date_created, date_modified, date_online, date_offline 775 ) VALUES ( 776 :instId, :id, :version, :hasStaging, :parentId, 777 0, :type, :definitionId, :languageId, :title, 778 :description, :keywords, :javascript, :stylesheet, 779 now(), now(), :dateOnline, :dateOffline 780 )"); 781 782 $this->context->connection->bindInstance($sth); 783 $sth->bindValue(":id", $this->id, \PDO::PARAM_INT); 784 $sth->bindValue(":version", $this->version, \PDO::PARAM_INT); 785 $sth->bindValue(":hasStaging", $this->hasStaging, \PDO::PARAM_INT); 786 $sth->bindValue( 787 ":parentId", $this->parentId, \PDO::PARAM_INT); 788 $sth->bindValue(":type", $this->type, \PDO::PARAM_INT); 789 $sth->bindValue( 790 ":definitionId", $this->definitionId, \PDO::PARAM_INT); 791 $sth->bindValue(":languageId", $this->languageId, \PDO::PARAM_INT); 792 $sth->bindValue(":title", $this->title, \PDO::PARAM_STR); 793 $sth->bindValue( 794 ":description", $this->description, \PDO::PARAM_STR); 795 $sth->bindValue(":keywords", $this->keywords, \PDO::PARAM_STR); 796 $sth->bindValue(":javascript", $this->javascript, \PDO::PARAM_STR); 797 $sth->bindValue(":stylesheet", $this->stylesheet, \PDO::PARAM_STR); 798 $sth->bindValue(":dateOnline", 799 $this->dateOnline->format("Y-m-d H:i:s"), \PDO::PARAM_STR); 800 $sth->bindValue(":dateOffline", $this->dateOffline 801 ? $this->dateOffline->format("Y-m-d H:i:s") 802 : null, \PDO::PARAM_STR); 803 804 $sth->execute(); 805 806 if ($this->type != \Scrivo\Page::TYPE_SUB_FOLDER) { 807 \Scrivo\SequenceNo::position($this->context, "page", 808 "parent_id", $this->id, \Scrivo\SequenceNo::MOVE_LAST); 809 } 810 811 ObjectRole::set($this->context, $this->id, 812 ObjectRole::select($this->context, $this->parentId)); 813 814 // TODO ************** 815 // $this->_commit_subfolder(); 816 // $this->set_pretty_path(); 817 818 unset($this->context->cache[$this->parentId]); 819 820 } catch(\PDOException $e) { 821 throw new \Scrivo\ResourceException($e); 822 } 823 } 824 825 /** 826 * Check if the page data can be updated in the database. 827 * 828 * @throws \Scrivo\ApplicationException If the data is not accessible, 829 * one or more of the fields contain invalid data or some other business 830 * rule is not met. 831 */ 832 private function validateUpdate() { 833 834 $this->context->checkPermission( 835 \Scrivo\AccessController::WRITE_ACCESS, $this->id); 836 837 try { 838 $newPath = self::selectPath($this); 839 } catch (\Scrivo\SystemException $e) { 840 throw new \Scrivo\ApplicationException( 841 "Can't move a page underneath itself"); 842 } 843 844 } 845 846 /** 847 * Update an existing page in the database. 848 * 849 * First the data fields of this user will be validated, then the data 850 * is updated in to database. 851 * 852 * @throws \Scrivo\ApplicationException If one or more of the fields 853 * contain invalid data. 854 */ 855 public function update() { 856 try { 857 $this->validateUpdate(); 858 859 $isParentWritable = false; 860 if ($this->parentId) { 861 try { 862 $this->context->checkPermission( 863 \Scrivo\AccessController::WRITE_ACCESS, 864 $this->parentId); 865 $isParentWritable = true; 866 } catch (\Scrivo\ApplicationException $e) {} 867 } 868 869 $sth = $this->context->connection->prepare( 870 "UPDATE page SET 871 version = :version, has_staging = :hasStaging, 872 parent_id = :parentId, 873 type = :type, page_definition_id = :definitionId, 874 language_id = :languageId, title = :title, 875 description = :description, keywords = :keywords, 876 javascript = :javascript, stylesheet = :stylesheet, 877 date_online = :dateOnline, date_offline = :dateOffline 878 WHERE instance_id = :instId AND page_id = :id"); 879 880 $this->context->connection->bindInstance($sth); 881 $sth->bindValue(":id", $this->id, \PDO::PARAM_INT); 882 883 $sth->bindValue(":version", $this->version, \PDO::PARAM_INT); 884 $sth->bindValue(":hasStaging", $this->hasStaging, \PDO::PARAM_INT); 885 $sth->bindValue( 886 ":parentId", $this->parentId, \PDO::PARAM_INT); 887 $sth->bindValue(":type", $this->type, \PDO::PARAM_INT); 888 $sth->bindValue( 889 ":definitionId", $this->definitionId, \PDO::PARAM_INT); 890 $sth->bindValue(":languageId", $this->languageId, \PDO::PARAM_INT); 891 $sth->bindValue(":title", $this->title, \PDO::PARAM_STR); 892 $sth->bindValue( 893 ":description", $this->description, \PDO::PARAM_STR); 894 $sth->bindValue(":keywords", $this->keywords, \PDO::PARAM_STR); 895 $sth->bindValue(":javascript", $this->javascript, \PDO::PARAM_STR); 896 $sth->bindValue(":stylesheet", $this->stylesheet, \PDO::PARAM_STR); 897 $sth->bindValue(":dateOnline", 898 $this->dateOnline->format("Y-m-d H:i:s"), \PDO::PARAM_STR); 899 $sth->bindValue(":dateOffline", $this->dateOffline 900 ? $this->dateOffline->format("Y-m-d H:i:s") 901 : null, \PDO::PARAM_STR); 902 903 $sth->execute(); 904 905 self::touch($this->context, $this->id); 906 907 if ($this->type == \Scrivo\Page::TYPE_SUB_FOLDER) { 908 \Scrivo\SequenceNo::position($this->context, "page", 909 "parent_id", $this->id, 0); 910 } 911 912 // TODO ************** 913 // $this->_commit_subfolder(); 914 // $this->set_pretty_path(); 915 916 unset($this->context->cache[$this->id]); 917 unset($this->context->cache[$this->parentId]); 918 919 } catch(\PDOException $e) { 920 throw new \Scrivo\ResourceException($e); 921 } 922 } 923 924 /** 925 * Check if deletion of page object data does not violate any 926 * business rules. 927 * 928 * @param \Scrivo\Context $context A Scrivo context. 929 * @param int $id The object id of the page definition to select. 930 * 931 * @throws \Scrivo\ApplicationException If the data is not accessible or 932 * if it is not possible to delete the language data. 933 */ 934 private static function validateDelete(\Scrivo\Context $context, $id) { 935 936 $context->checkPermission(\Scrivo\AccessController::WRITE_ACCESS, $id); 937 938 // Is it a labeled page? 939 $sth = $context->connection->prepare( 940 "SELECT COUNT(*) FROM id_label 941 WHERE instance_id = :instId AND id = :id"); 942 943 $context->connection->bindInstance($sth); 944 $sth->bindValue(":id", $id, \PDO::PARAM_INT); 945 946 $sth->execute(); 947 948 if ($sth->fetchColumn(0) > 0) { 949 throw new \Scrivo\ApplicationException( 950 "Trying to delete a labelled page"); 951 } 952 953 // Check the child pages. 954 $sth = $context->connection->prepare( 955 "SELECT page_id, type FROM page WHERE instance_id = :instId 956 AND parent_id = :id AND (has_staging+version) = 0"); 957 958 $context->connection->bindInstance($sth); 959 $sth->bindValue(":id", $id, \PDO::PARAM_INT); 960 961 $sth->execute(); 962 963 $folders = array(); 964 while ($rd = $sth->fetch(\PDO::FETCH_ASSOC)) { 965 if ($rd["type"] == \Scrivo\Page::TYPE_SUB_FOLDER) { 966 // Try to delete it: will generate an exception if not empty. 967 \Scrivo\Page::delete($context, intval($rd["page_id"])); 968 } else { 969 // Throw up on first 'normal' child page found. 970 throw new \Scrivo\ApplicationException( 971 "Trying to delete a page with child pages"); 972 } 973 } 974 } 975 976 /** 977 * Touch (update modification time) a page. 978 * 979 * @param \Scrivo\Context $context A Scrivo context. 980 * @param int $id The id of the page to touch. 981 */ 982 public static function touch(\Scrivo\Context $context, $id) { 983 \Scrivo\ArgumentCheck::assertArgs(func_get_args(), array( 984 null, 985 array(\Scrivo\ArgumentCheck::TYPE_INTEGER) 986 )); 987 try { 988 989 $sth = $context->connection->prepare( 990 "UPDATE page SET date_modified = NOW() 991 WHERE instance_id = :instId AND page_id = :id"); 992 993 $context->connection->bindInstance($sth); 994 $sth->bindValue(":id", $id, \PDO::PARAM_INT); 995 996 $sth->execute(); 997 998 unset($context->cache[$id]); 999 1000 } catch(\PDOException $e) { 1001 throw new \Scrivo\ResourceException($e); 1002 } 1003 } 1004 1005 /** 1006 * Delete an existing page from the database. 1007 * 1008 * First it is is checked if it's possible to delete this page 1009 * then the page data including its dependecies is deleted from 1010 * the database. 1011 * 1012 * @param \Scrivo\Context $context A Scrivo context. 1013 * @param int $id The id of the page to delete. 1014 * 1015 * @throws \Scrivo\ApplicationException If it is not possible to delete 1016 * this page. 1017 */ 1018 public static function delete(\Scrivo\Context $context, $id) { 1019 \Scrivo\ArgumentCheck::assertArgs(func_get_args(), array( 1020 null, 1021 array(\Scrivo\ArgumentCheck::TYPE_INTEGER) 1022 )); 1023 try { 1024 self::validateDelete($context, $id); 1025 1026 $p = \Scrivo\Page::fetch($context, $id); 1027 1028 foreach (array("object_role" => "page_id", 1029 "page_property" => "page_id", 1030 "page_property_html" => "page_id", 1031 "item_list" => "page_id", 1032 "list_item" => "page_id", 1033 "list_item_property" => "page_id", 1034 "id_label" => "id", 1035 "page" => "page_id") as $table => $keyFld) { 1036 1037 $sth = $context->connection->prepare( 1038 "DELETE FROM $table 1039 WHERE instance_id = :instId AND $keyFld = :id"); 1040 1041 $context->connection->bindInstance($sth); 1042 $sth->bindValue(":id", $id, \PDO::PARAM_INT); 1043 1044 $sth->execute(); 1045 } 1046 1047 unset($context->cache[$id]); 1048 unset($context->cache[$p->parentId]); 1049 1050 } catch(\PDOException $e) { 1051 throw new \Scrivo\ResourceException($e); 1052 } 1053 } 1054 1055 /** 1056 * Move a page one position up or down amongst its siblings. 1057 * 1058 * @param int $dir Direction of the move, see \Scrivo\SequenceNo:::MOVE_* 1059 */ 1060 function move($dir=\Scrivo\SequenceNo::MOVE_DOWN) { 1061 1062 if ($this->type == \Scrivo\Page::TYPE_SUB_FOLDER) { 1063 throw new \Scrivo\SystemException("Can't move subfolders"); 1064 } 1065 1066 $this->context->checkPermission( 1067 \Scrivo\AccessController::WRITE_ACCESS, $this->id); 1068 1069 \Scrivo\SequenceNo::position($this->context, "page", 1070 "parent_id", $this->id, $dir); 1071 1072 unset($this->context->cache[$this->parentId]); 1073 1074 } 1075 1076 /** 1077 * Retrieve a page from the database or cache. 1078 * 1079 * @param \Scrivo\Context $context A Scrivo context. 1080 * @param int $id An object id of a page. 1081 * 1082 * @throws \Scrivo\ApplicationException if the page was not readable for 1083 * the user defined in the context. 1084 */ 1085 public static function fetch(\Scrivo\Context $context, $id=null) { 1086 \Scrivo\ArgumentCheck::assertArgs(func_get_args(), array( 1087 null, 1088 array(\Scrivo\ArgumentCheck::TYPE_INTEGER) 1089 ), 1); 1090 try { 1091 1092 // Try to retieve form cache 1093 $p = null; 1094 if (isset($context->cache[$id])) { 1095 // Set the page from cache and set the context. 1096 $p = $context->cache[$id]; 1097 $p->context = $context; 1098 } else { 1099 1100 $sth = $context->connection->prepare( 1101 "SELECT page_id, version, has_staging, 1102 parent_id, sequence_no, type, 1103 page_definition_id, language_id, title, description, keywords, 1104 javascript, stylesheet, date_created, date_modified, date_online, 1105 date_offline 1106 FROM page 1107 WHERE instance_id = :instId AND page_id = :id"); 1108 1109 $context->connection->bindInstance($sth); 1110 $sth->bindValue(":id", $id, \PDO::PARAM_INT); 1111 1112 $sth->execute(); 1113 1114 $rd = $sth->fetch(\PDO::FETCH_ASSOC); 1115 1116 if ($sth->rowCount() != 1) { 1117 throw new \Scrivo\SystemException("Failed to load page"); 1118 } 1119 1120 $p = new \Scrivo\Page(); 1121 $p->setFields($context, $rd); 1122 1123 self::selectProperties($p->context, array($p->id => $p)); 1124 1125 $p->roles = new \Scrivo\RoleSet(); 1126 self::selectRoles($p->context, array($p->id => $p)); 1127 1128 $context->cache[$id] = $p; 1129 } 1130 1131 $p->roles->checkReadPermission($context->principal); 1132 return $p; 1133 1134 } catch(\PDOException $e) { 1135 throw new \Scrivo\ResourceException($e); 1136 } 1137 } 1138 1139 /** 1140 * Select child pages from the database. 1141 * 1142 * @param \Scrivo\Page $page A Scrivo page. 1143 * 1144 * @return \Scrivo\PageSet An array containing the selected pages. 1145 */ 1146 private static function selectChildren(\Scrivo\Page $page) { 1147 try { 1148 $sth = $page->context->connection->prepare( 1149 "SELECT D.page_id, D.version, D.has_staging, D.parent_id, 1150 D.sequence_no, D.type, D.page_definition_id, D.language_id, 1151 D.title, D.description, D.keywords, D.javascript, 1152 D.stylesheet, D.date_created, D.date_modified, D.date_online, 1153 D.date_offline, R.role_id 1154 FROM page D LEFT JOIN object_role R ON 1155 (D.instance_id = R.instance_id AND D.page_id = R.page_id) 1156 WHERE D.instance_id = :instId 1157 AND D.parent_id = :parentId 1158 ORDER BY sequence_no"); 1159 1160 $page->context->connection->bindInstance($sth); 1161 $sth->bindValue(":parentId", $page->id, \PDO::PARAM_INT); 1162 1163 $sth->execute(); 1164 $res = new \Scrivo\PageSet($page); 1165 $p = null; 1166 $lid = 0; 1167 $id = 0; 1168 1169 while ($rd = $sth->fetch(\PDO::FETCH_ASSOC)) { 1170 1171 $id = intval($rd["page_id"]); 1172 1173 if ($lid != $id) { 1174 1175 if ($lid !== 0) { 1176 $page->context->cache[$lid] = $p; 1177 $res[$lid] = $p; 1178 } 1179 $lid = $id; 1180 1181 $p = new \Scrivo\Page(); 1182 $p->setFields($page->context, $rd); 1183 $p->roles = new \Scrivo\RoleSet(); 1184 1185 } 1186 1187 // Add the roles to the role set 1188 $p->roles[] = intval($rd["role_id"]); 1189 } 1190 1191 if ($id) { 1192 $page->context->cache[$id] = $p; 1193 $res[$id] = $p; 1194 } 1195 1196 return $res; 1197 1198 } catch(\PDOException $e) { 1199 throw new \Scrivo\ResourceException($e); 1200 } 1201 } 1202 1203 /** 1204 * Select the page path. 1205 * 1206 * @param \Scrivo\Page $page A Scrivo page. 1207 * 1208 * @return \Scrivo\PageSet An array containing the selected pages. 1209 */ 1210 private static function selectPath(\Scrivo\Page $page) { 1211 try { 1212 1213 $res = new \Scrivo\PageSet($page); 1214 $res->prepend($page); 1215 $target = $page->parentId; 1216 1217 $i = 0; 1218 while ($target) { 1219 1220 if ($target == $page->id) { 1221 throw new \Scrivo\SystemException("Path loop"); 1222 } 1223 1224 if (isset($page->context->cache[$target])) { 1225 1226 $p = $page->context->cache[$target]; 1227 1228 } else { 1229 1230 $sth = $page->context->connection->prepare( 1231 "SELECT D.page_id, D.version, D.has_staging, 1232 D.parent_id, D.sequence_no, D.type, 1233 D.page_definition_id, D.language_id, 1234 D.title, D.description, D.keywords, D.javascript, 1235 D.stylesheet, D.date_created, D.date_modified, 1236 D.date_online, D.date_offline, R.role_id 1237 FROM page D LEFT JOIN object_role R ON 1238 (D.instance_id = R.instance_id AND 1239 D.page_id = R.page_id) 1240 WHERE D.instance_id = :instId AND 1241 D.page_id = :parentId AND 1242 (D.has_staging+D.version) = 0"); 1243 1244 $page->context->connection->bindInstance($sth); 1245 $sth->bindValue(":parentId", $target, \PDO::PARAM_INT); 1246 1247 $sth->execute(); 1248 $p = null; 1249 1250 while ($rd = $sth->fetch(\PDO::FETCH_ASSOC)) { 1251 1252 if (!$p) { 1253 $p = new \Scrivo\Page(); 1254 $p->setFields($page->context, $rd); 1255 $p->roles = new \Scrivo\RoleSet(); 1256 $target = intval($rd["parent_id"]); 1257 } 1258 1259 // Add the roles to the role set 1260 $p->roles[] = intval($rd["role_id"]); 1261 } 1262 1263 if ($p) { 1264 $page->context->cache[$p->id] = $p; 1265 } else { 1266 throw new \Scrivo\SystemException( 1267 "Failed to load page"); 1268 } 1269 1270 } 1271 1272 $res->prepend($p); 1273 1274 $target = $p->parentId; 1275 1276 } 1277 1278 return $res; 1279 1280 } catch(\PDOException $e) { 1281 throw new \Scrivo\ResourceException($e); 1282 } 1283 } 1284 1285 } 1286 1287 ?>
Documentation generated by phpDocumentor 2.0.0a12 and ScrivoDocumentor on August 29, 2013