1 <?php
2 3 4 5 6 7 8 9 10 11
12 class ViewableData extends Object implements IteratorAggregate {
13
14 15 16 17 18 19 20 21 22 23 24
25 public static $casting = array (
26 'BaseHref' => 'Varchar',
27 'CSSClasses' => 'Varchar'
28 );
29
30 31 32 33 34 35
36 public static $default_cast = 'HTMLVarchar';
37
38 39 40
41 private static $casting_cache = array();
42
43
44
45 46 47
48 protected $iteratorPos, $iteratorTotalItems;
49
50 51 52 53 54
55 protected $failover;
56
57 58 59
60 protected $customisedObject;
61
62 63 64
65 private $objCache = array();
66
67
68
69 70 71 72 73 74 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 82 83 84 85 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
92
93 94 95 96 97 98
99 public function __isset($property) {
100 return $this->hasField($property) || ($this->failover && $this->failover->hasField($property));
101 }
102
103 104 105 106 107 108 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 122 123 124 125 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 137 138 139 140
141 public function hasField($field) {
142 return property_exists($this, $field);
143 }
144
145 146 147 148 149 150
151 public function getField($field) {
152 return $this->$field;
153 }
154
155 156 157 158 159 160
161 public function setField($field, $value) {
162 $this->$field = $value;
163 }
164
165
166
167 168 169 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 194 195 196 197 198 199 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 217
218 public function setCustomisedObj(ViewableData $object) {
219 $this->customisedObject = $object;
220 }
221
222
223
224 225 226 227 228 229 230 231 232 233 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 242 243 244 245 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 260 261 262 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 275 276 277 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 289 290 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
318
319 320 321 322 323 324 325 326 327 328 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 352 353 354 355 356 357 358 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 404 405 406 407 408 409
410 public function cachedCall($field, $arguments = null, $identifier = null) {
411 return $this->obj($field, $arguments, false, true, $identifier);
412 }
413
414 415 416 417 418 419 420 421 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 438 439 440 441
442
443 444 445 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 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 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 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 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 484 485 486 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
499
500 501 502 503 504 505 506 507
508 public function getIterator() {
509 return new ArrayIterator(array($this));
510 }
511
512 513 514 515 516 517
518 public function iteratorProperties($pos, $totalItems) {
519 $this->iteratorPos = $pos;
520 $this->iteratorTotalItems = $totalItems;
521 }
522
523 524 525 526 527
528 public function First() {
529 return $this->iteratorPos == 0;
530 }
531
532 533 534 535 536
537 public function Last() {
538 return $this->iteratorPos == $this->iteratorTotalItems - 1;
539 }
540
541 542 543 544 545
546 public function FirstLast() {
547 if($this->First()) return 'first';
548 if($this->Last()) return 'last';
549 }
550
551 552 553 554 555
556 public function Middle() {
557 return !$this->First() && !$this->Last();
558 }
559
560 561 562 563 564
565 public function MiddleString() {
566 if($this->Middle()) return 'middle';
567 }
568
569 570 571 572 573
574 public function Even() {
575 return (bool) ($this->iteratorPos % 2);
576 }
577
578 579 580 581 582
583 public function Odd() {
584 return !$this->Even();
585 }
586
587 588 589 590 591
592 public function EvenOdd() {
593 return ($this->Even()) ? 'even' : 'odd';
594 }
595
596 597 598 599 600 601
602 public function Pos($startIndex = 1) {
603 return $this->iteratorPos + $startIndex;
604 }
605
606 607 608 609 610
611 public function TotalItems() {
612 return $this->iteratorTotalItems;
613 }
614
615 616 617 618 619 620 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
632
633 634 635 636 637 638
639 public function Me() {
640 return $this;
641 }
642
643 644 645 646 647 648 649 650 651 652 653 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 667 668 669
670 public function ThemeName() {
671 if($theme = SSViewer::current_theme()) {
672 return $theme;
673 }
674 return project();
675 }
676
677 678 679 680 681 682 683 684 685 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
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 707
708 public function CurrentMember() {
709 return Member::currentUser();
710 }
711
712 713 714 715 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 728
729 public function HasPerm($code) {
730 return Permission::check($code);
731 }
732
733 734 735
736 public function BaseHref() {
737 return Director::absoluteBaseURL();
738 }
739
740 741 742
743 public function IsAjax() {
744 return Director::is_ajax();
745 }
746
747 748 749
750 public function i18nLocale() {
751 return i18n::get_locale();
752 }
753
754 755 756 757 758
759 public function Debug() {
760 return new ViewableData_Debugger($this);
761 }
762
763 764 765
766 public function CurrentPage() {
767 return Controller::curr();
768 }
769
770 771 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 843 844
845 class ViewableData_Customised extends ViewableData {
846
847 848 849
850 protected $original, $customised;
851
852 853 854 855 856 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
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 933 934 935 936
937 class ViewableData_Debugger extends ViewableData {
938
939 protected static $hidden_methods = array('getField', 'getCustomClass', 'getExtensionInstance', 'getExtensionInstances', 'getIterator', 'getXMLValues');
940
941 942 943
944 protected $object;
945
946 947 948
949 public function __construct(ViewableData $object) {
950 $this->object = $object;
951 parent::__construct();
952 }
953
954 955 956 957 958 959 960
961 public function forTemplate($field = null) {
962
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
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
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
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
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
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.
-