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 (&lt;html lang&gt;).
  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 (&lt;title&gt;).
  84   * @property \Scrivo\String $description The page description
  85   *    (&lt;description&gt;).
  86   * @property \Scrivo\String $keywords The keywords for this page
  87   *    (&lt;keywords&gt;).
  88   * @property \Scrivo\String $javascript A javascript script for this page
  89   *    (&lt;script&gt;).
  90   * @property \Scrivo\String $stylesheet Additional CSS syle rules for this
  91   *    page (&lt;stylesheet&gt;).
  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   *    (&lt;html lang&gt;).
  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 (&lt;html lang&gt;).
 166       * @var int
 167       */
 168      private $languageId 0;
 169  
 170      /**
 171       * The page title (&lt;title&gt;).
 172       * @var \Scrivo\String
 173       */
 174      private $title null;
 175  
 176      /**
 177       * The page description (&lt;description&gt;).
 178       * @var \Scrivo\String
 179       */
 180      private $description null;
 181  
 182      /**
 183       * The keywords for this page (&lt;keywords&gt;).
 184       * @var \Scrivo\String
 185       */
 186      private $keywords null;
 187  
 188      /**
 189       * A javascript script for this page (&lt;script&gt;).
 190       * @var \Scrivo\String
 191       */
 192      private $javascript null;
 193  
 194      /**
 195       * Additional CSS syle rules for this page (&lt;stylesheet&gt;).
 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"]) == 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_PAGEself::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 (&lt;html lang&gt;).
 519       *
 520       * @param int $languageId The id the main language for the page
 521       *    (&lt;html lang&gt;).
 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 (&lt;title&gt;).
 532       *
 533       * @param \Scrivo\String $title The page title (&lt;title&gt;).
 534       */
 535      private function setTitle(\Scrivo\String $title) {
 536          $this->title $title;
 537      }
 538  
 539      /**
 540       * Set the page description (&lt;description&gt;).
 541       *
 542       * @param \Scrivo\String $description The page description
 543       *   (&lt;description&gt;).
 544       */
 545      private function setDescription(\Scrivo\String $description) {
 546          $this->description $description;
 547      }
 548  
 549      /**
 550       * Set the keywords for this page (&lt;keywords&gt;).
 551       *
 552       * @param \Scrivo\String $keywords The keywords for this page
 553       *   (&lt;keywords&gt;).
 554       */
 555      private function setKeywords(\Scrivo\String $keywords) {
 556          $this->keywords $keywords;
 557      }
 558  
 559      /**
 560       * Set a javascript script for this page (&lt;script&gt;).
 561       *
 562       * @param \Scrivo\String $javascript A javascript script for this
 563       *    page (&lt;script&gt;).
 564       */
 565      private function setJavascript(\Scrivo\String $javascript) {
 566          $this->javascript $javascript;
 567      }
 568  
 569      /**
 570       * Set additional CSS syle rules for this page (&lt;stylesheet&gt;).
 571       *
 572       * @param \Scrivo\String $stylesheet Additional CSS syle rules for this
 573       *   page (&lt;stylesheet&gt;).
 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->id0);
 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($contextintval($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