Complex classes like SSViewer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use SSViewer, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 687 | class SSViewer implements Flushable { |
||
| 688 | |||
| 689 | /** |
||
| 690 | * Identifier for the default theme |
||
| 691 | */ |
||
| 692 | const DEFAULT_THEME = '$default'; |
||
| 693 | |||
| 694 | /** |
||
| 695 | * @config |
||
| 696 | * @var boolean $source_file_comments |
||
| 697 | */ |
||
| 698 | private static $source_file_comments = false; |
||
| 699 | |||
| 700 | /** |
||
| 701 | * @ignore |
||
| 702 | */ |
||
| 703 | private static $template_cache_flushed = false; |
||
| 704 | |||
| 705 | /** |
||
| 706 | * @ignore |
||
| 707 | */ |
||
| 708 | private static $cacheblock_cache_flushed = false; |
||
| 709 | |||
| 710 | /** |
||
| 711 | * Set whether HTML comments indicating the source .SS file used to render this page should be |
||
| 712 | * included in the output. This is enabled by default |
||
| 713 | * |
||
| 714 | * @deprecated 4.0 Use the "SSViewer.source_file_comments" config setting instead |
||
| 715 | * @param boolean $val |
||
| 716 | */ |
||
| 717 | public static function set_source_file_comments($val) { |
||
| 718 | Deprecation::notice('4.0', 'Use the "SSViewer.source_file_comments" config setting instead'); |
||
| 719 | Config::inst()->update('SSViewer', 'source_file_comments', $val); |
||
| 720 | } |
||
| 721 | |||
| 722 | /** |
||
| 723 | * @deprecated 4.0 Use the "SSViewer.source_file_comments" config setting instead |
||
| 724 | * @return boolean |
||
| 725 | */ |
||
| 726 | public static function get_source_file_comments() { |
||
| 727 | Deprecation::notice('4.0', 'Use the "SSViewer.source_file_comments" config setting instead'); |
||
| 728 | return Config::inst()->get('SSViewer', 'source_file_comments'); |
||
| 729 | } |
||
| 730 | |||
| 731 | /** |
||
| 732 | * @var array $templates List of templates to select from |
||
| 733 | */ |
||
| 734 | private $templates = null; |
||
| 735 | |||
| 736 | /** |
||
| 737 | * @var string $chosen Absolute path to chosen template file |
||
| 738 | */ |
||
| 739 | private $chosen = null; |
||
| 740 | |||
| 741 | /** |
||
| 742 | * @var array Templates to use when looking up 'Layout' or 'Content' |
||
| 743 | */ |
||
| 744 | private $subTemplates = null; |
||
| 745 | |||
| 746 | /** |
||
| 747 | * @var boolean |
||
| 748 | */ |
||
| 749 | protected $rewriteHashlinks = true; |
||
| 750 | |||
| 751 | /** |
||
| 752 | * @config |
||
| 753 | * @var string A list (highest priority first) of themes to use |
||
| 754 | * Only used when {@link $theme_enabled} is set to TRUE. |
||
| 755 | */ |
||
| 756 | private static $themes = []; |
||
| 757 | |||
| 758 | /** |
||
| 759 | * @deprecated 4.0..5.0 |
||
| 760 | * @config |
||
| 761 | * @var string The used "theme", which usually consists of templates, images and stylesheets. |
||
| 762 | * Only used when {@link $theme_enabled} is set to TRUE, and $themes is empty |
||
| 763 | */ |
||
| 764 | private static $theme = null; |
||
| 765 | |||
| 766 | /** |
||
| 767 | * @config |
||
| 768 | * @var boolean Use the theme. Set to FALSE in order to disable themes, |
||
| 769 | * which can be useful for scenarios where theme overrides are temporarily undesired, |
||
| 770 | * such as an administrative interface separate from the website theme. |
||
| 771 | * It retains the theme settings to be re-enabled, for example when a website content |
||
| 772 | * needs to be rendered from within this administrative interface. |
||
| 773 | */ |
||
| 774 | private static $theme_enabled = true; |
||
| 775 | |||
| 776 | /** |
||
| 777 | * @var boolean |
||
| 778 | */ |
||
| 779 | protected $includeRequirements = true; |
||
| 780 | |||
| 781 | /** |
||
| 782 | * @var TemplateParser |
||
| 783 | */ |
||
| 784 | protected $parser; |
||
| 785 | |||
| 786 | /* |
||
| 787 | * Default prepended cache key for partial caching |
||
| 788 | * |
||
| 789 | * @var string |
||
| 790 | * @config |
||
| 791 | */ |
||
| 792 | private static $global_key = '$CurrentReadingMode, $CurrentUser.ID'; |
||
| 793 | |||
| 794 | /** |
||
| 795 | * Triggered early in the request when someone requests a flush. |
||
| 796 | */ |
||
| 797 | public static function flush() { |
||
| 798 | self::flush_template_cache(true); |
||
| 799 | self::flush_cacheblock_cache(true); |
||
| 800 | } |
||
| 801 | |||
| 802 | /** |
||
| 803 | * Create a template from a string instead of a .ss file |
||
| 804 | * |
||
| 805 | * @param string $content The template content |
||
| 806 | * @param bool|void $cacheTemplate Whether or not to cache the template from string |
||
| 807 | * @return SSViewer |
||
| 808 | */ |
||
| 809 | public static function fromString($content, $cacheTemplate = null) { |
||
| 810 | $viewer = new SSViewer_FromString($content); |
||
| 811 | if ($cacheTemplate !== null) { |
||
| 812 | $viewer->setCacheTemplate($cacheTemplate); |
||
| 813 | } |
||
| 814 | return $viewer; |
||
| 815 | } |
||
| 816 | |||
| 817 | /** |
||
| 818 | * Assign the list of active themes to apply. |
||
| 819 | * If default themes should be included add $default as the last entry. |
||
| 820 | * |
||
| 821 | * @param array $themes |
||
| 822 | */ |
||
| 823 | public static function set_themes($themes = []) { |
||
| 824 | Config::inst()->remove('SSViewer', 'themes'); |
||
| 825 | Config::inst()->update('SSViewer', 'themes', $themes); |
||
| 826 | } |
||
| 827 | |||
| 828 | public static function add_themes($themes = []) { |
||
| 829 | Config::inst()->update('SSViewer', 'themes', $themes); |
||
| 830 | } |
||
| 831 | |||
| 832 | public static function get_themes() { |
||
| 833 | $default = [self::DEFAULT_THEME]; |
||
| 834 | |||
| 835 | if (!Config::inst()->get('SSViewer', 'theme_enabled')) { |
||
| 836 | return $default; |
||
| 837 | } |
||
| 838 | |||
| 839 | // Explicit list is assigned |
||
| 840 | if ($list = Config::inst()->get('SSViewer', 'themes')) { |
||
| 841 | return $list; |
||
| 842 | } |
||
| 843 | |||
| 844 | // Support legacy behaviour |
||
| 845 | if ($theme = Config::inst()->get('SSViewer', 'theme')) { |
||
| 846 | return [$theme, self::DEFAULT_THEME]; |
||
| 847 | } |
||
| 848 | |||
| 849 | return $default; |
||
| 850 | } |
||
| 851 | |||
| 852 | /** |
||
| 853 | * @deprecated 4.0 Use the "SSViewer.theme" config setting instead |
||
| 854 | * @param string $theme The "base theme" name (without underscores). |
||
| 855 | */ |
||
| 856 | public static function set_theme($theme) { |
||
| 857 | Deprecation::notice('4.0', 'Use the "SSViewer#set_themes" instead'); |
||
| 858 | self::set_themes([$theme, self::DEFAULT_THEME]); |
||
| 859 | } |
||
| 860 | |||
| 861 | /** |
||
| 862 | * Traverses the given the given class context looking for candidate template names |
||
| 863 | * which match each item in the class hierarchy. The resulting list of template candidates |
||
| 864 | * may or may not exist, but you can invoke {@see SSViewer::chooseTemplate} on any list |
||
| 865 | * to determine the best candidate based on the current themes. |
||
| 866 | * |
||
| 867 | * @param string|object $classOrObject Valid class name, or object |
||
| 868 | * @param string $suffix |
||
| 869 | * @param string $baseClass Class to halt ancestry search at |
||
| 870 | * @return array |
||
| 871 | */ |
||
| 872 | public static function get_templates_by_class($classOrObject, $suffix = '', $baseClass = null) { |
||
| 873 | // Figure out the class name from the supplied context. |
||
| 874 | if (!is_object($classOrObject) && !( |
||
| 875 | is_string($classOrObject) && class_exists($classOrObject) |
||
| 876 | )) { |
||
| 877 | throw new InvalidArgumentException( |
||
| 878 | 'SSViewer::get_templates_by_class() expects a valid class name as its first parameter.' |
||
| 879 | ); |
||
| 880 | } |
||
| 881 | $templates = array(); |
||
| 882 | $classes = array_reverse(ClassInfo::ancestry($classOrObject)); |
||
| 883 | foreach($classes as $class) { |
||
| 884 | $template = $class . $suffix; |
||
| 885 | $templates[] = $template; |
||
| 886 | $templates[] = ['type' => 'Includes', $template]; |
||
| 887 | |||
| 888 | // If the class is "Page_Controller", look for Page.ss |
||
| 889 | if (stripos($class, '_controller') !== false) { |
||
| 890 | $templates[] = str_ireplace('_controller', '', $class) . $suffix; |
||
| 891 | } |
||
| 892 | |||
| 893 | if($baseClass && $class == $baseClass) { |
||
| 894 | break; |
||
| 895 | } |
||
| 896 | } |
||
| 897 | return $templates; |
||
| 898 | } |
||
| 899 | |||
| 900 | /** |
||
| 901 | * @param string|array $templates If passed as a string with .ss extension, used as the "main" template. |
||
| 902 | * If passed as an array, it can be used for template inheritance (first found template "wins"). |
||
| 903 | * Usually the array values are PHP class names, which directly correlate to template names. |
||
| 904 | * <code> |
||
| 905 | * array('MySpecificPage', 'MyPage', 'Page') |
||
| 906 | * </code> |
||
| 907 | * @param TemplateParser $parser |
||
| 908 | */ |
||
| 909 | public function __construct($templates, TemplateParser $parser = null) { |
||
| 910 | if ($parser) { |
||
| 911 | $this->setParser($parser); |
||
| 912 | } |
||
| 913 | |||
| 914 | $this->setTemplate($templates); |
||
| 915 | |||
| 916 | if(!$this->chosen) { |
||
| 917 | $message = 'None of the following templates could be found: '; |
||
| 918 | $message .= print_r($templates, true); |
||
| 919 | |||
| 920 | $themes = self::get_themes(); |
||
| 921 | if(!$themes) { |
||
| 922 | $message .= ' (no theme in use)'; |
||
| 923 | } else { |
||
| 924 | $message .= ' in themes "' . print_r($themes, true) . '"'; |
||
| 925 | } |
||
| 926 | |||
| 927 | user_error($message, E_USER_WARNING); |
||
| 928 | } |
||
| 929 | } |
||
| 930 | |||
| 931 | public function setTemplate($templates) { |
||
| 932 | $this->templates = $templates; |
||
| 933 | $this->chosen = $this->chooseTemplate($templates); |
||
| 934 | $this->subTemplates = []; |
||
| 935 | } |
||
| 936 | |||
| 937 | /** |
||
| 938 | * Find the template to use for a given list |
||
| 939 | * |
||
| 940 | * @param array|string $templates |
||
| 941 | * @return string |
||
| 942 | */ |
||
| 943 | public static function chooseTemplate($templates) { |
||
| 944 | return ThemeResourceLoader::instance()->findTemplate($templates, self::get_themes()); |
||
| 945 | } |
||
| 946 | |||
| 947 | /** |
||
| 948 | * Set the template parser that will be used in template generation |
||
| 949 | * @param \TemplateParser $parser |
||
| 950 | */ |
||
| 951 | public function setParser(TemplateParser $parser) |
||
| 952 | { |
||
| 953 | $this->parser = $parser; |
||
| 954 | } |
||
| 955 | |||
| 956 | /** |
||
| 957 | * Returns the parser that is set for template generation |
||
| 958 | * @return \TemplateParser |
||
| 959 | */ |
||
| 960 | public function getParser() |
||
| 961 | { |
||
| 962 | if (!$this->parser) { |
||
| 963 | $this->setParser(Injector::inst()->get('SSTemplateParser')); |
||
| 964 | } |
||
| 965 | return $this->parser; |
||
| 966 | } |
||
| 967 | |||
| 968 | /** |
||
| 969 | * Returns true if at least one of the listed templates exists. |
||
| 970 | * |
||
| 971 | * @param array $templates |
||
| 972 | * |
||
| 973 | * @return boolean |
||
| 974 | */ |
||
| 975 | public static function hasTemplate($templates) { |
||
| 976 | return (bool)ThemeResourceLoader::instance()->findTemplate($templates, self::get_themes()); |
||
| 977 | } |
||
| 978 | |||
| 979 | /** |
||
| 980 | * Set a global rendering option. |
||
| 981 | * |
||
| 982 | * The following options are available: |
||
| 983 | * - rewriteHashlinks: If true (the default), <a href="#..."> will be rewritten to contain the |
||
| 984 | * current URL. This lets it play nicely with our <base> tag. |
||
| 985 | * - If rewriteHashlinks = 'php' then, a piece of PHP script will be inserted before the hash |
||
| 986 | * links: "<?php echo $_SERVER['REQUEST_URI']; ?>". This is useful if you're generating a |
||
| 987 | * page that will be saved to a .php file and may be accessed from different URLs. |
||
| 988 | * |
||
| 989 | * @deprecated 4.0 Use the "SSViewer.rewrite_hash_links" config setting instead |
||
| 990 | * @param string $optionName |
||
| 991 | * @param mixed $optionVal |
||
| 992 | */ |
||
| 993 | public static function setOption($optionName, $optionVal) { |
||
| 994 | if($optionName == 'rewriteHashlinks') { |
||
| 995 | Deprecation::notice('4.0', 'Use the "SSViewer.rewrite_hash_links" config setting instead'); |
||
| 996 | Config::inst()->update('SSViewer', 'rewrite_hash_links', $optionVal); |
||
| 997 | } else { |
||
| 998 | Deprecation::notice('4.0', 'Use the "SSViewer.' . $optionName . '" config setting instead'); |
||
| 999 | Config::inst()->update('SSViewer', $optionName, $optionVal); |
||
| 1000 | } |
||
| 1001 | } |
||
| 1002 | |||
| 1003 | /** |
||
| 1004 | * @deprecated 4.0 Use the "SSViewer.rewrite_hash_links" config setting instead |
||
| 1005 | * @param string |
||
| 1006 | * @return mixed |
||
| 1007 | */ |
||
| 1008 | public static function getOption($optionName) { |
||
| 1009 | if($optionName == 'rewriteHashlinks') { |
||
| 1010 | Deprecation::notice('4.0', 'Use the "SSViewer.rewrite_hash_links" config setting instead'); |
||
| 1011 | return Config::inst()->get('SSViewer', 'rewrite_hash_links'); |
||
| 1012 | } else { |
||
| 1013 | Deprecation::notice('4.0', 'Use the "SSViewer.' . $optionName . '" config setting instead'); |
||
| 1014 | return Config::inst()->get('SSViewer', $optionName); |
||
| 1015 | } |
||
| 1016 | } |
||
| 1017 | |||
| 1018 | /** |
||
| 1019 | * @config |
||
| 1020 | * @var boolean |
||
| 1021 | */ |
||
| 1022 | private static $rewrite_hash_links = true; |
||
| 1023 | |||
| 1024 | protected static $topLevel = array(); |
||
| 1025 | |||
| 1026 | public static function topLevel() { |
||
| 1027 | if(SSViewer::$topLevel) { |
||
| 1028 | return SSViewer::$topLevel[sizeof(SSViewer::$topLevel)-1]; |
||
| 1029 | } |
||
| 1030 | } |
||
| 1031 | |||
| 1032 | /** |
||
| 1033 | * Call this to disable rewriting of <a href="#xxx"> links. This is useful in Ajax applications. |
||
| 1034 | * It returns the SSViewer objects, so that you can call new SSViewer("X")->dontRewriteHashlinks()->process(); |
||
| 1035 | */ |
||
| 1036 | public function dontRewriteHashlinks() { |
||
| 1037 | $this->rewriteHashlinks = false; |
||
| 1038 | Config::inst()->update('SSViewer', 'rewrite_hash_links', false); |
||
| 1039 | return $this; |
||
| 1040 | } |
||
| 1041 | |||
| 1042 | public function exists() { |
||
| 1043 | return $this->chosen; |
||
| 1044 | } |
||
| 1045 | |||
| 1046 | /** |
||
| 1047 | * @param string $identifier A template name without '.ss' extension or path |
||
| 1048 | * @param string $type The template type, either "main", "Includes" or "Layout" |
||
| 1049 | * |
||
| 1050 | * @return string Full system path to a template file |
||
| 1051 | */ |
||
| 1052 | public static function getTemplateFileByType($identifier, $type = null) { |
||
| 1053 | return ThemeResourceLoader::instance()->findTemplate(['type' => $type, $identifier], self::get_themes()); |
||
| 1054 | } |
||
| 1055 | |||
| 1056 | /** |
||
| 1057 | * Clears all parsed template files in the cache folder. |
||
| 1058 | * |
||
| 1059 | * Can only be called once per request (there may be multiple SSViewer instances). |
||
| 1060 | * |
||
| 1061 | * @param bool $force Set this to true to force a re-flush. If left to false, flushing |
||
| 1062 | * may only be performed once a request. |
||
| 1063 | */ |
||
| 1064 | public static function flush_template_cache($force = false) { |
||
| 1065 | if (!self::$template_cache_flushed || $force) { |
||
| 1066 | $dir = dir(TEMP_FOLDER); |
||
| 1067 | while (false !== ($file = $dir->read())) { |
||
| 1068 | if (strstr($file, '.cache')) unlink(TEMP_FOLDER . '/' . $file); |
||
| 1069 | } |
||
| 1070 | self::$template_cache_flushed = true; |
||
| 1071 | } |
||
| 1072 | } |
||
| 1073 | |||
| 1074 | /** |
||
| 1075 | * Clears all partial cache blocks. |
||
| 1076 | * |
||
| 1077 | * Can only be called once per request (there may be multiple SSViewer instances). |
||
| 1078 | * |
||
| 1079 | * @param bool $force Set this to true to force a re-flush. If left to false, flushing |
||
| 1080 | * may only be performed once a request. |
||
| 1081 | */ |
||
| 1082 | public static function flush_cacheblock_cache($force = false) { |
||
| 1083 | if (!self::$cacheblock_cache_flushed || $force) { |
||
| 1084 | $cache = SS_Cache::factory('cacheblock'); |
||
| 1085 | $backend = $cache->getBackend(); |
||
| 1086 | |||
| 1087 | if( |
||
| 1088 | $backend instanceof Zend_Cache_Backend_ExtendedInterface |
||
| 1089 | && ($capabilities = $backend->getCapabilities()) |
||
| 1090 | && $capabilities['tags'] |
||
| 1091 | ) { |
||
| 1092 | $cache->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG, $cache->getTags()); |
||
| 1093 | } else { |
||
| 1094 | $cache->clean(Zend_Cache::CLEANING_MODE_ALL); |
||
| 1095 | } |
||
| 1096 | |||
| 1097 | |||
| 1098 | self::$cacheblock_cache_flushed = true; |
||
| 1099 | } |
||
| 1100 | } |
||
| 1101 | |||
| 1102 | /** |
||
| 1103 | * @var Zend_Cache_Core |
||
| 1104 | */ |
||
| 1105 | protected $partialCacheStore = null; |
||
| 1106 | |||
| 1107 | /** |
||
| 1108 | * Set the cache object to use when storing / retrieving partial cache blocks. |
||
| 1109 | * |
||
| 1110 | * @param Zend_Cache_Core $cache |
||
| 1111 | */ |
||
| 1112 | public function setPartialCacheStore($cache) { |
||
| 1113 | $this->partialCacheStore = $cache; |
||
| 1114 | } |
||
| 1115 | |||
| 1116 | /** |
||
| 1117 | * Get the cache object to use when storing / retrieving partial cache blocks. |
||
| 1118 | * |
||
| 1119 | * @return Zend_Cache_Core |
||
| 1120 | */ |
||
| 1121 | public function getPartialCacheStore() { |
||
| 1122 | return $this->partialCacheStore ? $this->partialCacheStore : SS_Cache::factory('cacheblock'); |
||
| 1123 | } |
||
| 1124 | |||
| 1125 | /** |
||
| 1126 | * Flag whether to include the requirements in this response. |
||
| 1127 | * |
||
| 1128 | * @param boolean |
||
| 1129 | */ |
||
| 1130 | public function includeRequirements($incl = true) { |
||
| 1131 | $this->includeRequirements = $incl; |
||
| 1132 | } |
||
| 1133 | |||
| 1134 | /** |
||
| 1135 | * An internal utility function to set up variables in preparation for including a compiled |
||
| 1136 | * template, then do the include |
||
| 1137 | * |
||
| 1138 | * Effectively this is the common code that both SSViewer#process and SSViewer_FromString#process call |
||
| 1139 | * |
||
| 1140 | * @param string $cacheFile - The path to the file that contains the template compiled to PHP |
||
| 1141 | * @param Object $item - The item to use as the root scope for the template |
||
| 1142 | * @param array|null $overlay - Any variables to layer on top of the scope |
||
| 1143 | * @param array|null $underlay - Any variables to layer underneath the scope |
||
| 1144 | * @param Object $inheritedScope - the current scope of a parent template including a sub-template |
||
| 1145 | * |
||
| 1146 | * @return string - The result of executing the template |
||
| 1147 | */ |
||
| 1148 | protected function includeGeneratedTemplate($cacheFile, $item, $overlay, $underlay, $inheritedScope = null) { |
||
| 1149 | if(isset($_GET['showtemplate']) && $_GET['showtemplate'] && Permission::check('ADMIN')) { |
||
| 1150 | $lines = file($cacheFile); |
||
| 1151 | echo "<h2>Template: $cacheFile</h2>"; |
||
| 1152 | echo "<pre>"; |
||
| 1153 | foreach($lines as $num => $line) { |
||
| 1154 | echo str_pad($num+1,5) . htmlentities($line, ENT_COMPAT, 'UTF-8'); |
||
| 1155 | } |
||
| 1156 | echo "</pre>"; |
||
| 1157 | } |
||
| 1158 | |||
| 1159 | $cache = $this->getPartialCacheStore(); |
||
| 1160 | $scope = new SSViewer_DataPresenter($item, $overlay, $underlay, $inheritedScope); |
||
| 1161 | $val = ''; |
||
| 1162 | |||
| 1163 | include($cacheFile); |
||
| 1164 | |||
| 1165 | return $val; |
||
| 1166 | } |
||
| 1167 | |||
| 1168 | /** |
||
| 1169 | * The process() method handles the "meat" of the template processing. |
||
| 1170 | * |
||
| 1171 | * It takes care of caching the output (via {@link SS_Cache}), as well as |
||
| 1172 | * replacing the special "$Content" and "$Layout" placeholders with their |
||
| 1173 | * respective subtemplates. |
||
| 1174 | * |
||
| 1175 | * The method injects extra HTML in the header via {@link Requirements::includeInHTML()}. |
||
| 1176 | * |
||
| 1177 | * Note: You can call this method indirectly by {@link ViewableData->renderWith()}. |
||
| 1178 | * |
||
| 1179 | * @param ViewableData $item |
||
| 1180 | * @param array|null $arguments - arguments to an included template |
||
| 1181 | * @param Object $inheritedScope - the current scope of a parent template including a sub-template |
||
| 1182 | * |
||
| 1183 | * @return DBHTMLText Parsed template output. |
||
| 1184 | */ |
||
| 1185 | public function process($item, $arguments = null, $inheritedScope = null) { |
||
| 1186 | SSViewer::$topLevel[] = $item; |
||
| 1187 | |||
| 1188 | $template = $this->chosen; |
||
| 1189 | |||
| 1190 | $cacheFile = TEMP_FOLDER . "/.cache" |
||
| 1191 | . str_replace(array('\\','/',':'), '.', Director::makeRelative(realpath($template))); |
||
| 1192 | $lastEdited = filemtime($template); |
||
| 1193 | |||
| 1194 | if(!file_exists($cacheFile) || filemtime($cacheFile) < $lastEdited) { |
||
| 1195 | $content = file_get_contents($template); |
||
| 1196 | $content = $this->parseTemplateContent($content, $template); |
||
| 1197 | |||
| 1198 | $fh = fopen($cacheFile,'w'); |
||
| 1199 | fwrite($fh, $content); |
||
| 1200 | fclose($fh); |
||
| 1201 | } |
||
| 1202 | |||
| 1203 | $underlay = array('I18NNamespace' => basename($template)); |
||
| 1204 | |||
| 1205 | // Makes the rendered sub-templates available on the parent item, |
||
| 1206 | // through $Content and $Layout placeholders. |
||
| 1207 | foreach(array('Content', 'Layout') as $subtemplate) { |
||
| 1208 | $sub = null; |
||
| 1209 | if(isset($this->subTemplates[$subtemplate])) { |
||
| 1210 | $sub = $this->subTemplates[$subtemplate]; |
||
| 1211 | } |
||
| 1212 | elseif(!is_array($this->templates)) { |
||
| 1213 | $sub = ['type' => $subtemplate, $this->templates]; |
||
| 1214 | } |
||
| 1215 | elseif(!array_key_exists('type', $this->templates) || !$this->templates['type']) { |
||
| 1216 | $sub = array_merge($this->templates, ['type' => $subtemplate]); |
||
| 1217 | } |
||
| 1218 | |||
| 1219 | if ($sub) { |
||
| 1220 | $subtemplateViewer = clone $this; |
||
| 1221 | // Disable requirements - this will be handled by the parent template |
||
| 1222 | $subtemplateViewer->includeRequirements(false); |
||
| 1223 | // Select the right template |
||
| 1224 | $subtemplateViewer->setTemplate($sub); |
||
| 1225 | |||
| 1226 | if ($subtemplateViewer->exists()) { |
||
| 1227 | $underlay[$subtemplate] = $subtemplateViewer->process($item, $arguments); |
||
| 1228 | } |
||
| 1229 | } |
||
| 1230 | } |
||
| 1231 | |||
| 1232 | $output = $this->includeGeneratedTemplate($cacheFile, $item, $arguments, $underlay, $inheritedScope); |
||
| 1233 | |||
| 1234 | if($this->includeRequirements) { |
||
| 1235 | $output = Requirements::includeInHTML($output); |
||
| 1236 | } |
||
| 1237 | |||
| 1238 | array_pop(SSViewer::$topLevel); |
||
| 1239 | |||
| 1240 | // If we have our crazy base tag, then fix # links referencing the current page. |
||
| 1241 | |||
| 1242 | $rewrite = Config::inst()->get('SSViewer', 'rewrite_hash_links'); |
||
| 1243 | if($this->rewriteHashlinks && $rewrite) { |
||
| 1244 | if(strpos($output, '<base') !== false) { |
||
| 1245 | if($rewrite === 'php') { |
||
| 1246 | $thisURLRelativeToBase = "<?php echo Convert::raw2att(preg_replace(\"/^(\\\\/)+/\", \"/\", \$_SERVER['REQUEST_URI'])); ?>"; |
||
| 1247 | } else { |
||
| 1248 | $thisURLRelativeToBase = Convert::raw2att(preg_replace("/^(\\/)+/", "/", $_SERVER['REQUEST_URI'])); |
||
| 1249 | } |
||
| 1250 | |||
| 1251 | $output = preg_replace('/(<a[^>]+href *= *)"#/i', '\\1"' . $thisURLRelativeToBase . '#', $output); |
||
| 1252 | } |
||
| 1253 | } |
||
| 1254 | |||
| 1255 | return DBField::create_field('HTMLFragment', $output); |
||
| 1256 | } |
||
| 1257 | |||
| 1258 | /** |
||
| 1259 | * Execute the given template, passing it the given data. |
||
| 1260 | * Used by the <% include %> template tag to process templates. |
||
| 1261 | * |
||
| 1262 | * @param string $template Template name |
||
| 1263 | * @param mixed $data Data context |
||
| 1264 | * @param array $arguments Additional arguments |
||
| 1265 | * @return string Evaluated result |
||
| 1266 | */ |
||
| 1267 | public static function execute_template($template, $data, $arguments = null, $scope = null) { |
||
| 1273 | |||
| 1274 | /** |
||
| 1275 | * Execute the evaluated string, passing it the given data. |
||
| 1276 | * Used by partial caching to evaluate custom cache keys expressed using |
||
| 1277 | * template expressions |
||
| 1278 | * |
||
| 1279 | * @param string $content Input string |
||
| 1280 | * @param mixed $data Data context |
||
| 1281 | * @param array $arguments Additional arguments |
||
| 1282 | * @return string Evaluated result |
||
| 1283 | */ |
||
| 1284 | public static function execute_string($content, $data, $arguments = null) { |
||
| 1290 | |||
| 1291 | public function parseTemplateContent($content, $template="") { |
||
| 1298 | |||
| 1299 | /** |
||
| 1300 | * Returns the filenames of the template that will be rendered. It is a map that may contain |
||
| 1301 | * 'Content' & 'Layout', and will have to contain 'main' |
||
| 1302 | */ |
||
| 1303 | public function templates() { |
||
| 1306 | |||
| 1307 | /** |
||
| 1308 | * @param string $type "Layout" or "main" |
||
| 1309 | * @param string $file Full system path to the template file |
||
| 1310 | */ |
||
| 1311 | public function setTemplateFile($type, $file) { |
||
| 1312 | if (!$type || $type == 'main') $this->chosen = $file; |
||
| 1313 | else $this->subTemplates[$type] = $file; |
||
| 1314 | } |
||
| 1315 | |||
| 1316 | /** |
||
| 1317 | * Return an appropriate base tag for the given template. |
||
| 1318 | * It will be closed on an XHTML document, and unclosed on an HTML document. |
||
| 1319 | * |
||
| 1320 | * @param $contentGeneratedSoFar The content of the template generated so far; it should contain |
||
| 1321 | * the DOCTYPE declaration. |
||
| 1322 | */ |
||
| 1323 | public static function get_base_tag($contentGeneratedSoFar) { |
||
| 1324 | $base = Director::absoluteBaseURL(); |
||
| 1325 | |||
| 1326 | // Is the document XHTML? |
||
| 1327 | if(preg_match('/<!DOCTYPE[^>]+xhtml/i', $contentGeneratedSoFar)) { |
||
| 1328 | return "<base href=\"$base\" />"; |
||
| 1329 | } else { |
||
| 1330 | return "<base href=\"$base\"><!--[if lte IE 6]></base><![endif]-->"; |
||
| 1331 | } |
||
| 1332 | } |
||
| 1333 | } |
||
| 1334 | |||
| 1335 | /** |
||
| 1336 | * Special SSViewer that will process a template passed as a string, rather than a filename. |
||
| 1409 |
In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:
Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion: