Webylon 3.1 API Docs
  • Package
  • Class
  • Tree
  • Deprecated
  • Download
Version: current
  • 3.2
  • 3.1

Packages

  • auth
  • Booking
  • cart
    • shipping
    • steppedcheckout
  • Catalog
  • cms
    • assets
    • batchaction
    • batchactions
    • bulkloading
    • comments
    • content
    • core
    • export
    • newsletter
    • publishers
    • reports
    • security
    • tasks
  • Dashboard
  • DataObjectManager
  • event
  • faq
  • forms
    • actions
    • core
    • fields-basic
    • fields-dataless
    • fields-datetime
    • fields-files
    • fields-formatted
    • fields-formattedinput
    • fields-relational
    • fields-structural
    • transformations
    • validators
  • googlesitemaps
  • guestbook
  • installer
  • newsletter
  • None
  • photo
    • gallery
  • PHP
  • polls
  • recaptcha
  • sapphire
    • api
    • bulkloading
    • control
    • core
    • cron
    • dev
    • email
    • fields-formattedinput
    • filesystem
    • formatters
    • forms
    • i18n
    • integration
    • misc
    • model
    • parsers
    • search
    • security
    • tasks
    • testing
    • tools
    • validation
    • view
    • widgets
  • seo
    • open
      • graph
  • sfDateTimePlugin
  • spamprotection
  • stealth
    • captha
  • subsites
  • userform
    • pagetypes
  • userforms
  • webylon
  • widgets

Classes

  • ArrayData
  • CalendarWidget
  • DataObjectManagerAction
  • LiveCalendarWidget
  • MonthNavigator
  • Requirements
  • Requirements_Backend
  • SSViewer
  • SSViewer_Cached_PartialParser
  • SSViewer_FromString
  • SSViewer_PartialParser
  • ViewableData
  • ViewableData_Customised
  • ViewableData_Debugger
   1 <?php
   2 /**
   3  * A ViewableData object is any object that can be rendered into a template/view.
   4  *
   5  * A view interrogates the object being currently rendered in order to get data to render into the template. This data
   6  * is provided and automatically escaped by ViewableData. Any class that needs to be available to a view (controllers,
   7  * {@link DataObject}s, page controls) should inherit from this class.
   8  *
   9  * @package sapphire
  10  * @subpackage view
  11  */
  12 class ViewableData extends Object implements IteratorAggregate {
  13     
  14     /**
  15      * An array of objects to cast certain fields to. This is set up as an array in the format:
  16      *
  17      * <code>
  18      * public static $casting = array (
  19      *     'FieldName' => 'ClassToCastTo(Arguments)'
  20      * );
  21      * </code>
  22      *
  23      * @var array
  24      */
  25     public static $casting = array (
  26         'BaseHref'   => 'Varchar',
  27         'CSSClasses' => 'Varchar'
  28     );
  29     
  30     /**
  31      * The default object to cast scalar fields to if casting information is not specified, and casting to an object
  32      * is required.
  33      *
  34      * @var string
  35      */
  36     public static $default_cast = 'HTMLVarchar';
  37     
  38     /**
  39      * @var array
  40      */
  41     private static $casting_cache = array();
  42     
  43     // -----------------------------------------------------------------------------------------------------------------
  44     
  45     /**
  46      * @var int
  47      */
  48     protected $iteratorPos, $iteratorTotalItems;
  49     
  50     /**
  51      * A failover object to attempt to get data from if it is not present on this object.
  52      *
  53      * @var ViewableData
  54      */
  55     protected $failover;
  56     
  57     /**
  58      * @var ViewableData
  59      */
  60     protected $customisedObject;
  61     
  62     /**
  63      * @var array
  64      */
  65     private $objCache = array();
  66     
  67     // -----------------------------------------------------------------------------------------------------------------
  68     
  69     /**
  70      * Converts a field spec into an object creator. For example: "Int" becomes "new Int($fieldName);" and "Varchar(50)"
  71      * becomes "new Varchar($fieldName, 50);".
  72      *
  73      * @param string $fieldSchema The field spec
  74      * @return string
  75      */
  76     public static function castingObjectCreator($fieldSchema) {
  77         user_error("Deprecated in a breaking way, use Object::create_from_string()", E_USER_WARNING);
  78     }
  79     
  80     /**
  81      * Convert a field schema (e.g. "Varchar(50)") into a casting object creator array that contains both a className
  82      * and castingHelper constructor code. See {@link castingObjectCreator} for more information about the constructor.
  83      *
  84      * @param string $fieldSchema
  85      * @return array
  86      */
  87     public static function castingObjectCreatorPair($fieldSchema) {
  88         user_error("Deprecated in a breaking way, use Object::create_from_string()", E_USER_WARNING);
  89     }
  90     
  91     // FIELD GETTERS & SETTERS -----------------------------------------------------------------------------------------
  92     
  93     /**
  94      * Check if a field exists on this object or its failover.
  95      *
  96      * @param string $property
  97      * @return bool
  98      */
  99     public function __isset($property) {
 100         return $this->hasField($property) || ($this->failover && $this->failover->hasField($property));
 101     }
 102     
 103     /**
 104      * Get the value of a property/field on this object. This will check if a method called get{$property} exists, then
 105      * check if a field is available using {@link ViewableData::getField()}, then fall back on a failover object.
 106      *
 107      * @param string $property
 108      * @return mixed
 109      */
 110     public function __get($property) {
 111         if($this->hasMethod($method = "get$property")) {
 112             return $this->$method();
 113         } elseif($this->hasField($property)) {
 114             return $this->getField($property);
 115         } elseif($this->failover) {
 116             return $this->failover->$property;
 117         }
 118     }
 119     
 120     /**
 121      * Set a property/field on this object. This will check for the existence of a method called set{$property}, then
 122      * use the {@link ViewableData::setField()} method.
 123      *
 124      * @param string $property
 125      * @param mixed $value
 126      */
 127     public function __set($property, $value) {
 128         if($this->hasMethod($method = "set$property")) {
 129             $this->$method($value);
 130         } else {
 131             $this->setField($property, $value);
 132         }
 133     }
 134     
 135     /**
 136      * Check if a field exists on this object. This should be overloaded in child classes.
 137      *
 138      * @param string $field
 139      * @return bool
 140      */
 141     public function hasField($field) {
 142         return property_exists($this, $field);
 143     }
 144     
 145     /**
 146      * Get the value of a field on this object. This should be overloaded in child classes.
 147      *
 148      * @param string $field
 149      * @return mixed
 150      */
 151     public function getField($field) {
 152         return $this->$field;
 153     }
 154     
 155     /**
 156      * Set a field on this object. This should be overloaded in child classes.
 157      *
 158      * @param string $field
 159      * @param mixed $value
 160      */
 161     public function setField($field, $value) {
 162         $this->$field = $value;
 163     }
 164     
 165     // -----------------------------------------------------------------------------------------------------------------
 166     
 167     /**
 168      * Add methods from the {@link ViewableData::$failover} object, as well as wrapping any methods prefixed with an
 169      * underscore into a {@link ViewableData::cachedCall()}.
 170      */
 171     public function defineMethods() {
 172         if($this->failover) {
 173             if(is_object($this->failover)) $this->addMethodsFrom('failover');
 174             else user_error("ViewableData::\$failover set to a non-object", E_USER_WARNING);
 175             
 176             if(isset($_REQUEST['debugfailover'])) {
 177                 Debug::message("$this->class created with a failover class of {$this->failover->class}");
 178             }
 179         }
 180         
 181         foreach($this->allMethodNames() as $method) {
 182             if($method[0] == '_' && $method[1] != '_') {
 183                 $this->createMethod (
 184                     substr($method, 1), "return \$obj->cachedCall('$method', '" . substr($method, 1) . "', \$args);"
 185                 );
 186             }
 187         }
 188         
 189         parent::defineMethods();
 190     }
 191     
 192     /**
 193      * Merge some arbitrary data in with this object. This method returns a {@link ViewableData_Customised} instance
 194      * with references to both this and the new custom data.
 195      *
 196      * Note that any fields you specify will take precedence over the fields on this object.
 197      *
 198      * @param array|ViewableData $data
 199      * @return ViewableData_Customised
 200      */
 201     public function customise($data) {
 202         if(is_array($data) && (empty($data) || ArrayLib::is_associative($data))) {
 203             $data = new ArrayData($data);
 204         }
 205         
 206         if($data instanceof ViewableData) {
 207             return new ViewableData_Customised($this, $data);
 208         }
 209         
 210         throw new InvalidArgumentException (
 211             'ViewableData->customise(): $data must be an associative array or a ViewableData instance'
 212         );
 213     }
 214     
 215     /**
 216      * @param ViewableData $object
 217      */
 218     public function setCustomisedObj(ViewableData $object) {
 219         $this->customisedObject = $object;
 220     }
 221     
 222     // CASTING ---------------------------------------------------------------------------------------------------------
 223     
 224     /**
 225      * Get the class a field on this object would be casted to, as well as the casting helper for casting a field to
 226      * an object (see {@link ViewableData::castingHelper()} for information on casting helpers).
 227      *
 228      * The returned array contains two keys:
 229      *  - className: the class the field would be casted to (e.g. "Varchar")
 230      *  - castingHelper: the casting helper for casting the field (e.g. "return new Varchar($fieldName)")
 231      *
 232      * @param string $field
 233      * @return array
 234      */
 235     public function castingHelperPair($field) {
 236         user_error("castingHelperPair() Deprecated, use castingHelper() instead", E_USER_NOTICE);
 237         return $this->castingHelper($field);
 238     }
 239 
 240     /**
 241      * Return the "casting helper" (a piece of PHP code that when evaluated creates a casted value object) for a field
 242      * on this object.
 243      *
 244      * @param string $field
 245      * @return string
 246      */
 247     public function castingHelper($field) {
 248         if($this->hasMethod('db') && $fieldSpec = $this->db($field)) {
 249             return $fieldSpec;
 250         }
 251 
 252         $specs = Object::combined_static(get_class($this), 'casting');
 253         if(isset($specs[$field])) return $specs[$field];
 254 
 255         if($this->failover) return $this->failover->castingHelper($field);
 256     }
 257     
 258     /**
 259      * Get the class name a field on this object will be casted to
 260      *
 261      * @param string $field
 262      * @return string
 263      */
 264     public function castingClass($field) {
 265         $spec = $this->castingHelper($field);
 266         if(!$spec) return null;
 267         
 268         $bPos = strpos($spec,'(');
 269         if($bPos === false) return $spec;
 270         else return substr($spec, 0, $bPos-1);
 271     }
 272     
 273     /**
 274      * Return the string-format type for the given field.
 275      *
 276      * @param string $field
 277      * @return string 'xml'|'raw'
 278      */
 279     public function escapeTypeForField($field) {
 280         if(!$class = $this->castingClass($field)) {
 281             $class = self::$default_cast;
 282         }
 283         
 284         return Object::get_static($class, 'escape_type');
 285     }
 286     
 287     /**
 288      * Save the casting cache for this object (including data from any failovers) into a variable
 289      *
 290      * @param reference $cache
 291      */
 292     public function buildCastingCache(&$cache) {
 293         $ancestry = array_reverse(ClassInfo::ancestry($this->class));
 294         $merge    = true;
 295         
 296         foreach($ancestry as $class) {
 297             if(!isset(self::$casting_cache[$class]) && $merge) {
 298                 $mergeFields = is_subclass_of($class, 'DataObject') ? array('db', 'casting') : array('casting');
 299                 
 300                 if($mergeFields) foreach($mergeFields as $field) {
 301                     $casting = Object::uninherited_static($class, $field);
 302                     
 303                     if($casting) foreach($casting as $field => $cast) {
 304                         if(!isset($cache[$field])) $cache[$field] = self::castingObjectCreatorPair($cast);
 305                     }
 306                 }
 307                 
 308                 if($class == 'ViewableData') $merge = false;
 309             } elseif($merge) {
 310                 $cache = ($cache) ? array_merge(self::$casting_cache[$class], $cache) : self::$casting_cache[$class];
 311             }
 312             
 313             if($class == 'ViewableData') break;
 314         }
 315     }
 316     
 317     // TEMPLATE ACCESS LAYER -------------------------------------------------------------------------------------------
 318     
 319     /**
 320      * Render this object into the template, and get the result as a string. You can pass one of the following as the
 321      * $template parameter:
 322      *  - a template name (e.g. Page)
 323      *  - an array of possible template names - the first valid one will be used
 324      *  - an SSViewer instance
 325      *
 326      * @param string|array|SSViewer $template the template to render into
 327      * @param array $customFields fields to customise() the object with before rendering
 328      * @return string
 329      */
 330     public function renderWith($template, $customFields = null) {
 331         if(!is_object($template)) {
 332             $template = new SSViewer($template);
 333         }
 334         
 335         $data = ($this->customisedObject) ? $this->customisedObject : $this;
 336         
 337         if(is_array($customFields) || $customFields instanceof ViewableData) {
 338             $data = $data->customise($customFields);
 339         }
 340         
 341         if($template instanceof SSViewer) {
 342             return $template->process($data);
 343         }
 344         
 345         throw new UnexpectedValueException (
 346             "ViewableData::renderWith(): unexpected $template->class object, expected an SSViewer instance"
 347         );
 348     }
 349     
 350     /**
 351      * Get the value of a field on this object, automatically inserting the value into any available casting objects
 352      * that have been specified.
 353      *
 354      * @param string $fieldName
 355      * @param array $arguments
 356      * @param bool $forceReturnedObject if TRUE, the value will ALWAYS be casted to an object before being returned,
 357      *        even if there is no explicit casting information
 358      * @param string $cacheName a custom cache name
 359      */
 360     public function obj($fieldName, $arguments = null, $forceReturnedObject = true, $cache = false, $cacheName = null) {
 361         if(isset($_REQUEST['debug_profile'])) {
 362             Profiler::mark("obj.$fieldName", "on a $this->class object");
 363         }
 364         
 365         if(!$cacheName) $cacheName = $arguments ? $fieldName . implode(',', $arguments) : $fieldName;
 366         
 367         if(!isset($this->objCache[$cacheName])) {
 368             if($this->hasMethod($fieldName)) {
 369                 $value = $arguments ? call_user_func_array(array($this, $fieldName), $arguments) : $this->$fieldName();
 370             } else {
 371                 $value = $this->$fieldName;
 372             }
 373             
 374             if(!is_object($value) && ($this->castingClass($fieldName) || $forceReturnedObject)) {
 375                 if(!$castConstructor = $this->castingHelper($fieldName)) {
 376                     $castConstructor = $this->stat('default_cast');
 377                 }
 378                 
 379                 $valueObject = Object::create_from_string($castConstructor, $fieldName);
 380                 $valueObject->setValue($value, ($this->hasMethod('getAllFields') ? $this->getAllFields() : null));
 381                 
 382                 $value = $valueObject;
 383             }
 384             
 385             if($cache) $this->objCache[$cacheName] = $value;
 386         } else {
 387             $value = $this->objCache[$cacheName];
 388         }
 389         
 390         if(isset($_REQUEST['debug_profile'])) {
 391             Profiler::unmark("obj.$fieldName", "on a $this->class object");
 392         }
 393         
 394         if(!is_object($value) && $forceReturnedObject) {
 395             $default = Object::get_static('ViewableData', 'default_cast');
 396             $value   = new $default($fieldName);
 397         }
 398         
 399         return $value;
 400     }
 401     
 402     /**
 403      * A simple wrapper around {@link ViewableData::obj()} that automatically caches the result so it can be used again
 404      * without re-running the method.
 405      *
 406      * @param string $field
 407      * @param array $arguments
 408      * @param string $identifier an optional custom cache identifier
 409      */
 410     public function cachedCall($field, $arguments = null, $identifier = null) {
 411         return $this->obj($field, $arguments, false, true, $identifier);
 412     }
 413     
 414     /**
 415      * Checks if a given method/field has a valid value. If the result is an object, this will return the result of the
 416      * exists method, otherwise will check if the result is not just an empty paragraph tag.
 417      *
 418      * @param string $field
 419      * @param array $arguments
 420      * @param bool $cache
 421      * @return bool
 422      */
 423     public function hasValue($field, $arguments = null, $cache = true) {
 424         if (!$field) {
 425             return false;
 426         }
 427         $result = $cache ? $this->cachedCall($field, $arguments) : $this->obj($field, $arguments, false, false);
 428         
 429         if(is_object($result)) {
 430             return $result->exists();
 431         } else {
 432             return ($result && $result !== '<p></p>');
 433         }
 434     }
 435     
 436     /**#@+
 437      * @param string $field
 438      * @param array $arguments
 439      * @param bool $cache
 440      * @return string
 441      */
 442     
 443     /**
 444      * Get the string value of a field on this object that has been suitable escaped to be inserted directly into a
 445      * template.
 446      */
 447     public function XML_val($field, $arguments = null, $cache = false) {
 448         $result = $this->obj($field, $arguments, false, $cache);
 449         return (is_object($result) && method_exists($result, 'forTemplate')) ? $result->forTemplate() : $result;
 450     }
 451     
 452     /**
 453      * Return the value of the field without any escaping being applied.
 454      */
 455     public function RAW_val($field, $arguments = null, $cache = true) {
 456         return Convert::xml2raw($this->XML_val($field, $arguments, $cache));
 457     }
 458     
 459     /**
 460      * Return the value of a field in an SQL-safe format.
 461      */
 462     public function SQL_val($field, $arguments = null, $cache = true) {
 463         return Convert::raw2sql($this->RAW_val($field, $arguments, $cache));
 464     }
 465     
 466     /**
 467      * Return the value of a field in a JavaScript-save format.
 468      */
 469     public function JS_val($field, $arguments = null, $cache = true) {
 470         return Convert::raw2js($this->RAW_val($field, $arguments, $cache));
 471     }
 472     
 473     /**
 474      * Return the value of a field escaped suitable to be inserted into an XML node attribute.
 475      */
 476     public function ATT_val($field, $arguments = null, $cache = true) {
 477         return Convert::raw2att($this->RAW_val($field, $arguments, $cache));
 478     }
 479     
 480     /**#@-*/
 481     
 482     /**
 483      * Get an array of XML-escaped values by field name
 484      *
 485      * @param array $elements an array of field names
 486      * @return array
 487      */
 488     public function getXMLValues($fields) {
 489         $result = array();
 490         
 491         foreach($fields as $field) {
 492             $result[$field] = $this->XML_val($field);
 493         }
 494         
 495         return $result;
 496     }
 497     
 498     // ITERATOR SUPPORT ------------------------------------------------------------------------------------------------
 499     
 500     /**
 501      * Return a single-item iterator so you can iterate over the fields of a single record.
 502      *
 503      * This is useful so you can use a single record inside a <% control %> block in a template - and then use
 504      * to access individual fields on this object.
 505      *
 506      * @return ArrayIterator
 507      */
 508     public function getIterator() {
 509         return new ArrayIterator(array($this));
 510     }
 511     
 512     /** 
 513      * Set the current iterator properties - where we are on the iterator.
 514      *
 515      * @param int $pos position in iterator
 516      * @param int $totalItems total number of items
 517      */
 518     public function iteratorProperties($pos, $totalItems) {
 519         $this->iteratorPos        = $pos;
 520         $this->iteratorTotalItems = $totalItems;
 521     }
 522     
 523     /**
 524      * Returns true if this object is the first in a set.
 525      *
 526      * @return bool
 527      */
 528     public function First() {
 529         return $this->iteratorPos == 0;
 530     }
 531     
 532     /**
 533      * Returns true if this object is the last in a set.
 534      *
 535      * @return bool
 536      */
 537     public function Last() {
 538         return $this->iteratorPos == $this->iteratorTotalItems - 1;
 539     }
 540     
 541     /**
 542      * Returns 'first' or 'last' if this is the first or last object in the set.
 543      *
 544      * @return string|null
 545      */
 546     public function FirstLast() {
 547         if($this->First()) return 'first';
 548         if($this->Last())  return 'last';
 549     }
 550     
 551     /**
 552      * Return true if this object is between the first & last objects.
 553      *
 554      * @return bool
 555      */
 556     public function Middle() {
 557         return !$this->First() && !$this->Last();
 558     }
 559     
 560     /**
 561      * Return 'middle' if this object is between the first & last objects.
 562      *
 563      * @return string|null
 564      */
 565     public function MiddleString() {
 566         if($this->Middle()) return 'middle';
 567     }
 568     
 569     /**
 570      * Return true if this object is an even item in the set.
 571      *
 572      * @return bool
 573      */
 574     public function Even() {
 575         return (bool) ($this->iteratorPos % 2);
 576     }
 577     
 578     /**
 579      * Return true if this is an odd item in the set.
 580      *
 581      * @return bool
 582      */
 583     public function Odd() {
 584         return !$this->Even();
 585     }
 586     
 587     /**
 588      * Return 'even' or 'odd' if this object is in an even or odd position in the set respectively.
 589      *
 590      * @return string
 591      */
 592     public function EvenOdd() {
 593         return ($this->Even()) ? 'even' : 'odd';
 594     }
 595     
 596     /**
 597      * Return the numerical position of this object in the container set. The count starts at $startIndex.
 598      *
 599      * @param int $startIndex Number to start count from.
 600      * @return int
 601      */
 602     public function Pos($startIndex = 1) {
 603         return $this->iteratorPos + $startIndex;
 604     }
 605     
 606     /**
 607      * Return the total number of "sibling" items in the dataset.
 608      *
 609      * @return int
 610      */
 611     public function TotalItems() {
 612         return $this->iteratorTotalItems;
 613     }
 614 
 615     /**
 616      * Returns the modulus of the numerical position of the item in the data set.
 617      * The count starts from $startIndex, which defaults to 1.
 618      * @param int $Mod The number to perform Mod operation to.
 619      * @param int $startIndex Number to start count from.
 620      * @return int
 621      */
 622     public function Modulus($mod, $startIndex = 1) {
 623         return ($this->iteratorPos + $startIndex) % $mod;
 624     }
 625     
 626     public function MultipleOf($factor, $offset = 1) {
 627         return ($this->Modulus($factor, $offset) == 0);
 628     }
 629 
 630 
 631     // UTILITY METHODS -------------------------------------------------------------------------------------------------
 632     
 633     /**
 634      * When rendering some objects it is necessary to iterate over the object being rendered, to do this, you need
 635      * access to itself.
 636      *
 637      * @return ViewableData
 638      */
 639     public function Me() {
 640         return $this;
 641     }
 642     
 643     /**
 644      * Return the directory if the current active theme (relative to the site root).
 645      *
 646      * This method is useful for things such as accessing theme images from your template without hardcoding the theme
 647      * page - e.g. <img src="$ThemeDir/images/something.gif">.
 648      *
 649      * This method should only be used when a theme is currently active. However, it will fall over to the current
 650      * project directory.
 651      *
 652      * @param string $subtheme the subtheme path to get
 653      * @return string
 654      */
 655     public function ThemeDir($subtheme = false) {
 656         if($theme = SSViewer::current_theme()) {
 657             return THEMES_DIR . "/$theme" . ($subtheme ? "_$subtheme" : null);
 658         }
 659         
 660         return project();
 661     }
 662     
 663     /**
 664      * Возвращает имя текущей темы
 665      * 
 666      * Если текущая тема не выбрана то возвращается site
 667      *
 668      * @return string
 669      */
 670     public function ThemeName() {
 671         if($theme = SSViewer::current_theme()) {
 672             return $theme;
 673         }       
 674         return project();
 675     }
 676     
 677     /**
 678      * Get part of the current classes ancestry to be used as a CSS class.
 679      *
 680      * This method returns an escaped string of CSS classes representing the current classes ancestry until it hits a
 681      * stop point - e.g. "Page DataObject ViewableData".
 682      *
 683      * @param string $stopAtClass the class to stop at (default: ViewableData)
 684      * @return string
 685      * @uses ClassInfo
 686      */
 687     public function CSSClasses($stopAtClass = 'ViewableData') {
 688         $classes       = array();
 689         $classAncestry = array_reverse(ClassInfo::ancestry($this->class));
 690         $stopClasses   = ClassInfo::ancestry($stopAtClass);
 691         
 692         foreach($classAncestry as $class) {
 693             if(in_array($class, $stopClasses)) break;
 694             $classes[] = $class;
 695         }
 696         
 697         // optionally add template identifier
 698         if(isset($this->template) && !in_array($this->template, $classes)) {
 699             $classes[] = $this->template;
 700         }
 701         
 702         return Convert::raw2att(implode(' ', $classes));
 703     }
 704     
 705     /**
 706      * @see Member::currentUser()
 707      */
 708     public function CurrentMember() {
 709         return Member::currentUser();
 710     }
 711     
 712     /**
 713      * Return a CSRF-preventing ID to insert into a form.
 714      *
 715      * @return string
 716      */
 717     public function getSecurityID() {
 718         if(!$id = Session::get('SecurityID')) {
 719             $id = rand();
 720             Session::set('SecurityID', $id);
 721         }
 722         
 723         return $id;
 724     }
 725     
 726     /**
 727      * @see Permission::check()
 728      */
 729     public function HasPerm($code) {
 730         return Permission::check($code);
 731     }
 732     
 733     /**
 734      * @see Director::absoluteBaseURL()
 735      */
 736     public function BaseHref() {
 737         return Director::absoluteBaseURL();
 738     }
 739     
 740     /**
 741      * @see Director::is_ajax()
 742      */
 743     public function IsAjax() {
 744         return Director::is_ajax();
 745     }
 746     
 747     /**
 748      * @see i18n::get_locale()
 749      */
 750     public function i18nLocale() {
 751         return i18n::get_locale();
 752     }
 753     
 754     /**
 755      * Return debug information about this object that can be rendered into a template
 756      *
 757      * @return ViewableData_Debugger
 758      */
 759     public function Debug() {
 760         return new ViewableData_Debugger($this);
 761     }
 762     
 763     /**
 764      * @see Controller::curr()
 765      */
 766     public function CurrentPage() {
 767         return Controller::curr();
 768     }
 769     
 770     /**
 771      * @see SSViewer::topLevel()
 772      */
 773     public function Top() {
 774         return SSViewer::topLevel();
 775     }
 776 
 777     function ColumnCalc($col) {
 778         $pos = $this->Pos();
 779         $total = $this->TotalItems();
 780 
 781         $long = $total % $col;
 782         $minSize = floor($total / $col);
 783         if ($minSize == 0 ) $minSize = 1;
 784         $maxSize = ($long) ? $minSize + 1 : $minSize;
 785         $maxLongPos = $long * $maxSize;
 786 
 787         if (!$long) {
 788             $colNum = ceil($pos / $minSize);
 789             $colPos = $pos - $minSize * ($colNum - 1);
 790             return array(
 791                 'col' => $colNum,
 792                 'pos' => $colPos,
 793                 'break' => ($pos < $total && $colPos == $minSize) ? 1 : 0,
 794                 'pad' => 0,
 795             );
 796         }
 797         if ($pos <= $maxLongPos)    {
 798             $colNum = ceil($pos / $maxSize);
 799             $colPos = $pos - $maxSize * ($colNum - 1);
 800             return array(
 801                 'col' => $colNum,
 802                 'pos' => $colPos,
 803                 'break' => ($pos < $total && $colPos == $maxSize) ? 1 : 0,
 804                 'pad' => 0,
 805             );
 806         }
 807         $pos -= $maxLongPos;
 808         $total -= $maxLongPos;
 809         $colNum = ceil($pos / $minSize);
 810         $colPos = $pos - $minSize * ($colNum - 1);
 811         $colBreak = ($pos < $total && $colPos == $minSize) ? 1 : 0;
 812         return array(
 813             'col' => $colNum + $long,
 814             'pos' => $colPos,
 815             'break' => $colBreak,
 816             'pad' => ($colBreak || $pos == $total) ? 1 : 0,
 817         );
 818     }
 819 
 820     function ColumnNumber($col) {
 821         $info = $this->cachedCall('ColumnCalc', array($col));
 822         return $info['col'];
 823     }
 824     
 825     function ColumnPos($col) {
 826         $info = $this->cachedCall('ColumnCalc', array($col));
 827         return $info['pos'];
 828     }
 829 
 830     function ColumnBreak($col) {
 831         $info = $this->cachedCall('ColumnCalc', array($col));
 832         return $info['break'];
 833     }
 834 
 835     function ColumnPad($col) {
 836         $info = $this->cachedCall('ColumnCalc', array($col));
 837         return $info['pad'];
 838     }
 839 }
 840 
 841 /**
 842  * @package sapphire
 843  * @subpackage view
 844  */
 845 class ViewableData_Customised extends ViewableData {
 846     
 847     /**
 848      * @var ViewableData
 849      */
 850     protected $original, $customised;
 851     
 852     /**
 853      * Instantiate a new customised ViewableData object
 854      *
 855      * @param ViewableData $originalObject
 856      * @param ViewableData $customisedObject
 857      */
 858     public function __construct(ViewableData $originalObject, ViewableData $customisedObject) {
 859         $this->original   = $originalObject;
 860         $this->customised = $customisedObject;
 861         
 862         $this->original->setCustomisedObj($this);
 863         
 864         parent::__construct();
 865     }
 866     
 867     public function __call($method, $arguments) {
 868         if($this->customised->hasMethod($method)) {
 869             return call_user_func_array(array($this->customised, $method), $arguments);
 870         }
 871         
 872         return call_user_func_array(array($this->original, $method), $arguments);
 873     }
 874     
 875     public function __get($property) {
 876         if(isset($this->customised->$property)) {
 877             return $this->customised->$property;
 878         }
 879         
 880         return $this->original->$property;
 881     }
 882     
 883     public function __set($property, $value) {
 884         $this->customised->$property = $this->original->$property = $value;
 885     }
 886     
 887     public function hasMethod($method) {
 888         return $this->customised->hasMethod($method) || $this->original->hasMethod($method);
 889     }
 890     
 891     public function cachedCall($field, $arguments = null, $identifier = null) {
 892         if($this->customised->hasMethod($field) || $this->customised->hasField($field)) {
 893             $result = $this->customised->cachedCall($field, $arguments, $identifier);
 894         } else {
 895             $result = $this->original->cachedCall($field, $arguments, $identifier);
 896         }
 897         
 898         return $result;
 899     }
 900     
 901     public function obj($fieldName, $arguments = null, $forceReturnedObject = true, $cache = false, $cacheName = null) {
 902         // don't proxy Debug() method!
 903         if (strcasecmp($fieldName, 'debug') == 0) return parent::Debug();
 904 
 905         if($this->customised->hasField($fieldName) || $this->customised->hasMethod($fieldName)) {
 906             return $this->customised->obj($fieldName, $arguments, $forceReturnedObject, $cache, $cacheName);
 907         }
 908         
 909         return $this->original->obj($fieldName, $arguments, $forceReturnedObject, $cache, $cacheName);
 910     }
 911 
 912     function getAllFields() {
 913         $res = array();
 914         if ($this->original->hasMethod('getAllFields'))
 915             $res = array_merge($res, $this->original->getAllFields());
 916 
 917         if ($this->customised->hasMethod('getAllFields'))
 918             $res = array_merge($res, $this->customised->getAllFields());
 919         return array_unique($res);
 920     }
 921 
 922     function allMethodNames() {
 923         return array_unique(array_merge($this->original->allMethodNames(), $this->customised->allMethodNames()));
 924     }
 925 
 926     function getOriginal() {
 927         return $this->original;
 928     }
 929 }
 930 
 931 /**
 932  * Allows you to render debug information about a {@link ViewableData} object into a template.
 933  *
 934  * @package sapphire
 935  * @subpackage view
 936  */
 937 class ViewableData_Debugger extends ViewableData {
 938     
 939     protected static $hidden_methods = array('getField', 'getCustomClass', 'getExtensionInstance', 'getExtensionInstances', 'getIterator', 'getXMLValues');
 940 
 941     /**
 942      * @var ViewableData
 943      */
 944     protected $object;
 945     
 946     /**
 947      * @param ViewableData $object
 948      */
 949     public function __construct(ViewableData $object) {
 950         $this->object = $object;
 951         parent::__construct();
 952     }
 953     
 954     /**
 955      * Return debugging information, as XHTML. If a field name is passed, it will show debugging information on that
 956      * field, otherwise it will show information on all methods and fields.
 957      *
 958      * @param string $field the field name
 959      * @return string
 960      */
 961     public function forTemplate($field = null) {
 962         // debugging info for a specific field
 963         if($field) return "<b>Debugging Information for {$this->class}->{$field}</b><br/>" .
 964             ($this->object->hasMethod($field)? "Has method '$field'<br/>" : null)             .
 965             ($this->object->hasField($field) ? "Has field '$field'<br/>"  : null)             ;
 966         
 967         // debugging information for the entire class
 968         if ($this->object->class == 'ViewableData_Customised') {
 969             $object = $this->object->getOriginal();
 970             $reflector = new ReflectionObject($object);
 971             $class =  $object->class . ' (customized)';
 972         }
 973         else {
 974             $reflector = new ReflectionObject($this->object);
 975             $class =  $this->object->class;
 976         }
 977         $debug     = "<b>Debugging Information: all methods available in '{$class}'</b><br/><ul>";
 978 
 979         $vars = array();
 980         $methods = $this->object->allMethodNames();
 981         sort($methods);
 982         foreach($methods as $method) {
 983             // check that the method is public
 984             if ($method[0] == '_') continue;
 985 
 986             if ($reflector->hasMethod($method) && $method = $reflector->getMethod($method)) {
 987                 if (!$method->isPublic()) continue;
 988 
 989                 $methodName = $method->getName();
 990                 if (array_search($methodName, self::$hidden_methods) !== false) continue;
 991                 // only MethodForTemplate() not (internalMethod() or Method_xxx())
 992                 if ($methodName[0] === strtoupper($methodName[0]) && strpos($methodName, '_') === false) {
 993                     $debug .= "<li>\${$method->getName()}";
 994                     
 995                     if(count($method->getParameters())) {
 996                         $debug .= ' <small>(' . implode(', ', $method->getParameters()) . ')</small>';
 997                     }
 998                     
 999                     $debug .= '</li>';
1000                 }
1001                 // getVar() not (get_xxx() or getxxx()) -> $vars
1002                 elseif (strlen($methodName) > 3 && substr($methodName, 0 , 3) == 'get' && $methodName[3] != '_' && $methodName[3] === strtoupper($methodName[3])) {
1003                     $vars[] = substr($methodName, 3);
1004                 }
1005             }
1006         }
1007         
1008         $debug .= '</ul>';
1009         
1010         if($this->object->hasMethod('getAllFields')) 
1011             $vars = array_unique(array_merge($vars, array_keys($this->object->getAllFields())));
1012         
1013         if (count($vars)) {
1014             sort($vars);
1015             $debug .= "<b>Debugging Information: all fields available in '{$class}'</b><br/><ul>";
1016             
1017             foreach($vars as $field) {
1018                 $debug .= "<li>\$$field</li>";
1019             }
1020             
1021             $debug .= "</ul>";
1022         }
1023         
1024         // check for an extra attached data
1025         if($this->object->hasMethod('data') && $this->object->data() != $this->object) {
1026             $debug .= Object::create('ViewableData_Debugger', $this->object->data())->forTemplate();
1027         }
1028         
1029         return $debug;
1030     }
1031 
1032 }
1033 
[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -
Webylon 3.1 API Docs API documentation generated by ApiGen 2.8.0