Overview

Namespaces

  • None
  • PHP

Classes

  • Breadcrumb
  • Breadcrumbs
  • CacheFile
  • Comment
  • CommentArray
  • CommentFilter
  • CommentForm
  • CommentList
  • Config
  • Database
  • DatabaseQuery
  • DatabaseQuerySelect
  • DatabaseQuerySelectFulltext
  • DatabaseStopwords
  • Debug
  • Field
  • Fieldgroup
  • Fieldgroups
  • FieldgroupsArray
  • Fields
  • FieldsArray
  • Fieldtype
  • FieldtypeMulti
  • Fieldtypes
  • FileLog
  • FilenameArray
  • Fuel
  • HookEvent
  • ImageSizer
  • Inputfield
  • InputfieldsArray
  • InputfieldWrapper
  • Language
  • LanguageParser
  • Languages
  • LanguagesPageFieldValue
  • LanguageSupportInstall
  • LanguageTranslator
  • Markdown_Parser
  • MarkdownExtra_Parser
  • ModuleJS
  • ModulePlaceholder
  • Modules
  • Notice
  • NoticeError
  • NoticeMessage
  • Notices
  • NullPage
  • Page
  • PageArray
  • Pagefile
  • Pagefiles
  • PagefilesManager
  • PageFinder
  • Pageimage
  • Pageimages
  • PagerNav
  • PagerNavItem
  • Pages
  • PagesAccess
  • PagesSortfields
  • PagesType
  • Paths
  • Permission
  • Permissions
  • Process
  • ProcessController
  • ProcessWire
  • Role
  • Roles
  • Sanitizer
  • Selector
  • SelectorBitwiseAnd
  • SelectorContains
  • SelectorContainsLike
  • SelectorContainsWords
  • SelectorEnds
  • SelectorEqual
  • SelectorGreaterThan
  • SelectorGreaterThanEqual
  • SelectorLessThan
  • SelectorLessThanEqual
  • SelectorNotEqual
  • Selectors
  • SelectorStarts
  • Session
  • SessionCSRF
  • SmartyPants_Parser
  • SmartyPantsTypographer_Parser
  • SystemUpdate1
  • Template
  • TemplateFile
  • Templates
  • TemplatesArray
  • Textformatter
  • Textile
  • User
  • Users
  • Wire
  • WireArray
  • WireData
  • WireInput
  • WireInputData
  • WireSaveableItems
  • WireSaveableItemsLookup
  • WireUpload

Interfaces

  • CommentFormInterface
  • CommentListInterface
  • ConfigurableModule
  • FieldtypeLanguageInterface
  • FieldtypePageTitleCompatible
  • HasLookupItems
  • HasRoles
  • InputfieldHasArrayValue
  • Module
  • Saveable
  • TrackChanges

Exceptions

  • ProcessController404Exception
  • ProcessControllerPermissionException
  • Wire404Exception
  • WireDatabaseException
  • WireException
  • WirePermissionException

