|
4 | 4 |
|
5 | 5 | use Symphony\ApiFramework\Lib; |
6 | 6 |
|
7 | | -class extension_api_framework extends Extension |
8 | | -{ |
9 | | - public function getSubscribedDelegates() |
| 7 | +// This file is included automatically in the composer autoloader, however, |
| 8 | +// Symphony might try to include it again which would cause a fatal error. |
| 9 | +// Check if the class already exists before declaring it again. |
| 10 | +if (!class_exists("\\Extension_API_Framework")) { |
| 11 | + class Extension_API_Framework extends Extension |
10 | 12 | { |
11 | | - return[ |
| 13 | + const CACHE_DURATION_MINUTE = "minute"; |
| 14 | + const CACHE_DURATION_DAY = "day"; |
| 15 | + const CACHE_DURATION_HOUR= "hour"; |
| 16 | + const CACHE_DURATION_WEEK = "week"; |
| 17 | + |
| 18 | + const CACHE_ENABLED = 'yes'; |
| 19 | + const CACHE_DISABLED = 'no'; |
| 20 | + |
| 21 | + public function getSubscribedDelegates() |
| 22 | + { |
| 23 | + return [ |
| 24 | + [ |
| 25 | + 'page' => '/all/', |
| 26 | + 'delegate' => 'ModifySymphonyLauncher', |
| 27 | + 'callback' => 'setJSONLauncher' |
| 28 | + ], |
| 29 | + [ |
| 30 | + 'page' => '/frontend/', |
| 31 | + 'delegate' => 'APIFrameworkJSONRendererAppendTransformations', |
| 32 | + 'callback' => 'appendTransformations' |
| 33 | + ], |
| 34 | + [ |
| 35 | + 'page' => '/system/preferences/', |
| 36 | + 'delegate' => 'AddCustomPreferenceFieldsets', |
| 37 | + 'callback' => 'appendPreferences' |
| 38 | + ], |
12 | 39 | [ |
13 | | - 'page' => '/all/', |
14 | | - 'delegate' => 'ModifySymphonyLauncher', |
15 | | - 'callback' => 'setJSONLauncher' |
| 40 | + 'page' => '/system/preferences/', |
| 41 | + 'delegate' => 'Save', |
| 42 | + 'callback' => 'savePreferences' |
16 | 43 | ], |
17 | 44 | [ |
18 | | - 'page' => '/frontend/', |
19 | | - 'delegate' => 'APIFrameworkJSONRendererAppendTransformations', |
20 | | - 'callback' => 'appendTransformations' |
| 45 | + 'page' => '/blueprints/pages/', |
| 46 | + 'delegate' => 'AppendPageContent', |
| 47 | + 'callback' => 'appendCacheablePageType' |
21 | 48 | ], |
22 | 49 | ]; |
23 | | - } |
| 50 | + } |
24 | 51 |
|
25 | | - public function appendTransformations($context) |
26 | | - { |
| 52 | + /** |
| 53 | + * Append API Framework page cache preferences |
| 54 | + * |
| 55 | + * @param array $context |
| 56 | + * delegate context |
| 57 | + */ |
| 58 | + public function appendPreferences($context) |
| 59 | + { |
| 60 | + // Create preference group |
| 61 | + $group = new XMLElement('fieldset'); |
| 62 | + $group->setAttribute('class', 'settings'); |
| 63 | + $group->appendChild(new XMLElement('legend', __('API Framework'))); |
27 | 64 |
|
28 | | - // Add the @jsonForceArray transformation |
29 | | - $context['transformer']->append( |
30 | | - new Lib\Transformation( |
31 | | - function (array $input, array $attributes=[]) { |
32 | | - // First make sure there is an attributes array |
33 | | - if (empty($attributes)) { |
34 | | - return false; |
35 | | - } |
36 | | - // Only looking at the jsonForceArray property |
37 | | - elseif (!isset($attributes['jsonForceArray']) || $attributes['jsonForceArray'] !== "true") { |
38 | | - return false; |
39 | | - } |
40 | | - // This is already an indexed array. |
41 | | - elseif (!Lib\array_is_assoc($input)) { |
42 | | - return false; |
43 | | - } |
44 | | - // jsonForceArray is set, and it's true |
45 | | - return true; |
46 | | - }, |
47 | | - function (array $input, array $attributes=[]) { |
48 | | - $result = []; |
49 | | - // Encapsulate everything in an array |
50 | | - foreach ($input as $key => $value) { |
51 | | - $result[$key] = $value; |
52 | | - unset($input[$key]); |
53 | | - } |
54 | | - $input[] = $result; |
55 | | - return $input; |
56 | | - } |
57 | | - ) |
58 | | - ); |
59 | | - } |
| 65 | + // Append enable cache |
| 66 | + $label = Widget::Label(); |
| 67 | + $input = Widget::Input('settings[api_framework][enable_caching]', self::CACHE_ENABLED, 'checkbox'); |
60 | 68 |
|
61 | | - public function setJSONLauncher($context) |
62 | | - { |
63 | | - if ($_REQUEST['mode'] == 'administration') { |
64 | | - return; |
| 69 | + if (self::isCacheEnabled()) { |
| 70 | + $input->setAttribute('checked', 'checked'); |
| 71 | + } |
| 72 | + |
| 73 | + $label->setValue($input->generate() . ' ' . __('Enable caching')); |
| 74 | + $group->appendChild($label); |
| 75 | + |
| 76 | + // Append help |
| 77 | + $group->appendChild(new XMLElement('p', __('Rendered page content for pages with type \'cacheable\' will have their content stored and reused for subsequent loads.'), ['class' => 'help'])); |
| 78 | + |
| 79 | + // Cache lifetime |
| 80 | + $label = Widget::Label(); |
| 81 | + $input = Widget::Input( |
| 82 | + 'settings[api_framework][cache_lifetime]', |
| 83 | + self::getCacheLifetime(), |
| 84 | + null, |
| 85 | + ['size' => '6'] |
| 86 | + ); |
| 87 | + $selected = self::getCacheDuration(); |
| 88 | + $options = [ |
| 89 | + [ |
| 90 | + self::CACHE_DURATION_MINUTE, |
| 91 | + ($selected == self::CACHE_DURATION_MINUTE), |
| 92 | + 'minute(s)' |
| 93 | + ], |
| 94 | + [ |
| 95 | + self::CACHE_DURATION_HOUR, |
| 96 | + ($selected == self::CACHE_DURATION_HOUR), |
| 97 | + 'hour(s)' |
| 98 | + ], |
| 99 | + [ |
| 100 | + self::CACHE_DURATION_DAY, |
| 101 | + ($selected == self::CACHE_DURATION_DAY), |
| 102 | + 'day(s)' |
| 103 | + ], |
| 104 | + [ |
| 105 | + self::CACHE_DURATION_WEEK, |
| 106 | + ($selected == self::CACHE_DURATION_WEEK), |
| 107 | + 'week(s)' |
| 108 | + ], |
| 109 | + ]; |
| 110 | + $select = Widget::Select('settings[api_framework][cache_duration]', $options, ['class' => 'inline', 'style' => 'display: inline; width: auto;']); |
| 111 | + |
| 112 | + $label->setValue(__('Refresh page cache every %s %s', [$input->generate(false), $select->generate(false)])); |
| 113 | + $group->appendChild($label); |
| 114 | + |
| 115 | + // Append help |
| 116 | + $group->appendChild(new XMLElement('p', __('Once page cache expires, the next time that page is loaded any existing cache data will be replaced.'), ['class' => 'help'])); |
| 117 | + |
| 118 | + // Append disable cleanup |
| 119 | + $label = Widget::Label(); |
| 120 | + $input = Widget::Input('settings[api_framework][cache_disable_cleanup]', 'yes', 'checkbox'); |
| 121 | + |
| 122 | + if (!self::isCacheCleanupEnabled()) { |
| 123 | + $input->setAttribute('checked', 'checked'); |
| 124 | + } |
| 125 | + |
| 126 | + $label->setValue($input->generate() . ' ' . __('Disable Cleanup')); |
| 127 | + $group->appendChild($label); |
| 128 | + |
| 129 | + // Append help |
| 130 | + $group->appendChild(new XMLElement('p', __('By default, any expired cache entries are automatically checked for and removed each time a cacheable page is rendered. If there the site has a large volume of cached content, you may wish to disable this to reduce load.'), ['class' => 'help'])); |
| 131 | + |
| 132 | + // Append new preference group |
| 133 | + $context['wrapper']->appendChild($group); |
65 | 134 | } |
66 | | - define('SYMPHONY_LAUNCHER', 'renderer_json'); |
67 | 135 |
|
68 | | - include __DIR__ . '/src/Includes/JsonRendererLauncher.php'; |
| 136 | + /** |
| 137 | + * Save preferences |
| 138 | + * |
| 139 | + * @param array $context |
| 140 | + * delegate context |
| 141 | + */ |
| 142 | + public function savePreferences($context) |
| 143 | + { |
| 144 | + if (!is_array($context['settings'])) { |
| 145 | + // Disable caching by default |
| 146 | + $context['settings'] = [ |
| 147 | + 'api_framework' => [ |
| 148 | + 'enable_caching' => self::CACHE_DISABLED, |
| 149 | + 'cache_lifetime' => 1, |
| 150 | + 'cache_duration' => self::CACHE_DURATION_HOUR, |
| 151 | + 'cache_disable_cleanup' => 'no' |
| 152 | + ] |
| 153 | + ]; |
| 154 | + } else { |
| 155 | + if (!isset($context['settings']['api_framework']['enable_caching'])) { |
| 156 | + // Disable caching if it has not been checked |
| 157 | + $context['settings']['api_framework']['enable_caching'] = self::CACHE_DISABLED; |
| 158 | + } |
| 159 | + |
| 160 | + if (!isset($context['settings']['api_framework']['cache_disable_cleanup'])) { |
| 161 | + // Disable cache cheanup had been checked |
| 162 | + $context['settings']['api_framework']['cache_disable_cleanup'] = 'no'; |
| 163 | + } |
| 164 | + |
| 165 | + $context['settings']['api_framework']['cache_lifetime'] = max(1, (int)$context['settings']['api_framework']['cache_lifetime']); |
| 166 | + } |
| 167 | + } |
| 168 | + |
| 169 | + /** |
| 170 | + * Check if cache is enabled |
| 171 | + */ |
| 172 | + public static function isCacheEnabled() |
| 173 | + { |
| 174 | + return |
| 175 | + strtolower(Symphony::Configuration()->get('enable_caching', 'api_framework')) == self::CACHE_ENABLED |
| 176 | + ? true |
| 177 | + : false |
| 178 | + ; |
| 179 | + } |
| 180 | + |
| 181 | + /** |
| 182 | + * Check if cache cleanup is enabled |
| 183 | + */ |
| 184 | + public static function isCacheCleanupEnabled() |
| 185 | + { |
| 186 | + return |
| 187 | + strtolower(Symphony::Configuration()->get('cache_disable_cleanup', 'api_framework')) != 'yes' |
| 188 | + ? true |
| 189 | + : false |
| 190 | + ; |
| 191 | + } |
| 192 | + |
| 193 | + /** |
| 194 | + * Convienence method for getting the cache_lifetime setting |
| 195 | + */ |
| 196 | + public static function getCacheLifetime() |
| 197 | + { |
| 198 | + return (int)Symphony::Configuration()->get('cache_lifetime', 'api_framework'); |
| 199 | + } |
| 200 | + |
| 201 | + /** |
| 202 | + * Convienence method for getting the cache_duration setting |
| 203 | + */ |
| 204 | + public static function getCacheDuration() |
| 205 | + { |
| 206 | + return Symphony::Configuration()->get('cache_duration', 'api_framework'); |
| 207 | + } |
| 208 | + |
| 209 | + /** |
| 210 | + * Takes the cache lifetime and duration and calculate when cache should |
| 211 | + * expire. |
| 212 | + * @returns timestamp |
| 213 | + */ |
| 214 | + public static function calculateNextCacheExpiryTime() |
| 215 | + { |
| 216 | + return strtotime(sprintf( |
| 217 | + "+%s %s", |
| 218 | + self::getCacheLifetime(), |
| 219 | + self::getCacheDuration() |
| 220 | + )); |
| 221 | + } |
| 222 | + |
| 223 | + /** |
| 224 | + * Converts cache lifetime and cache duration into seconds. This is used |
| 225 | + * when calculating the cache expiry time. |
| 226 | + * @returns integer |
| 227 | + */ |
| 228 | + public static function cacheLifetimeReal() |
| 229 | + { |
| 230 | + $value = self::getCacheLifetime(); |
| 231 | + |
| 232 | + switch (self::getCacheDuration()) { |
| 233 | + |
| 234 | + case self::DURATION_WEEK: |
| 235 | + $value *= 7; |
| 236 | + |
| 237 | + // no break |
| 238 | + case self::DURATION_DAY: |
| 239 | + $value *= 24; |
| 240 | + |
| 241 | + // no break |
| 242 | + case self::DURATION_HOUR: |
| 243 | + $value *= 60; |
| 244 | + break; |
| 245 | + |
| 246 | + } |
| 247 | + |
| 248 | + return $value; |
| 249 | + } |
| 250 | + |
| 251 | + /** |
| 252 | + * Append type for cacheable pages to page editor. |
| 253 | + * |
| 254 | + * @param array $context |
| 255 | + * delegate context |
| 256 | + */ |
| 257 | + public function appendCacheablePageType($context) |
| 258 | + { |
| 259 | + // Find page types |
| 260 | + $elements = $context['form']->getChildren(); |
| 261 | + $fieldset = $elements[0]->getChildren(); |
| 262 | + $group = $fieldset[2]->getChildren(); |
| 263 | + $div = $group[1]->getChildren(); |
| 264 | + $types = $div[2]->getChildren(); |
| 265 | + |
| 266 | + // Search for existing cacheable type |
| 267 | + $cacheableTypeAlreadyExists = false; |
| 268 | + foreach ($types as $type) { |
| 269 | + if ($type->getValue() == 'cacheable') { |
| 270 | + $cacheableTypeAlreadyExists = true; |
| 271 | + break; |
| 272 | + } |
| 273 | + } |
| 274 | + |
| 275 | + // Append cacheable type |
| 276 | + if (!$cacheableTypeAlreadyExists) { |
| 277 | + $div[2]->appendChild(new XMLElement('li', 'cacheable')); |
| 278 | + } |
| 279 | + } |
| 280 | + |
| 281 | + public function appendTransformations($context) |
| 282 | + { |
| 283 | + // Add the @jsonForceArray transformation |
| 284 | + $context['transformer']->append(new Lib\Transformation( |
| 285 | + function (array $input, array $attributes=[]) { |
| 286 | + // First make sure there is an attributes array |
| 287 | + if (empty($attributes)) { |
| 288 | + return false; |
| 289 | + } |
| 290 | + // Only looking at the jsonForceArray property |
| 291 | + elseif (!isset($attributes['jsonForceArray']) || $attributes['jsonForceArray'] !== "true") { |
| 292 | + return false; |
| 293 | + } |
| 294 | + // This is already an indexed array. |
| 295 | + elseif (!Lib\array_is_assoc($input)) { |
| 296 | + return false; |
| 297 | + } |
| 298 | + // jsonForceArray is set, and it's true |
| 299 | + return true; |
| 300 | + }, |
| 301 | + function (array $input, array $attributes=[]) { |
| 302 | + $result = []; |
| 303 | + // Encapsulate everything in an array |
| 304 | + foreach ($input as $key => $value) { |
| 305 | + $result[$key] = $value; |
| 306 | + unset($input[$key]); |
| 307 | + } |
| 308 | + $input[] = $result; |
| 309 | + return $input; |
| 310 | + } |
| 311 | + )); |
| 312 | + |
| 313 | + // Add the @convertEmptyElementsToString transformation |
| 314 | + // Render empty string values instead of empty arrays |
| 315 | + // i.e. <banana></banana>, normally converted to |
| 316 | + // array(0) {} and thus banana: [], becomes banana: "" |
| 317 | + $context['transformer']->append(new Lib\Transformation( |
| 318 | + function (array $input, array $attributes=[]) { |
| 319 | + if (isset($attributes['convertEmptyElementsToString'])) { |
| 320 | + return true; |
| 321 | + } |
| 322 | + return false; |
| 323 | + }, |
| 324 | + function (array $input, array $attributes=[]) { |
| 325 | + foreach ($input as $key => $value) { |
| 326 | + if (empty($value)) { |
| 327 | + $input[$key] = ""; |
| 328 | + } |
| 329 | + } |
| 330 | + return $input; |
| 331 | + } |
| 332 | + )); |
| 333 | + } |
| 334 | + |
| 335 | + public function setJSONLauncher($context) |
| 336 | + { |
| 337 | + if ($_REQUEST['mode'] == 'administration') { |
| 338 | + return; |
| 339 | + } |
| 340 | + define('SYMPHONY_LAUNCHER', 'renderer_json'); |
| 341 | + |
| 342 | + include __DIR__ . '/src/Includes/JsonRendererLauncher.php'; |
| 343 | + } |
69 | 344 | } |
70 | 345 | } |
0 commit comments