Functions

  • __
  • _n
  • _x
  • fuel
  • identify_modifier_markdown
  • Markdown
  • mdwp_add_p
  • mdwp_hide_tags
  • mdwp_show_tags
  • mdwp_strip_p
  • ProcessWireClassLoader
  • ProcessWireHostSiteConfig
  • ProcessWireShutdown
  • removeNewlines
  • SmartDashes
  • SmartEllipsis
  • SmartQuotes
  • smarty_modifier_markdown
  • smarty_modifier_smartypants
  • SmartyPants
  • tabIndent
  • unregisterGLOBALS
  • wire
  • wireDecodeJSON
  • wireEncodeJSON
  • wireMkdir
  • Overview
  • Namespace
  • Class
  • Tree
  • Download
   1: <?php
   2: 
   3: /**
   4:  * ProcessWire Page
   5:  *
   6:  * Page is the class used by all instantiated pages and it provides functionality for:
   7:  *
   8:  * 1. Providing get/set access to the Page's properties
   9:  * 2. Accessing the related hierarchy of pages (i.e. parents, children, sibling pages)
  10:  * 
  11:  * ProcessWire 2.x 
  12:  * Copyright (C) 2010 by Ryan Cramer 
  13:  * Licensed under GNU/GPL v2, see LICENSE.TXT
  14:  * 
  15:  * http://www.processwire.com
  16:  * http://www.ryancramer.com
  17:  *
  18:  */
  19: 
  20: class Page extends WireData {
  21: 
  22:     /*
  23:      * The following constant flags are specific to a Page's 'status' field. A page can have 1 or more flags using bitwise logic. 
  24:      * Status levels 1024 and above are excluded from search by the core. Status levels 16384 and above are runtime only and not 
  25:      * stored in the DB unless for logging or page history.
  26:      *
  27:      * If the under 1024 status flags are expanded in the future, it must be ensured that the combined value of the searchable flags 
  28:      * never exceeds 1024, otherwise issues in Pages::find() will need to be considered. 
  29:      *
  30:      * The status levels 16384 and above can safely be changed as needed as they are runtime only. 
  31:      *
  32:      */
  33:     const statusOn = 1;             // base status for all pages
  34:     const statusLocked = 4;         // page locked for changes. Not enforced by the core, but checked by Process modules. 
  35:     const statusSystemID = 8;       // page is for the system and may not be deleted or have it's id changed (everything else, okay)
  36:     const statusSystem = 16;        // page is for the system and may not be deleted or have it's id, name, template or parent changed
  37:     const statusHidden = 1024;      // page is excluded selector methods like $pages->find() and $page->children() unless status is specified, like "status&1"
  38:     const statusUnpublished = 2048;     // page is not published and is not renderable. 
  39:     const statusTrash = 8192;       // page is in the trash
  40:     const statusDeleted = 16384;        // page is deleted (runtime only)
  41:     const statusSystemOverride = 32768;     // page is in a state where system flags may be overridden
  42:     const statusCorrupted = 131072;     // page was corrupted at runtime and is NOT saveable: see setFieldValue() and $outputFormatting. (runtime)
  43:     const statusMax = 9999999;      // number to use for max status comparisons, runtime only
  44: 
  45:     /**
  46:      * The Template this page is using (object)
  47:      *
  48:      */
  49:     protected $template; 
  50: 
  51:     /**
  52:      * The previous template used by the page, if it was changed during runtime.    
  53:      *
  54:      * Allows Pages::save() to delete data that's no longer used. 
  55:      *
  56:      */
  57:     private $templatePrevious; 
  58: 
  59:     /**
  60:      * Parent Page - Instance of Page
  61:      *
  62:      */
  63:     protected $parent = null;
  64: 
  65:     /**
  66:      * The previous parent used by the page, if it was changed during runtime.  
  67:      *
  68:      * Allows Pages::save() to identify when the parent has changed
  69:      *
  70:      */
  71:     private $parentPrevious; 
  72: 
  73:     /**
  74:      * Reference to the Page's template file, used for output. Instantiated only when asked for. 
  75:      *
  76:      */
  77:     private $output; 
  78: 
  79:     /**
  80:      * Instance of PageFilesManager, which manages and migrates file versions for this page
  81:      *
  82:      * Only instantiated upon request, so access only from filesManager() method in Page class. 
  83:      * Outside API can use $page->filesManager.
  84:      *
  85:      */
  86:     private $filesManager = null;
  87: 
  88:     /**
  89:      * RolesArray containing Role instances that are assigned to this page. 
  90:      *
  91:      * Dynamically set as needed, refer only to roles() method rather than this directly. 
  92:      *
  93:     private $rolesArray = null;
  94:      */
  95: 
  96:     /**
  97:      * Field data that queues while the page is loading. 
  98:      *
  99:      * Once setIsLoaded(true) is called, this data is processed and instantiated into the Page and the fieldDataQueue is emptied (and no longer relevant)   
 100:      *
 101:      */
 102:     protected $fieldDataQueue = array();
 103: 
 104:     /**
 105:      * Is this a new page (not yet existing in the database)?
 106:      *
 107:      */
 108:     protected $isNew = true; 
 109: 
 110:     /**
 111:      * Is this Page finished loading from the DB (i.e. Pages::getById)?
 112:      *
 113:      * When false, it is assumed that any values set need to be woken up. 
 114:      * When false, it also assumes that built-in properties (like name) don't need to be sanitized. 
 115:      *
 116:      * Note: must be kept in the 'true' state. Pages::getById sets it to false before populating data and then back to true when done.
 117:      *
 118:      */
 119:     protected $isLoaded = true; 
 120: 
 121:     /**
 122:      * Is this page allowing it's output to be formatted?
 123:      *
 124:      * If so, the page may not be saveable because calls to $page->get(field) are returning versions of 
 125:      * variables that may have been formatted at runtime for output. An exception will be thrown if you
 126:      * attempt to set the value of a formatted field when $outputFormatting is on. 
 127:      *
 128:      * Output formatting should be turned off for pages that you are manipulating and saving. 
 129:      * Whereas it should be turned on for pages that are being used for output on a public site. 
 130:      * Having it on means that Textformatters and any other output formatters will be executed
 131:      * on any values returned by this page. Likewise, any values you set to the page while outputFormatting
 132:      * is set to true are considered potentially corrupt. 
 133:      *
 134:      */
 135:     protected $outputFormatting = false; 
 136: 
 137:     /**
 138:      * A unique instance ID assigned to the page at the time it's loaded (for debugging purposes only)
 139:      *
 140:      */
 141:     protected $instanceID = 0; 
 142: 
 143:     /**
 144:      * IDs for all the instances of pages, used for debugging and testing.
 145:      *
 146:      * Indexed by $instanceID => $pageID
 147:      *
 148:      */
 149:     static public $instanceIDs = array();
 150: 
 151:     /**
 152:      * Stack of ID indexed Page objects that are currently in the loading process. 
 153:      *
 154:      * Used to avoid possible circular references when multiple pages referencing each other are being populated at the same time.
 155:      *
 156:      */
 157:     static public $loadingStack = array();
 158: 
 159:     /**
 160:      * The current page number, starting from 1
 161:      *
 162:      */
 163:     protected $pageNum = 1; 
 164: 
 165:     /**
 166:      * Reference to main config, optimization so that get() method doesn't get called
 167:      *
 168:      */
 169:     protected $config = null; 
 170: 
 171:     /**
 172:      * Page-specific settings which are either saved in pages table, or generated at runtime.
 173:      *
 174:      */
 175:     protected $settings = array(
 176:         'id' => 0, 
 177:         'name' => '', 
 178:         'status' => 1, 
 179:         'numChildren' => 0, 
 180:         'sort' => 0, 
 181:         'sortfield' => 'sort', 
 182:         'modified_users_id', 
 183:         'created_users_id',
 184:         ); 
 185: 
 186:     /**
 187:      * Create a new page in memory. 
 188:      *
 189:      * @param Template $tpl Template object this page should use. 
 190:      *
 191:      */
 192:     public function __construct(Template $tpl = null) {
 193: 
 194:         if(!is_null($tpl)) $this->template = $tpl;
 195:         $this->useFuel(false); // prevent fuel from being in local scope
 196:         $this->parentPrevious = null;
 197:         $this->templatePrevious = null;
 198:     }
 199: 
 200:     /**
 201:      * Destruct this page instance
 202:      *
 203:      */
 204:     public function __destruct() {
 205:         if($this->instanceID) {
 206:             // remove from the record of instanceID, so that we have record of page's that HAVEN'T been destructed. 
 207:             unset(self::$instanceIDs[$this->instanceID]); 
 208:         }
 209:     }
 210: 
 211:     /**
 212:      * Clone this page instance
 213:      *
 214:      */
 215:     public function __clone() {
 216:         $track = $this->trackChanges();
 217:         $this->setTrackChanges(false); 
 218:         if($this->filesManager) {
 219:             $this->filesManager = clone $this->filesManager; 
 220:             $this->filesManager->setPage($this);
 221:         }
 222:         foreach($this->template->fieldgroup as $field) {
 223:             $name = $field->name; 
 224:             if(!$field->type->isAutoload() && !isset($this->data[$name])) continue; // important for draft loading
 225:             $value = $this->get($name); 
 226:             if(is_object($value)) {
 227:                 if(!$value instanceof Page) $this->set($name, clone $value); // attempt re-commit
 228:                 if($value instanceof Pagefiles) $this->get($name)->setPage($this); 
 229:             }
 230:         }
 231:         $this->instanceID .= ".clone";
 232:         if($track) $this->setTrackChanges(true); 
 233:     }
 234: 
 235:     /**
 236:      * Set the value of a page property
 237:      *
 238:      * @param string $key Property to set
 239:      * @param mixed $value
 240:      * @return Page Reference to this Page
 241:      * @see __set
 242:      *
 243:      */
 244:     public function set($key, $value) {
 245: 
 246:         if(($key == 'id' || $key == 'name') && $this->settings[$key] && $value != $this->settings[$key]) 
 247:             if( ($key == 'id' && (($this->settings['status'] & Page::statusSystem) || ($this->settings['status'] & Page::statusSystemID))) ||
 248:                 ($key == 'name' && (($this->settings['status'] & Page::statusSystem)))) {
 249:                     throw new WireException("You may not modify '$key' on page '{$this->path}' because it is a system page"); 
 250:         }
 251: 
 252:         switch($key) {
 253:             case 'id':
 254:                 if(!$this->isLoaded) Page::$loadingStack[(int) $value] = $this;
 255:             case 'sort': 
 256:             case 'numChildren': 
 257:             case 'num_children':
 258:                 if($key == 'num_children') $key = 'numChildren';
 259:                 if($this->settings[$key] !== $value) $this->trackChange($key); 
 260:                 $this->settings[$key] = (int) $value; 
 261:                 break;
 262:             case 'status':
 263:                 $this->setStatus($value); 
 264:                 break;
 265:             case 'name':
 266:                 if($this->isLoaded) {
 267:                     $beautify = empty($this->settings[$key]); 
 268:                     $value = $this->fuel('sanitizer')->pageName($value, $beautify); 
 269:                     if($this->settings[$key] !== $value) $this->trackChange($key); 
 270:                 }
 271:                 $this->settings[$key] = $value; 
 272:                 break;
 273:             case 'parent': 
 274:             case 'parent_id':
 275:                 if(($key == 'parent_id' || is_int($value)) && $value) $value = $this->fuel('pages')->get((int)$value); 
 276:                     else if(is_string($value)) $value = $this->fuel('pages')->get($value); 
 277:                 if($value) $this->setParent($value);
 278:                 break;
 279:             case 'template': 
 280:             case 'templates_id':
 281:                 if($key == 'templates_id' && $this->template && $this->template->id == $value) break;
 282:                 if($key == 'templates_id') $value = $this->fuel('templates')->get((int)$value); 
 283:                 $this->setTemplate($value); 
 284:                 break;
 285:             case 'created': 
 286:             case 'modified':
 287:                 if(!ctype_digit("$value")) $value = strtotime($value); 
 288:                 $this->settings[$key] = (int) $value; 
 289:                 break;
 290:             case 'created_users_id':
 291:             case 'modified_users_id': 
 292:                 $this->settings[$key] = (int) $value; 
 293:                 break;
 294:             case 'createdUser':
 295:             case 'modifiedUser':
 296:                 $this->setUser($value, strpos($key, 'created') === 0 ? 'created' : 'modified'); 
 297:                 break;
 298:             case 'sortfield':
 299:                 $value = $this->fuel('pages')->sortfields()->decode($value); 
 300:                 if($this->settings[$key] != $value) $this->trackChange($key); 
 301:                 $this->settings[$key] = $value; 
 302:                 break;
 303:             case 'isLoaded': 
 304:                 $this->setIsLoaded($value); 
 305:                 break;
 306:             case 'pageNum':
 307:                 $this->pageNum = ((int) $value) > 1 ? (int) $value : 1; 
 308:                 break;
 309:             case 'instanceID': 
 310:                 $this->instanceID = $value; 
 311:                 self::$instanceIDs[$value] = $this->settings['id']; 
 312:                 break;
 313:             default:
 314:                 $this->setFieldValue($key, $value, $this->isLoaded); 
 315: 
 316:         }
 317:         return $this; 
 318:     }
 319: 
 320: 
 321:     /**
 322:      * Set the value of a field that is defined in the page's Fieldgroup
 323:      *
 324:      * This may not be called when outputFormatting is on. 
 325:      *
 326:      * This is for internal use. API should generally use the set() method, but this is kept public for the minority of instances where it's useful.
 327:      *
 328:      * @param string $key
 329:      * @param mixed $value
 330:      * @param bool $load Should the existing value be loaded for change comparisons? (applicable only to non-autoload fields)
 331:      *
 332:      */
 333:     public function setFieldValue($key, $value, $load = true) {
 334: 
 335:         if(!$this->template) throw new WireException("You must assign a template to the page before setting custom field values."); 
 336: 
 337:         // if the page is not yet loaded and a '__' field was set, then we queue it so that the loaded() method can 
 338:         // instantiate all those fields knowing that all parts of them are present for wakeup. 
 339:         if(!$this->isLoaded && strpos($key, '__')) {
 340:             list($key, $subKey) = explode('__', $key); 
 341:             if(!isset($this->fieldDataQueue[$key])) $this->fieldDataQueue[$key] = array();
 342:             $this->fieldDataQueue[$key][$subKey] = $value; 
 343:             return;
 344:         }
 345: 
 346:         if(!$field = $this->template->fieldgroup->getField($key)) {
 347:             // not a known/saveable field, let them use it for runtime storage
 348:             return parent::set($key, $value); 
 349:         }
 350: 
 351:         // if a null value is set, then ensure the proper blank type is set to the field
 352:         if(is_null($value)) {
 353:             return parent::set($key, $field->type->getBlankValue($this, $field)); 
 354:         }
 355: 
 356:         // if the page is currently loading from the database, we assume that any set values are 'raw' and need to be woken up
 357:         if(!$this->isLoaded) {
 358: 
 359:             // send the value to the Fieldtype to be woken up for storage in the page
 360:             $value = $field->type->wakeupValue($this, $field, $value); 
 361: 
 362:             // page is currently loading, so we don't need to continue any further
 363:             return parent::set($key, $value); 
 364:         }
 365: 
 366:         // check if the field hasn't been already loaded
 367:         if(is_null(parent::get($key))) {
 368:             // this field is not currently loaded. if the $load param is true, then ...
 369:             // retrieve old value first in case it's not autojoined so that change comparisons and save's work 
 370:             if($load && $this->isLoaded) $this->get($key); 
 371: 
 372:         } else if($this->outputFormatting && $field->type->formatValue($this, $field, $value) != $value) {
 373:             // The field has been loaded or dereferenced from the API, and this field changes when formatters are applied to it. 
 374:             // There is a good chance they are trying to set a formatted value, and we don't allow this situation because the 
 375:             // possibility of data corruption is high. We set the Page::statusCorrupted status so that Pages::save() can abort.
 376:             $this->set('status', $this->status | self::statusCorrupted); 
 377:         }
 378: 
 379:         // ensure that the value is in a safe format and set it 
 380:         $value = $field->type->sanitizeValue($this, $field, $value); 
 381: 
 382:         parent::set($key, $value); 
 383:     }
 384: 
 385:     /**
 386:      * Get the value of a requested Page property
 387:      *
 388:      * @param string $key
 389:      * @return mixed
 390:      * @see __get
 391:      *
 392:      */
 393:     public function get($key) {
 394:         $value = null;
 395:         switch($key) {
 396:             case 'parent_id':
 397:             case 'parentID': 
 398:                 $value = $this->parent ? $this->parent->id : 0; 
 399:                 break;
 400:             case 'child':
 401:                 $value = $this->child(); 
 402:                 break;
 403:             case 'children':
 404:             case 'subpages': // PW1 
 405:                 $value = $this->children();
 406:                 break;
 407:             case 'parent':
 408:             case 'parents':
 409:             case 'rootParent':
 410:             case 'siblings':
 411:             case 'next':
 412:             case 'prev':
 413:             case 'url':
 414:             case 'path':
 415:             case 'outputFormatting': 
 416:             case 'isTrash': 
 417:                 $value = $this->{$key}(); 
 418:                 break;
 419:             case 'httpUrl': 
 420:             case 'httpURL': 
 421:                 $value = $this->httpUrl();
 422:                 break;
 423:             case 'fieldgroup': 
 424:             case 'fields':
 425:                 $value = $this->template->fieldgroup; 
 426:                 break; 
 427:             case 'template':
 428:             case 'templatePrevious':
 429:             case 'parentPrevious':
 430:             case 'isLoaded':
 431:             case 'isNew':
 432:             case 'pageNum':
 433:             case 'instanceID': 
 434:                 $value = $this->$key; 
 435:                 break;
 436:             case 'out':
 437:             case 'output':
 438:                 $value = $this->getTemplateFile();
 439:                 break;
 440:             case 'filesManager':
 441:                 $value = $this->filesManager();
 442:                 break;
 443:             case 'name':
 444:                 $value = $this->settings['name'];
 445:                 break;
 446:             case 'modified_users_id': 
 447:             case 'modifiedUsersID':
 448:             case 'modifiedUserID':
 449:                 $value = $this->settings['created_users_id']; 
 450:                 break;
 451:             case 'created_users_id':
 452:             case 'createdUsersID':
 453:             case 'createdUserID': 
 454:                 $value = $this->settings['modified_users_id'];
 455:                 break;
 456:             case 'modifiedUser':
 457:                 if(!$value = $this->fuel('users')->get($this->settings['modified_users_id'])) $value = new NullUser(); 
 458:                 break;
 459:             case 'createdUser':
 460:                 if(!$value = $this->fuel('users')->get($this->settings['created_users_id'])) $value = new NullUser(); 
 461:                 break;
 462:             case 'urlSegment':
 463:                 $value = $this->fuel('input')->urlSegment1; // deprecated, but kept for backwards compatibility
 464:                 break;
 465:             case 'accessTemplate': 
 466:                 $value = $this->getAccessTemplate();
 467:                 break;
 468:             default:
 469:                 if($key && isset($this->settings[(string)$key])) return $this->settings[$key]; 
 470: 
 471:                 if(($value = $this->getFieldFirstValue($key)) === null) {
 472:                     if(($value = $this->getFieldValue($key)) === null) {
 473:                         // if there is a selector, we'll assume they are using the get() method to get a child
 474:                         if(Selectors::stringHasOperator($key)) $value = $this->child($key); 
 475:                     }
 476:                 }
 477:         }
 478: 
 479:         return $value; 
 480:     }
 481: 
 482:     /**
 483:      * Given a Multi Key, determine if there are multiple keys requested and return the first non-empty value
 484:      *
 485:      * A Multi Key is a string with multiple field names split by pipes, i.e. headline|title
 486:      *
 487:      * Example: browser_title|headline|title - Return the value of the first field that is non-empty
 488:      *
 489:      * @param string $key
 490:      * @return null|mixed Returns null if no values match, or if there aren't multiple keys split by "|" chars
 491:      *
 492:      */
 493:     protected function getFieldFirstValue($multiKey) {
 494: 
 495:         // looking multiple keys split by "|" chars, and not an '=' selector
 496:         if(strpos($multiKey, '|') === false || strpos($multiKey, '=') !== false) return null;
 497: 
 498:         $value = null;
 499:         $keys = explode('|', $multiKey); 
 500: 
 501:         foreach($keys as $key) {
 502:             $value = $this->getFieldValue($key);
 503:             if(is_string($value)) $value = trim($value); 
 504:             if($value) break;
 505:         }
 506: 
 507:         return $value;
 508:     }
 509: 
 510:     /**
 511:      * Get the value for a non-native page field, and call upon Fieldtype to join it if not autojoined
 512:      *
 513:      * @param string $key
 514:      * @return null|mixed
 515:      *
 516:      */
 517:     protected function getFieldValue($key) {
 518:         if(!$this->template) return null;
 519:         $field = $this->template->fieldgroup->getField($key); 
 520:         $value = parent::get($key); 
 521:         if(!$field) return $value;  // likely a runtime field, not part of our data
 522: 
 523:         // if the value is already loaded, return it 
 524:         if(!is_null($value)) return $this->outputFormatting ? $field->type->formatValue($this, $field, $value) : $value; 
 525:         $track = $this->trackChanges();
 526:         $this->setTrackChanges(false); 
 527:         $value = $field->type->loadPageField($this, $field); 
 528:         if(is_null($value)) $value = $field->type->getDefaultValue($this, $field); 
 529:             else $value = $field->type->wakeupValue($this, $field, $value); 
 530: 
 531:         // if outputFormatting is being used, turn it off because it's not necessary here and may throw an exception
 532:         $outputFormatting = $this->outputFormatting; 
 533:         if($outputFormatting) $this->setOutputFormatting(false); 
 534:         $this->setFieldValue($key, $value, false); 
 535:         if($outputFormatting) $this->setOutputFormatting(true); 
 536:         
 537:         $value = parent::get($key);     
 538:         if(is_object($value) && $value instanceof Wire) $value->setTrackChanges(true);
 539:         if($track) $this->setTrackChanges(true); 
 540:         return $this->outputFormatting ? $field->type->formatValue($this, $field, $value) : $value; 
 541:     }
 542: 
 543:     /**
 544:      * Get the raw/unformatted value of a field, regardless of what $this->outputFormatting is set at
 545:      *
 546:      */
 547:     public function getUnformatted($key) {
 548:         $outputFormatting = $this->outputFormatting; 
 549:         if($outputFormatting) $this->setOutputFormatting(false); 
 550:         $value = $this->get($key); 
 551:         if($outputFormatting) $this->setOutputFormatting(true); 
 552:         return $value; 
 553:     }
 554: 
 555: 
 556:     /**
 557:      * @see get
 558:      *
 559:      */
 560:     public function __get($key) {
 561:         return $this->get($key); 
 562:     }
 563: 
 564:     /**
 565:      * @see set
 566:      *
 567:      */
 568:     public function __set($key, $value) {
 569:         $this->set($key, $value); 
 570:     }
 571: 
 572:     /**
 573:      * Set the 'status' setting, with some built-in protections
 574:      *
 575:      */
 576:     protected function setStatus($value) {
 577:         $value = (int) $value; 
 578:         $override = $this->settings['status'] & Page::statusSystemOverride; 
 579:         if(!$override) { 
 580:             if($this->settings['status'] & Page::statusSystemID) $value = $value | Page::statusSystemID;
 581:             if($this->settings['status'] & Page::statusSystem) $value = $value | Page::statusSystem; 
 582:         }
 583:         if($this->settings['status'] != $value) $this->trackChange('status');
 584:         $this->settings['status'] = $value;
 585:     }
 586: 
 587:     /**
 588:      * Set this Page's Template object
 589:      *
 590:      */
 591:     protected function setTemplate($tpl) {
 592:         if(!is_object($tpl)) $tpl = $this->fuel('templates')->get($tpl); 
 593:         if(!$tpl instanceof Template) throw new WireException("Invalid value sent to Page::setTemplate"); 
 594:         if($this->template && $this->template->id != $tpl->id) {
 595:             if($this->settings['status'] & Page::statusSystem) throw new WireException("Template changes are disallowed on this page"); 
 596:             if(is_null($this->templatePrevious)) $this->templatePrevious = $this->template; 
 597:             $this->trackChange('template'); 
 598:         }
 599:         $this->template = $tpl; 
 600:         return $this;
 601:     }
 602: 
 603: 
 604:     /**
 605:      * Set this page's parent Page
 606:      *
 607:      */
 608:     protected function setParent(Page $parent) {
 609:         if($this->parent && $this->parent->id == $parent->id) return $this; 
 610:         $this->trackChange('parent');
 611:         if(($this->parent && $this->parent->id) && $this->parent->id != $parent->id) {
 612:             if($this->settings['status'] & Page::statusSystem) throw new WireException("Parent changes are disallowed on this page"); 
 613:             $this->parentPrevious = $this->parent; 
 614:         }
 615:         $this->parent = $parent; 
 616:         return $this; 
 617:     }
 618: 
 619: 
 620:     /**
 621:      * Set either the createdUser or the modifiedUser 
 622:      *
 623:      * @param User|int|string User object or integer/string representation of User
 624:      * @param string $userType Must be either 'created' or 'modified' 
 625:      * @return this
 626:      *
 627:      */
 628:     protected function setUser($user, $userType) {
 629: 
 630:         if(!$user instanceof User) $user = $this->fuel('users')->get($user); 
 631: 
 632:         // if they are setting an invalid user or unknown user, then the Page defaults to the super user
 633:         if(!$user || !$user->id) $user = $this->fuel('users')->get(User::superUserID); 
 634: 
 635:         if($userType == 'created') $field = 'createdUser';
 636:             else if($userType == 'modified') $field = 'modifiedUser';
 637:             else throw new WireException("Unknown user type in Page::setUser(user, type)"); 
 638: 
 639:         $existingUser = $this->$field; 
 640:         if($existingUser && $existingUser->id != $user->id) $this->trackChange($field); 
 641:         $this->$field = $user; 
 642:         return $this;   
 643:     }
 644: 
 645:     /**
 646:      * Return this page's parent Page
 647:      *
 648:      */
 649:     public function parent() {
 650:         return $this->parent ? $this->parent : new NullPage(); 
 651:     }
 652: 
 653:     /**
 654:      * Find Pages in the descendent hierarchy
 655:      *
 656:      * Same as Pages::find() except that the results are limited to descendents of this Page
 657:      *
 658:      * @param string $selector
 659:      *
 660:      */
 661:     public function find($selector = '', $options = array()) {
 662:         if(!$this->numChildren) return new PageArray();
 663:         $selector = "has_parent={$this->id}, $selector"; 
 664:         return $this->fuel('pages')->find(trim($selector, ", "), $options); 
 665:     }
 666: 
 667:     /**
 668:      * Return this page's children pages, optionally filtered by a selector
 669:      *
 670:      * @param string $selector Selector to use, or blank to return all children
 671:      * @return PageArray
 672:      *
 673:      */
 674:     public function children($selector = '', $options = array()) {
 675:         if(!$this->numChildren) return new PageArray();
 676:         if($selector) $selector .= ", ";
 677:         $selector = "parent_id={$this->id}, $selector"; 
 678:         if(strpos($selector, 'sort=') === false) $selector .= "sort={$this->sortfield}"; 
 679:         return $this->fuel('pages')->find(trim($selector, ", "), $options); 
 680:     }
 681: 
 682:     /**
 683:      * Return the page's first single child that matches the given selector. 
 684:      *
 685:      * Same as children() but returns a Page object or NullPage (with id=0) rather than a PageArray
 686:      *
 687:      * @param string $selector Selector to use, or blank to return the first child. 
 688:      * @return Page|NullPage
 689:      *
 690:      */
 691:     public function child($selector = '', $options = array()) {
 692:         $selector .= ($selector ? ', ' : '') . "limit=1";
 693:         if(strpos($selector, 'start=') === false) $selector .= ", start=0"; // prevent pagination
 694:         $children = $this->children($selector); 
 695:         return count($children) ? $children->first() : new NullPage();
 696:     }
 697: 
 698:     /**
 699:      * Return this page's NON-HIDDEN children pages, optionally filtered by a selector
 700:      *
 701:      * This is suitable to call for generating navigation.
 702:      *
 703:      * @deprecated This functionality is now handled by default with the regular children() method. 
 704:      *
 705:      */
 706:     public function navChildren($selector = '') {
 707:         if($this->fuel('config')->debug) throw new WireException("Deprecated function call: please use children() rather than navChildren()"); 
 708:         return $this->children($selector); 
 709:     }
 710: 
 711:     /**
 712:      * Return this page's parent pages. 
 713:      *
 714:      */
 715:     public function parents() {
 716:         $parents = new PageArray();
 717:         $parent = $this->parent();
 718:         while($parent && $parent->id) {
 719:             $parents->prepend($parent);     
 720:             $parent = $parent->parent();
 721:         }
 722:         return $parents; 
 723:     }
 724: 
 725:     /**
 726:      * Get the lowest-level, non-homepage parent of this page
 727:      *
 728:      * rootParents typically comprise the first level of navigation on a site. 
 729:      *
 730:      * @return Page 
 731:      *
 732:      */
 733:     public function rootParent() {
 734:         if(!$this->parent || !$this->parent->id || $this->parent->id === 1) return $this; 
 735:         $parents = $this->parents();
 736:         $parents->shift(); // shift off homepage
 737:         return $parents->first();
 738:     }
 739: 
 740:     /**
 741:      * Return this Page's sibling pages, optionally filtered by a selector. 
 742:      *
 743:      */
 744:     public function siblings($selector = '') {
 745:         if($selector) $selector .= ", ";
 746:         $selector = "parent_id={$this->parent_id}, $selector";
 747:         if(strpos($selector, 'sort=') === false) $selector .= "sort=" . ($this->parent ? $this->parent->sortfield : 'sort'); 
 748:         return $this->fuel('pages')->find(trim($selector, ", ")); 
 749:     }
 750: 
 751:     /**
 752:      * Return the next sibling page
 753:      *
 754:      * If given a PageArray of siblings (containing the current) it will return the next sibling relative to the provided PageArray.
 755:      *
 756:      * Be careful with this function when the page has a lot of siblings. It has to load them all, so this function is best
 757:      * avoided at large scale, unless you provide your own already-reduced siblings list (like from pagination)
 758:      *
 759:      * @param PageArray $siblings Optional siblings to use instead of the default. 
 760:      * @return Page|NullPage Returns the next sibling page, or a NullPage if none found. 
 761:      *
 762:      */
 763:     public function next(PageArray $siblings = null) {
 764:         if(is_null($siblings)) $siblings = $this->parent->children();
 765:         $next = $siblings->getNext($this); 
 766:         if(is_null($next)) $next = new NullPage();
 767:         return $next; 
 768:     }
 769: 
 770:     /**
 771:      * Return the previous sibling page
 772:      *
 773:      * If given a PageArray of siblings (containing the current) it will return the previous sibling relative to the provided PageArray.
 774:      *
 775:      * Be careful with this function when the page has a lot of siblings. It has to load them all, so this function is best
 776:      * avoided at large scale, unless you provide your own already-reduced siblings list (like from pagination)
 777:      *
 778:      * @param PageArray $siblings Optional siblings to use instead of the default. 
 779:      * @return Page|NullPage Returns the previous sibling page, or a NullPage if none found. 
 780:      *
 781:      */
 782:     public function prev(PageArray $siblings = null) {
 783:         if(is_null($siblings)) $siblings = $this->parent->children();
 784:         $prev = $siblings->getPrev($this);
 785:         if(is_null($prev)) $prev = new NullPage();
 786:         return $prev;
 787:     }
 788: 
 789:     /**
 790:      * Save this page to the database. 
 791:      *
 792:      * To hook into this (___save), use 'Pages::save' 
 793:      * To hook into a field-only save, use 'Pages::saveField'
 794:      *
 795:      * @param Field|string $field Optional field to save (name of field or Field object)
 796:      *
 797:      */
 798:     public function save($field = null) {
 799:         if(!is_null($field)) return $this->fuel('pages')->saveField($this, $field);
 800:         return $this->fuel('pages')->save($this);
 801:     }
 802: 
 803:     /**
 804:      * Delete this page from the Database
 805:      *
 806:      * Throws WireException if action not allowed. 
 807:      * See Pages::delete for a hookable version. 
 808:      *
 809:      * @return bool True on success
 810:      *
 811:      */
 812:     public function delete() {
 813:         return $this->fuel('pages')->delete($this); 
 814:     }
 815: 
 816:     /**
 817:      * Move this page to the trash
 818:      *
 819:      * Throws WireException if action is not allowed. 
 820:      * See Pages::trash for a hookable version. 
 821:      *
 822:      * @return bool True on success
 823:      *
 824:      */
 825:     public function trash() {
 826:         return $this->fuel('pages')->trash($this); 
 827:     }
 828: 
 829:     /**
 830:      * Allow iteration of the page's properties with foreach(), fulfilling IteratorAggregate interface.
 831:      *
 832:      */
 833:     public function getIterator() {
 834:         $a = $this->settings; 
 835:         foreach($this->template->fieldgroup as $field) {
 836:             $a[$field->name] = $this->get($field->name); 
 837:         }
 838:         return new ArrayObject($a);     
 839:     }
 840: 
 841: 
 842:     /**
 843:      * Has the Page (or optionally one of it's fields) changed since it was loaded?
 844:      *
 845:      * Assumes that Pages has turned on this Page's change tracking with a call to setTrackChanges(). 
 846:      * Pages that are new (i.e. don't yet exist in the DB) always return true. 
 847:      * 
 848:      * @param string $what If specified, only checks the given property for changes rather than the whole page. 
 849:      * @return bool 
 850:      *
 851:      */
 852:     public function isChanged($what = '') {
 853:         if($this->isNew()) return true; 
 854:         if(parent::isChanged($what)) return true; 
 855:         $changed = false;
 856:         if($what) {
 857:             $value = $this->get($what); 
 858:             if(is_object($value) && $value instanceof Wire) 
 859:                 $changed = $value->isChanged(); 
 860:         } else {
 861:             foreach($this->data as $key => $value) {
 862:                 if(is_object($value) && $value instanceof Wire)
 863:                     $changed = $value->isChanged();
 864:                 if($changed) break;
 865:             }
 866:         }
 867: 
 868:         return $changed;    
 869:     }
 870: 
 871: 
 872:     /**
 873:      * Returns the Page's ID in a string
 874:      *
 875:      */
 876:     public function __toString() {
 877:         return "{$this->id}"; 
 878:     }
 879: 
 880:     /**
 881:      * Returns the Page's path from the site root. 
 882:      *
 883:      */
 884:     public function path() {
 885:         return self::isHooked('Page::path()') ? $this->__call('path', array()) : $this->___path();
 886:     }
 887: 
 888:     /**
 889:      * Provides the hookable implementation for the path() method.
 890:      *
 891:      * The method we're using here by having a real path() function above is slightly quicker than just letting 
 892:      * PW's hook handler handle it all. We're taking this approach since path() is a function that can feasibly
 893:      * be called hundreds or thousands of times in a request, so we want it as optimized as possible.
 894:      *
 895:      */
 896:     protected function ___path() {
 897:         if($this->id === 1) return '/';
 898:         $path = '';
 899:         $parents = $this->parents();
 900:         foreach($parents as $parent) if($parent->id > 1) $path .= "/{$parent->name}";
 901:         return $path . '/' . $this->name . '/'; 
 902:     }
 903: 
 904:     /**
 905:      * Like path() but comes from server document root (which may or may not be different)
 906:      *
 907:      * Does not include urlSegment, if applicable. 
 908:      * Does not include protocol and hostname -- use httpUrl() for that.
 909:      *
 910:      * @see path
 911:      *
 912:      */
 913:     public function url() {
 914:         $url = rtrim($this->fuel('config')->urls->root, "/") . $this->path(); 
 915:         if($this->template->slashUrls === 0 && $this->settings['id'] > 1) $url = rtrim($url, '/'); 
 916:         return $url;
 917:     }
 918: 
 919:     /**
 920:      * Like URL, but includes the protocol and hostname
 921:      *
 922:      */
 923:     public function httpUrl() {
 924: 
 925:         switch($this->template->https) {
 926:             case -1: $protocol = 'http'; break;
 927:             case 1: $protocol = 'https'; break;
 928:             default: $protocol = $this->fuel('config')->https ? 'https' : 'http'; 
 929:         }
 930: 
 931:         return "$protocol://" . $this->fuel('config')->httpHost . $this->url();
 932:     }
 933: 
 934:     /**
 935:      * Get the output TemplateFile object for rendering this page
 936:      *
 937:      * You can retrieve the results of this by calling $page->out or $page->output
 938:      *
 939:      * @return TemplateFile
 940:      *
 941:      */
 942:     protected function getTemplateFile() {
 943:         if($this->output) return $this->output; 
 944:         if(!$this->template) return null;
 945:         $this->output = new TemplateFile($this->template->filename); 
 946:         $fuel = self::getAllFuel();
 947:         $this->output->set('wire', $fuel); 
 948:         foreach($fuel as $key => $value) $this->output->set($key, $value); 
 949:         $this->output->set('page', $this); 
 950:         return $this->output; 
 951:     }
 952: 
 953: 
 954:     /**
 955:      * Return a Inputfield object that contains all the custom Inputfield objects required to edit this page
 956:      *
 957:      */
 958:     public function getInputfields() {
 959:         return $this->template ? $this->template->fieldgroup->getPageInputfields($this) : null;
 960:     }
 961: 
 962: 
 963:     /** 
 964:      * Does this page have the specified status number or template name? 
 965:      *
 966:      * See status flag constants at top of Page class
 967:      *
 968:      * @param int|string|Selectors $status Status number or Template name or selector string/object
 969:      * @return bool
 970:      *
 971:      */
 972:     public function is($status) {
 973: 
 974:         if(is_int($status)) {
 975:             return ((bool) ($this->status & $status)); 
 976: 
 977:         } else if(is_string($status) && $this->fuel('sanitizer')->name($status) == $status) {
 978:             // valid template name
 979:             if($this->template->name == $status) return true; 
 980: 
 981:         } else if($this->matches($status)) { 
 982:             // Selectors object or selector string
 983:             return true; 
 984:         }
 985: 
 986:         return false;
 987:     }
 988: 
 989:     /**
 990:      * Given a Selectors object or a selector string, return whether this Page matches it
 991:      *
 992:      * @param string|Selectors $s
 993:      * @return bool
 994:      *
 995:      */
 996:     public function matches($s) {
 997: 
 998:         if(is_string($s)) {
 999:             if(!Selectors::stringHasOperator($s)) return false;
1000:             $selectors = new Selectors($s); 
1001: 
1002:         } else if($s instanceof Selectors) {
1003:             $selectors = $s; 
1004: 
1005:         } else { 
1006:             return false;
1007:         }
1008: 
1009:         $matches = false;
1010: 
1011:         foreach($selectors as $selector) {
1012:             $name = $selector->field;
1013:             if(in_array($name, array('limit', 'start', 'sort', 'include'))) continue; 
1014:             $matches = true; 
1015:             $value = $this->get($name); 
1016:             if(!$selector->matches("$value")) {
1017:                 $matches = false; 
1018:                 break;
1019:             }
1020:         }
1021: 
1022:         return $matches; 
1023:     }
1024: 
1025:     /**
1026:      * Add the specified status flag to this page's status
1027:      *
1028:      * @param int $statusFlag
1029:      * @return this
1030:      *
1031:      */
1032:     public function addStatus($statusFlag) {
1033:         $statusFlag = (int) $statusFlag; 
1034:         $this->status = $this->status | $statusFlag; 
1035:         return $this;
1036:     }
1037: 
1038:     /** 
1039:      * Remove the specified status flag from this page's status
1040:      *
1041:      * @param int $statusFlag
1042:      * @return this
1043:      *
1044:      */
1045:     public function removeStatus($statusFlag) {
1046:         $statusFlag = (int) $statusFlag; 
1047:         $override = $this->settings['status'] & Page::statusSystemOverride; 
1048:         if($statusFlag == Page::statusSystem || $statusFlag == Page::statusSystemID) {
1049:             if(!$override) throw new WireException("You may not remove the 'system' status from a page"); 
1050:         }
1051:         $this->status = $this->status & ~$statusFlag; 
1052:         return $this;
1053:     }
1054: 
1055:     /**
1056:      * Does this page have a 'hidden' status?
1057:      *
1058:      * @return bool
1059:      *
1060:      */
1061:     public function isHidden() {
1062:         return $this->is(self::statusHidden); 
1063:     }
1064: 
1065:     /**
1066:      * Is this Page new? (i.e. doesn't yet exist in DB)
1067:      *
1068:      */
1069:     public function isNew() {
1070:         return $this->isNew; 
1071:     }
1072: 
1073:     /**
1074:      * Is the page fully loaded?
1075:      *
1076:      */
1077:     public function isLoaded() {
1078:         return $this->isLoaded; 
1079:     }
1080: 
1081:     /**
1082:      * Is this Page in the trash?
1083:      *
1084:      * @return bool
1085:      *
1086:      */ 
1087:     public function isTrash() {
1088:         if($this->is(self::statusTrash)) return true; 
1089:         $trashPageID = $this->fuel('config')->trashPageID; 
1090:         if($this->id == $trashPageID) return true; 
1091:         // this is so that isTrash() still returns the correct result, even if the page was just trashed and not yet saved
1092:         foreach($this->parents() as $parent) if($parent->id == $trashPageID) return true; 
1093:         return false;
1094:     }
1095: 
1096:     /**
1097:      * Set the value for isNew, i.e. doesn't exist in the DB
1098:      *
1099:      * @param bool @isNew
1100:      * @return this
1101:      *
1102:      */
1103:     public function setIsNew($isNew) {
1104:         $this->isNew = $isNew ? true : false; 
1105:         return $this; 
1106:     }
1107: 
1108:     /**
1109:      * Set that the Page is fully loaded
1110:      *
1111:      * Pages::getById sets this once it has completed loading the page
1112:      * This method also triggers the loaded() method that hooks may listen to
1113:      *
1114:      * @param bool $isLoaded
1115:      *
1116:      */
1117:     public function setIsLoaded($isLoaded) {
1118:         if($isLoaded) {
1119:             $this->processFieldDataQueue();
1120:             unset(Page::$loadingStack[$this->settings['id']]); 
1121:         }
1122:         $this->isLoaded = $isLoaded ? true : false; 
1123:         if($isLoaded) $this->loaded();
1124:         return $this; 
1125:     }
1126: 
1127:     /**
1128:      * Process and instantiate any data in the fieldDataQueue
1129:      *
1130:      * This happens after setIsLoaded(true) is called
1131:      *
1132:      */
1133:     protected function processFieldDataQueue() {
1134: 
1135:         foreach($this->fieldDataQueue as $key => $value) {
1136: 
1137:             $field = $this->fieldgroup->get($key); 
1138:             if(!$field) continue;
1139: 
1140:             // check for autojoin multi fields, which may have multiple values bundled into one string
1141:             // as a result of an sql group_concat() function
1142:             if($field->type instanceof FieldtypeMulti && ($field->flags & Field::flagAutojoin)) {
1143:                 foreach($value as $k => $v) {   
1144:                     if(is_string($v) && strpos($v, FieldtypeMulti::multiValueSeparator) !== false) {
1145:                         $value[$k] = explode(FieldtypeMulti::multiValueSeparator, $v);  
1146:                     }
1147:                 }
1148:             }
1149: 
1150:             // if all there is in the array is 'data', then we make that the value rather than keeping an array
1151:             // this is so that Fieldtypes that only need to interact with a single value don't have to receive an array of data
1152:             if(count($value) == 1 && array_key_exists('data', $value)) $value = $value['data']; 
1153: 
1154:             $this->setFieldValue($key, $value, false); 
1155:         }
1156:         $this->fieldDataQueue = array(); // empty it out, no longer needed
1157:     }
1158: 
1159: 
1160:     /**
1161:      * For hooks to listen to, triggered when page is loaded and ready
1162:      *
1163:      */
1164:     public function ___loaded() { }
1165: 
1166: 
1167:     /**
1168:      * Set if this page's output is allowed to be filtered by runtime formatters. 
1169:      *
1170:      * Pages used for output should have it on. 
1171:      * Pages you intend to manipulate and save should have it off. 
1172:      *
1173:      * @param bool @outputFormatting Optional, default true
1174:      * @return this
1175:      *
1176:      */
1177:     public function setOutputFormatting($outputFormatting = true) {
1178:         $this->outputFormatting = $outputFormatting ? true : false; 
1179:         return $this; 
1180:     }
1181: 
1182:     /**
1183:      * Return true if outputFormatting is on, false if not. 
1184:      *
1185:      * @return bool
1186:      *
1187:      */
1188:     public function outputFormatting() {
1189:         return $this->outputFormatting; 
1190:     }
1191: 
1192:     /**
1193:      * Shorter version of setOutputFormatting() and outputFormatting() function
1194:      *
1195:      * Always returns the current state of outputFormatting like the outputFormatting() function (and unlike setOutputFormatting())
1196:      * You may optionally specify a boolean value for $outputFormatting which will set the current state, like setOutputFormatting().
1197:      *
1198:      * @param bool $outputFormatting If specified, sets outputFormatting ON or OFF. If not specified, outputFormatting status does not change. 
1199:      * @return bool Current outputFormatting state. 
1200:      *
1201:      */
1202:     public function of($outputFormatting = null) {
1203:         if(!is_null($outputFormatting)) $this->outputFormatting = $outputFormatting ? true : false; 
1204:         return $this->outputFormatting; 
1205:     }
1206: 
1207:     /**
1208:      * Return instance of PagefileManager specific to this Page
1209:      *
1210:      * @return PageFilesManager
1211:      *
1212:      */
1213:     public function filesManager() {
1214:         if(is_null($this->filesManager)) $this->filesManager = new PagefilesManager($this); 
1215:         return $this->filesManager; 
1216:     }
1217: 
1218:     /**
1219:      * Prepare the page and it's fields for removal from runtime memory, called primarily by Pages::uncache()
1220:      *
1221:      */
1222:     public function uncache() {
1223:         if($this->template) {
1224:             foreach($this->template->fieldgroup as $field) {
1225:                 $value = parent::get($field->name);
1226:                 if($value != null && is_object($value)) {
1227:                     if(method_exists($value, 'uncache')) $value->uncache();
1228:                     parent::set($field->name, null); 
1229:                 }
1230:             }
1231:         }
1232:         if($this->filesManager) $this->filesManager->uncache(); 
1233:         $this->filesManager = null;
1234:     }
1235: 
1236:     /**
1237:      * Ensures that isset() and empty() work for this classes properties. 
1238:      *
1239:      */
1240:     public function __isset($key) {
1241:         if(isset($this->settings[$key])) return true; 
1242:         return parent::__isset($key); 
1243:     }
1244: 
1245:     /**
1246:      * Returns the parent page that has the template from which we get our role/access settings from
1247:      *
1248:      * @return Page|NullPage Returns NullPage if none found
1249:      *
1250:      */
1251:     public function getAccessParent() {
1252:         if($this->template->useRoles || $this->settings['id'] === 1) return $this;
1253:         $parent = $this->parent();  
1254:         if($parent->id) return $parent->getAccessParent();
1255:         return new NullPage();
1256:     }
1257: 
1258:     /**
1259:      * Returns the template from which we get our role/access settings from
1260:      *
1261:      * @return Template|null Returns null if none   
1262:      *
1263:      */
1264:     public function getAccessTemplate() {
1265:         $parent = $this->getAccessParent();
1266:         if(!$parent->id) return null;
1267:         return $parent->template; 
1268:     }
1269:     
1270:     /**
1271:      * Return the PageArray of roles that have access to this page
1272:      *
1273:      * This is determined from the page's template. If the page's template has roles turned off, 
1274:      * then it will go down the tree till it finds usable roles to use. 
1275:      *
1276:      * @return PageArray
1277:      *
1278:      */
1279:     public function getAccessRoles() {
1280:         $template = $this->getAccessTemplate();
1281:         if($template) return $template->roles; 
1282:         return new PageArray();
1283:     }
1284: 
1285:     /**
1286:      * Returns whether this page has the given access role
1287:      *
1288:      * Given access role may be a role name, role ID or Role object
1289:      *
1290:      * @param string|int|Role $role 
1291:      * @return bool
1292:      *
1293:      */
1294:     public function hasAccessRole($role) {
1295:         $roles = $this->getAccessRoles();
1296:         if(is_string($role)) return $roles->has("name=$role"); 
1297:         if($role instanceof Role) return $roles->has($role); 
1298:         if(is_int($role)) return $roles->has("id=$role"); 
1299:         return false;
1300:     }
1301: 
1302: 
1303:     /** REMOVED
1304:     public function roles() {}
1305:     public function addRole($role) {}
1306:     public function addsRole($role) {}
1307:     public function hasRole($role) {}
1308:     public function removeRole($role) {}
1309:     public function removesRole($role) {}
1310:      */
1311: 
1312: }
1313: 
1314: /**
1315:  * Placeholder class for non-existant and non-saveable Page
1316:  *
1317:  */
1318: class NullPage extends Page { 
1319: 
1320:     // public function roles() { return new RolesArray(); }
1321:     public function path() { return ''; }
1322:     public function url() { return ''; }
1323:     public function set($key, $value) { return $this; }
1324:     public function parent() { return null; }
1325:     public function parents() { return new PageArray(); } 
1326:     public function __toString() { return ""; }
1327:     public function isHidden() { return true; }
1328:     public function filesManager() { return null; }
1329:     public function rootParent() { return new NullPage(); }
1330:     public function siblings($selector = '', $options = array()) { return new PageArray(); }
1331:     public function children($selector = '', $options = array()) { return new PageArray(); }
1332:     public function getAccessParent() { return new NullPage(); }
1333:     public function getAccessRoles() { return new PageArray(); }
1334:     public function hasAccessRole($role) { return false; }
1335: 
1336: }
1337: 
1338: 
1339: 
1340: 
ProcessWire API documentation generated by ApiGen 2.6.0