| 1 | <?php |
|---|
| 2 | |
|---|
| 3 | // +---------------------------------------------------------------------------+ |
|---|
| 4 | // | This file is part of the Agavi package. | |
|---|
| 5 | // | Copyright (c) 2005-2008 the Agavi Project. | |
|---|
| 6 | // | Based on the Mojavi3 MVC Framework, Copyright (c) 2003-2005 Sean Kerr. | |
|---|
| 7 | // | | |
|---|
| 8 | // | For the full copyright and license information, please view the LICENSE | |
|---|
| 9 | // | file that was distributed with this source code. You can also view the | |
|---|
| 10 | // | LICENSE file online at http://www.agavi.org/LICENSE.txt | |
|---|
| 11 | // | vi: set noexpandtab: | |
|---|
| 12 | // | Local Variables: | |
|---|
| 13 | // | indent-tabs-mode: t | |
|---|
| 14 | // | End: | |
|---|
| 15 | // +---------------------------------------------------------------------------+ |
|---|
| 16 | |
|---|
| 17 | /** |
|---|
| 18 | * AgaviExecutionFilter is the last filter registered for each filter chain. |
|---|
| 19 | * This filter does all action and view execution. |
|---|
| 20 | * |
|---|
| 21 | * @package agavi |
|---|
| 22 | * @subpackage filter |
|---|
| 23 | * |
|---|
| 24 | * @author David Zülke <dz@bitxtender.com> |
|---|
| 25 | * @author Sean Kerr <skerr@mojavi.org> |
|---|
| 26 | * @copyright Authors |
|---|
| 27 | * @copyright The Agavi Project |
|---|
| 28 | * |
|---|
| 29 | * @since 0.9.0 |
|---|
| 30 | * |
|---|
| 31 | * @version $Id$ |
|---|
| 32 | */ |
|---|
| 33 | class AgaviExecutionFilter extends AgaviFilter implements AgaviIActionFilter |
|---|
| 34 | { |
|---|
| 35 | /* |
|---|
| 36 | * The directory inside %core.cache_dir% where cached stuff is stored. |
|---|
| 37 | */ |
|---|
| 38 | const CACHE_SUBDIR = 'content'; |
|---|
| 39 | |
|---|
| 40 | /* |
|---|
| 41 | * The name of the file that holds the cached action data. |
|---|
| 42 | * Minuses because these are not allowed in an output type name. |
|---|
| 43 | */ |
|---|
| 44 | const ACTION_CACHE_ID = '4-8-15-16-23-42'; |
|---|
| 45 | |
|---|
| 46 | /** |
|---|
| 47 | * Check if a cache exists and is up-to-date |
|---|
| 48 | * |
|---|
| 49 | * @param array An array of cache groups |
|---|
| 50 | * @param string The lifetime of the cache as a strtotime relative string |
|---|
| 51 | * without the leading plus sign. |
|---|
| 52 | * |
|---|
| 53 | * @return bool true, if the cache is up to date, otherwise false |
|---|
| 54 | * |
|---|
| 55 | * @author David Zülke <dz@bitxtender.com> |
|---|
| 56 | * @since 0.11.0 |
|---|
| 57 | */ |
|---|
| 58 | public function checkCache(array $groups, $lifetime = null) |
|---|
| 59 | { |
|---|
| 60 | foreach($groups as &$group) { |
|---|
| 61 | $group = base64_encode($group); |
|---|
| 62 | } |
|---|
| 63 | $filename = AgaviConfig::get('core.cache_dir') . DIRECTORY_SEPARATOR . self::CACHE_SUBDIR . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $groups) . '.cefcache'; |
|---|
| 64 | $isReadable = is_readable($filename); |
|---|
| 65 | if($lifetime === null || !$isReadable) { |
|---|
| 66 | return $isReadable; |
|---|
| 67 | } else { |
|---|
| 68 | $expiry = strtotime('+' . $lifetime, filemtime($filename)); |
|---|
| 69 | if($expiry !== false) { |
|---|
| 70 | return $isReadable && ($expiry >= time()); |
|---|
| 71 | } else { |
|---|
| 72 | return false; |
|---|
| 73 | } |
|---|
| 74 | } |
|---|
| 75 | } |
|---|
| 76 | |
|---|
| 77 | /** |
|---|
| 78 | * Read the contents of a cache |
|---|
| 79 | * |
|---|
| 80 | * @param array An array of cache groups |
|---|
| 81 | * |
|---|
| 82 | * @return array The cache data |
|---|
| 83 | * |
|---|
| 84 | * @author David Zülke <dz@bitxtender.com> |
|---|
| 85 | * @since 0.11.0 |
|---|
| 86 | */ |
|---|
| 87 | public function readCache(array $groups) |
|---|
| 88 | { |
|---|
| 89 | foreach($groups as &$group) { |
|---|
| 90 | $group = base64_encode($group); |
|---|
| 91 | } |
|---|
| 92 | $filename = AgaviConfig::get('core.cache_dir') . DIRECTORY_SEPARATOR . self::CACHE_SUBDIR . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $groups) . '.cefcache'; |
|---|
| 93 | $data = @file_get_contents($filename); |
|---|
| 94 | if($data !== false) { |
|---|
| 95 | return unserialize($data); |
|---|
| 96 | } else { |
|---|
| 97 | throw new AgaviException(sprintf('Failed to read cache file "%s"', $filename)); |
|---|
| 98 | } |
|---|
| 99 | } |
|---|
| 100 | |
|---|
| 101 | /** |
|---|
| 102 | * Write cache content |
|---|
| 103 | * |
|---|
| 104 | * @param array An array of cache groups |
|---|
| 105 | * @param array The cache data |
|---|
| 106 | * @param string The lifetime of the cache as a strtotime relative string |
|---|
| 107 | * without the leading plus sign. |
|---|
| 108 | * |
|---|
| 109 | * @return bool The result of the write operation |
|---|
| 110 | * |
|---|
| 111 | * @author David Zülke <dz@bitxtender.com> |
|---|
| 112 | * @since 0.11.0 |
|---|
| 113 | */ |
|---|
| 114 | public function writeCache(array $groups, $data, $lifetime = null) |
|---|
| 115 | { |
|---|
| 116 | // lifetime is not used in this implementation! |
|---|
| 117 | |
|---|
| 118 | foreach($groups as &$group) { |
|---|
| 119 | $group = base64_encode($group); |
|---|
| 120 | } |
|---|
| 121 | @mkdir(AgaviConfig::get('core.cache_dir') . DIRECTORY_SEPARATOR . self::CACHE_SUBDIR . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR , array_slice($groups, 0, -1)), 0777, true); |
|---|
| 122 | return file_put_contents(AgaviConfig::get('core.cache_dir') . DIRECTORY_SEPARATOR . self::CACHE_SUBDIR . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $groups) . '.cefcache', serialize($data), LOCK_EX); |
|---|
| 123 | } |
|---|
| 124 | |
|---|
| 125 | /** |
|---|
| 126 | * Flushes the cache for a group |
|---|
| 127 | * |
|---|
| 128 | * @param array An array of cache groups |
|---|
| 129 | * |
|---|
| 130 | * @author David Zülke <dz@bitxtender.com> |
|---|
| 131 | * @since 0.11.0 |
|---|
| 132 | */ |
|---|
| 133 | public static function clearCache(array $groups = array()) |
|---|
| 134 | { |
|---|
| 135 | foreach($groups as &$group) { |
|---|
| 136 | $group = base64_encode($group); |
|---|
| 137 | } |
|---|
| 138 | $path = self::CACHE_SUBDIR . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $groups); |
|---|
| 139 | if(is_file(AgaviConfig::get('core.cache_dir') . DIRECTORY_SEPARATOR . $path . '.cefcache')) { |
|---|
| 140 | AgaviToolkit::clearCache($path . '.cefcache'); |
|---|
| 141 | } else { |
|---|
| 142 | AgaviToolkit::clearCache($path); |
|---|
| 143 | } |
|---|
| 144 | } |
|---|
| 145 | |
|---|
| 146 | /** |
|---|
| 147 | * Builds an array of cache groups using the configuration and a container. |
|---|
| 148 | * |
|---|
| 149 | * @param array The group array from the configuration. |
|---|
| 150 | * @param AgaviExecutionContainer The execution container. |
|---|
| 151 | * |
|---|
| 152 | * @return array An array of groups. |
|---|
| 153 | * |
|---|
| 154 | * @author David Zülke <dz@bitxtender.com> |
|---|
| 155 | * @since 0.11.0 |
|---|
| 156 | */ |
|---|
| 157 | public function determineGroups(array $groups, AgaviExecutionContainer $container) |
|---|
| 158 | { |
|---|
| 159 | $retval = array(); |
|---|
| 160 | |
|---|
| 161 | foreach($groups as $group) { |
|---|
| 162 | $group += array('name' => null, 'source' => null, 'namespace' => null); |
|---|
| 163 | $val = $this->getVariable($group['name'], $group['source'], $group['namespace'], $container); |
|---|
| 164 | if($val === null) { |
|---|
| 165 | $val = "0"; |
|---|
| 166 | } elseif(is_object($val) && is_callable(array($val, '__toString'))) { |
|---|
| 167 | $val = $val->__toString(); |
|---|
| 168 | } elseif(is_object($val) && function_exists('spl_object_hash')) { |
|---|
| 169 | $val = spl_object_hash($val); |
|---|
| 170 | } |
|---|
| 171 | $retval[] = $val; |
|---|
| 172 | } |
|---|
| 173 | |
|---|
| 174 | $retval[] = $container->getModuleName() . '_' . $container->getActionName(); |
|---|
| 175 | |
|---|
| 176 | return $retval; |
|---|
| 177 | } |
|---|
| 178 | |
|---|
| 179 | /** |
|---|
| 180 | * Read a variable from the given source and, optionally, namespace. |
|---|
| 181 | * |
|---|
| 182 | * @param string The variable name. |
|---|
| 183 | * @param string The optional variable source. |
|---|
| 184 | * @param string The optional namespace in the source. |
|---|
| 185 | * @param AgaviExecutionContainer The container to use, if necessary. |
|---|
| 186 | * |
|---|
| 187 | * @return mixed The variable. |
|---|
| 188 | * |
|---|
| 189 | * @author David Zülke <dz@bitxtender.com> |
|---|
| 190 | * @since 0.11.0 |
|---|
| 191 | */ |
|---|
| 192 | public function getVariable($name, $source = 'string', $namespace = null, AgaviExecutionContainer $container = null) |
|---|
| 193 | { |
|---|
| 194 | $val = $name; |
|---|
| 195 | |
|---|
| 196 | switch($source) { |
|---|
| 197 | case 'constant': |
|---|
| 198 | $val = constant($name); |
|---|
| 199 | break; |
|---|
| 200 | case 'container_parameter': |
|---|
| 201 | $val = $container->getParameter($name); |
|---|
| 202 | break; |
|---|
| 203 | case 'global_request_data': |
|---|
| 204 | $val = $this->context->getRequest()->getRequestData()->get($namespace ? $namespace : AgaviRequestDataHolder::SOURCE_PARAMETERS, $name); |
|---|
| 205 | break; |
|---|
| 206 | case 'locale': |
|---|
| 207 | $val = $this->context->getTranslationManager()->getCurrentLocaleIdentifier(); |
|---|
| 208 | break; |
|---|
| 209 | case 'request_attribute': |
|---|
| 210 | $val = $this->context->getRequest()->getAttribute($name, $namespace); |
|---|
| 211 | break; |
|---|
| 212 | case 'request_data': |
|---|
| 213 | $val = $container->getRequestData()->get($namespace ? $namespace : AgaviRequestDataHolder::SOURCE_PARAMETERS, $name); |
|---|
| 214 | break; |
|---|
| 215 | case 'request_parameter': |
|---|
| 216 | $val = $this->context->getRequest()->getRequestData()->getParameter($name); |
|---|
| 217 | break; |
|---|
| 218 | case 'user_attribute': |
|---|
| 219 | $val = $this->context->getUser()->getAttribute($name, $namespace); |
|---|
| 220 | break; |
|---|
| 221 | case 'user_authenticated': |
|---|
| 222 | if(($user = $this->context->getUser()) instanceof AgaviISecurityUser) { |
|---|
| 223 | $val = $user->isAuthenticated(); |
|---|
| 224 | } |
|---|
| 225 | break; |
|---|
| 226 | case 'user_credential': |
|---|
| 227 | if(($user = $this->context->getUser()) instanceof AgaviISecurityUser) { |
|---|
| 228 | $val = $user->hasCredentials($name); |
|---|
| 229 | } |
|---|
| 230 | break; |
|---|
| 231 | case 'user_parameter': |
|---|
| 232 | $val = $this->context->getUser()->getParameter($name); |
|---|
| 233 | break; |
|---|
| 234 | } |
|---|
| 235 | |
|---|
| 236 | return $val; |
|---|
| 237 | } |
|---|
| 238 | |
|---|
| 239 | /** |
|---|
| 240 | * Execute this filter. |
|---|
| 241 | * |
|---|
| 242 | * @param AgaviFilterChain The filter chain. |
|---|
| 243 | * @param AgaviExecutionContainer The current execution container. |
|---|
| 244 | * |
|---|
| 245 | * @throws <b>AgaviInitializationException</b> If an error occurs during |
|---|
| 246 | * View initialization. |
|---|
| 247 | * @throws <b>AgaviViewException</b> If an error occurs while |
|---|
| 248 | * executing the View. |
|---|
| 249 | * |
|---|
| 250 | * @author David Zülke <dz@bitxtender.com> |
|---|
| 251 | * @author Sean Kerr <skerr@mojavi.org> |
|---|
| 252 | * @since 0.9.0 |
|---|
| 253 | */ |
|---|
| 254 | public function execute(AgaviFilterChain $filterChain, AgaviExecutionContainer $container) |
|---|
| 255 | { |
|---|
| 256 | // $lm = $this->context->getLoggerManager(); |
|---|
| 257 | |
|---|
| 258 | // get the context, controller and validator manager |
|---|
| 259 | $controller = $this->context->getController(); |
|---|
| 260 | |
|---|
| 261 | // get the current action information |
|---|
| 262 | $actionName = $container->getActionName(); |
|---|
| 263 | $moduleName = $container->getModuleName(); |
|---|
| 264 | |
|---|
| 265 | // the action instance |
|---|
| 266 | $actionInstance = $container->getActionInstance(); |
|---|
| 267 | |
|---|
| 268 | $request = $this->context->getRequest(); |
|---|
| 269 | |
|---|
| 270 | $isCacheable = false; |
|---|
| 271 | if($this->getParameter('enable_caching', true) && is_readable($cachingDotXml = AgaviConfig::get('core.module_dir') . '/' . $moduleName . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR . $actionName . '.xml')) { |
|---|
| 272 | // $lm->log('Caching enabled, configuration file found, loading...'); |
|---|
| 273 | // no _once please! |
|---|
| 274 | include(AgaviConfigCache::checkConfig($cachingDotXml, $this->context->getName())); |
|---|
| 275 | } |
|---|
| 276 | |
|---|
| 277 | $isActionCached = false; |
|---|
| 278 | |
|---|
| 279 | if($isCacheable) { |
|---|
| 280 | $groups = $this->determineGroups($config["groups"], $container); |
|---|
| 281 | $isActionCached = $this->checkCache(array_merge($groups, array(self::ACTION_CACHE_ID)), $config['lifetime']); |
|---|
| 282 | } else { |
|---|
| 283 | // $lm->log('Action is not cacheable!'); |
|---|
| 284 | } |
|---|
| 285 | |
|---|
| 286 | if($isActionCached) { |
|---|
| 287 | // $lm->log('Action is cached, loading...'); |
|---|
| 288 | // cache/dir/4-8-15-16-23-42 contains the action cache |
|---|
| 289 | try { |
|---|
| 290 | $actionCache = $this->readCache(array_merge($groups, array(self::ACTION_CACHE_ID))); |
|---|
| 291 | // and restore action attributes |
|---|
| 292 | $actionInstance->setAttributes($actionCache['action_attributes']); |
|---|
| 293 | } catch(AgaviException $e) { |
|---|
| 294 | $isActionCached = false; |
|---|
| 295 | } |
|---|
| 296 | } |
|---|
| 297 | |
|---|
| 298 | $isViewCached = false; |
|---|
| 299 | $rememberTheView = null; |
|---|
| 300 | |
|---|
| 301 | while(true) { |
|---|
| 302 | if(!$isActionCached) { |
|---|
| 303 | $actionCache = array(); |
|---|
| 304 | |
|---|
| 305 | // $lm->log('Action not cached, executing...'); |
|---|
| 306 | // execute the Action and get the View to execute |
|---|
| 307 | list($actionCache['view_module'], $actionCache['view_name']) = $this->runAction($container); |
|---|
| 308 | |
|---|
| 309 | // check if we've just run the action again after a previous cache read revealed that the view is not cached for this output type and we need to go back to square one due to the lack of action attribute caching configuration... |
|---|
| 310 | // if yes: is the view module/name that we got just now different from what was in the cache? |
|---|
| 311 | if(isset($rememberTheView) && $actionCache != $rememberTheView) { |
|---|
| 312 | // yup. clear it! |
|---|
| 313 | $ourClass = get_class($this); |
|---|
| 314 | call_user_func(array($ourClass, 'clearCache'), $groups); |
|---|
| 315 | } |
|---|
| 316 | |
|---|
| 317 | // check if the returned view is cacheable |
|---|
| 318 | if($isCacheable && is_array($config['views']) && !in_array(array('module' => $actionCache['view_module'], 'name' => $actionCache['view_name']), $config['views'], true)) { |
|---|
| 319 | $isCacheable = false; |
|---|
| 320 | |
|---|
| 321 | // so that view is not cacheable? okay then: |
|---|
| 322 | // check if we've just run the action again after a previous cache read revealed that the view is not cached for this output type and we need to go back to square one due to the lack of action attribute caching configuration... |
|---|
| 323 | // 'cause then we need to flush all those existing caches - obviously, that data is stale now, as we learned, since we are not allowed to cache anymore for the view that was returned now |
|---|
| 324 | if(isset($rememberTheView)) { |
|---|
| 325 | // yup. clear it! |
|---|
| 326 | $ourClass = get_class($this); |
|---|
| 327 | call_user_func(array($ourClass, 'clearCache'), $groups); |
|---|
| 328 | } |
|---|
| 329 | // $lm->log('Returned View is not cleared for caching, setting cacheable status to false.'); |
|---|
| 330 | } else { |
|---|
| 331 | // $lm->log('Returned View is cleared for caching, proceeding...'); |
|---|
| 332 | } |
|---|
| 333 | |
|---|
| 334 | $actionAttributes = $actionInstance->getAttributes(); |
|---|
| 335 | } |
|---|
| 336 | |
|---|
| 337 | // clear the response |
|---|
| 338 | $response = $container->getResponse(); |
|---|
| 339 | $response->clear(); |
|---|
| 340 | |
|---|
| 341 | // clear any forward set, it's ze view's job |
|---|
| 342 | $container->clearNext(); |
|---|
| 343 | |
|---|
| 344 | if($actionCache['view_name'] !== AgaviView::NONE) { |
|---|
| 345 | |
|---|
| 346 | $container->setViewModuleName($actionCache['view_module']); |
|---|
| 347 | $container->setViewName($actionCache['view_name']); |
|---|
| 348 | |
|---|
| 349 | $key = $request->toggleLock(); |
|---|
| 350 | try { |
|---|
| 351 | // get the view instance |
|---|
| 352 | $viewInstance = $controller->createViewInstance($actionCache['view_module'], $actionCache['view_name']); |
|---|
| 353 | // initialize the view |
|---|
| 354 | $viewInstance->initialize($container); |
|---|
| 355 | } catch(Exception $e) { |
|---|
| 356 | // we caught an exception... unlock the request and rethrow! |
|---|
| 357 | $request->toggleLock($key); |
|---|
| 358 | throw $e; |
|---|
| 359 | } |
|---|
| 360 | $request->toggleLock($key); |
|---|
| 361 | |
|---|
| 362 | // Set the View Instance in the container |
|---|
| 363 | $container->setViewInstance($viewInstance); |
|---|
| 364 | |
|---|
| 365 | $outputType = $container->getOutputType()->getName(); |
|---|
| 366 | |
|---|
| 367 | if($isCacheable) { |
|---|
| 368 | if(isset($config['output_types'][$otConfig = $outputType]) || isset($config['output_types'][$otConfig = '*'])) { |
|---|
| 369 | $otConfig = $config['output_types'][$otConfig]; |
|---|
| 370 | |
|---|
| 371 | if($isActionCached) { |
|---|
| 372 | $isViewCached = $this->checkCache(array_merge($groups, array($outputType)), $config['lifetime']); |
|---|
| 373 | } |
|---|
| 374 | } else { |
|---|
| 375 | $isCacheable = false; |
|---|
| 376 | } |
|---|
| 377 | } |
|---|
| 378 | |
|---|
| 379 | if($isViewCached) { |
|---|
| 380 | // $lm->log('View is cached, loading...'); |
|---|
| 381 | try { |
|---|
| 382 | $viewCache = $this->readCache(array_merge($groups, array($outputType))); |
|---|
| 383 | } catch(AgaviException $e) { |
|---|
| 384 | $isViewCached = false; |
|---|
| 385 | } |
|---|
| 386 | } |
|---|
| 387 | if(!$isViewCached) { |
|---|
| 388 | // view not cached |
|---|
| 389 | // but the action might |
|---|
| 390 | if($isActionCached && !$config['action_attributes']) { |
|---|
| 391 | // has the cache config a list of action attributes? |
|---|
| 392 | // no. that means we must run the action again! |
|---|
| 393 | $isActionCached = false; |
|---|
| 394 | // but remember the view info, just in case it differs if we run the action again now |
|---|
| 395 | $rememberTheView = array( |
|---|
| 396 | 'view_module' => $actionCache['view_module'], |
|---|
| 397 | 'view_name' => $actionCache['view_name'], |
|---|
| 398 | ); |
|---|
| 399 | continue; |
|---|
| 400 | } |
|---|
| 401 | |
|---|
| 402 | $viewCache = array(); |
|---|
| 403 | |
|---|
| 404 | // $lm->log('View is not cached, executing...'); |
|---|
| 405 | // view initialization completed successfully |
|---|
| 406 | $executeMethod = 'execute' . $outputType; |
|---|
| 407 | if(!method_exists($viewInstance, $executeMethod)) { |
|---|
| 408 | $executeMethod = 'execute'; |
|---|
| 409 | } |
|---|
| 410 | $key = $request->toggleLock(); |
|---|
| 411 | try { |
|---|
| 412 | $viewCache['next'] = $viewInstance->$executeMethod($container->getRequestData()); |
|---|
| 413 | } catch(Exception $e) { |
|---|
| 414 | // we caught an exception... unlock the request and rethrow! |
|---|
| 415 | $request->toggleLock($key); |
|---|
| 416 | throw $e; |
|---|
| 417 | } |
|---|
| 418 | $request->toggleLock($key); |
|---|
| 419 | } |
|---|
| 420 | |
|---|
| 421 | if($viewCache['next'] instanceof AgaviExecutionContainer) { |
|---|
| 422 | // $lm->log('Forwarding request, skipping rendering...'); |
|---|
| 423 | $container->setNext($viewCache['next']); |
|---|
| 424 | } else { |
|---|
| 425 | $output = array(); |
|---|
| 426 | $nextOutput = null; |
|---|
| 427 | |
|---|
| 428 | if($isViewCached) { |
|---|
| 429 | $layers = $viewCache['layers']; |
|---|
| 430 | $response = $viewCache['response']; |
|---|
| 431 | $container->setResponse($response); |
|---|
| 432 | |
|---|
| 433 | foreach($viewCache['template_variables'] as $name => $value) { |
|---|
| 434 | $viewInstance->setAttribute($name, $value); |
|---|
| 435 | } |
|---|
| 436 | |
|---|
| 437 | foreach($viewCache['request_attributes'] as $requestAttribute) { |
|---|
| 438 | $request->setAttribute($requestAttribute['name'], $requestAttribute['value'], $requestAttribute['namespace']); |
|---|
| 439 | } |
|---|
| 440 | |
|---|
| 441 | foreach($viewCache['request_attribute_namespaces'] as $ranName => $ranValues) { |
|---|
| 442 | $request->setAttributes($ranValues, $ranName); |
|---|
| 443 | } |
|---|
| 444 | |
|---|
| 445 | $nextOutput = $response->getContent(); |
|---|
| 446 | } else { |
|---|
| 447 | if($viewCache['next'] !== null) { |
|---|
| 448 | // response content was returned from view execute() |
|---|
| 449 | $response->setContent($nextOutput = $viewCache['next']); |
|---|
| 450 | $viewCache['next'] = null; |
|---|
| 451 | } |
|---|
| 452 | |
|---|
| 453 | $layers = $viewInstance->getLayers(); |
|---|
| 454 | |
|---|
| 455 | if($isCacheable) { |
|---|
| 456 | $viewCache['template_variables'] = array(); |
|---|
| 457 | foreach($otConfig['template_variables'] as $varName) { |
|---|
| 458 | $viewCache['template_variables'][$varName] = $viewInstance->getAttribute($varName); |
|---|
| 459 | } |
|---|
| 460 | |
|---|
| 461 | $viewCache['response'] = clone $response; |
|---|
| 462 | |
|---|
| 463 | $viewCache['layers'] = array(); |
|---|
| 464 | |
|---|
| 465 | $viewCache['slots'] = array(); |
|---|
| 466 | |
|---|
| 467 | $lastCacheableLayer = -1; |
|---|
| 468 | if(is_array($otConfig['layers'])) { |
|---|
| 469 | if(count($otConfig['layers'])) { |
|---|
| 470 | for($i = count($layers)-1; $i >= 0; $i--) { |
|---|
| 471 | $layer = $layers[$i]; |
|---|
| 472 | $layerName = $layer->getName(); |
|---|
| 473 | if(isset($otConfig['layers'][$layerName])) { |
|---|
| 474 | if(is_array($otConfig['layers'][$layerName])) { |
|---|
| 475 | $lastCacheableLayer = $i - 1; |
|---|
| 476 | } else { |
|---|
| 477 | $lastCacheableLayer = $i; |
|---|
| 478 | } |
|---|
| 479 | } |
|---|
| 480 | } |
|---|
| 481 | } |
|---|
| 482 | } else { |
|---|
| 483 | $lastCacheableLayer = count($layers) - 1; |
|---|
| 484 | } |
|---|
| 485 | |
|---|
| 486 | for($i = $lastCacheableLayer + 1; $i < count($layers); $i++) { |
|---|
| 487 | // $lm->log('Adding non-cacheable layer "' . $layers[$i]->getName() . '" to list'); |
|---|
| 488 | $viewCache['layers'][] = clone $layers[$i]; |
|---|
| 489 | } |
|---|
| 490 | } |
|---|
| 491 | } |
|---|
| 492 | |
|---|
| 493 | $attributes =& $viewInstance->getAttributes(); |
|---|
| 494 | |
|---|
| 495 | // whether or not we should assign the previous' layer's output to the $slots array |
|---|
| 496 | $assignInnerToSlots = $this->getParameter('assign_inner_to_slots', false); |
|---|
| 497 | |
|---|
| 498 | // $lm->log('Starting rendering...'); |
|---|
| 499 | for($i = 0; $i < count($layers); $i++) { |
|---|
| 500 | $layer = $layers[$i]; |
|---|
| 501 | $layerName = $layer->getName(); |
|---|
| 502 | // $lm->log('Running layer "' . $layerName . '"...'); |
|---|
| 503 | foreach($layer->getSlots() as $slotName => $slotContainer) { |
|---|
| 504 | if($isViewCached && isset($viewCache['slots'][$layerName][$slotName])) { |
|---|
| 505 | // $lm->log('Loading cached slot "' . $slotName . '"...'); |
|---|
| 506 | $slotResponse = $viewCache['slots'][$layerName][$slotName]; |
|---|
| 507 | } else { |
|---|
| 508 | // $lm->log('Running slot "' . $slotName . '"...'); |
|---|
| 509 | $slotResponse = $slotContainer->execute(); |
|---|
| 510 | if($isCacheable && !$isViewCached && isset($otConfig['layers'][$layerName]) && is_array($otConfig['layers'][$layerName]) && in_array($slotName, $otConfig['layers'][$layerName])) { |
|---|
| 511 | // $lm->log('Adding response of slot "' . $slotName . '" to cache...'); |
|---|
| 512 | $viewCache['slots'][$layerName][$slotName] = $slotResponse; |
|---|
| 513 | } |
|---|
| 514 | } |
|---|
| 515 | // set the presentation data as a template attribute |
|---|
| 516 | if(($output[$slotName] = $slotResponse->getContent()) !== null) { |
|---|
| 517 | // $lm->log('Merging in response from slot "' . $slotName . '"...'); |
|---|
| 518 | // the slot really output something |
|---|
| 519 | // let our response grab the stuff it needs from the slot's response |
|---|
| 520 | $response->merge($slotResponse); |
|---|
| 521 | } |
|---|
| 522 | } |
|---|
| 523 | $moreAssigns = array( |
|---|
| 524 | 'container' => $container, |
|---|
| 525 | 'inner' => $nextOutput, |
|---|
| 526 | 'request_data' => $container->getRequestData(), |
|---|
| 527 | 'validation_manager' => $container->getValidationManager(), |
|---|
| 528 | 'view' => $viewInstance, |
|---|
| 529 | ); |
|---|
| 530 | // lock the request. can't be done outside the loop for the whole run, see #628 |
|---|
| 531 | $key = $request->toggleLock(); |
|---|
| 532 | try { |
|---|
| 533 | $nextOutput = $layer->getRenderer()->render($layer, $attributes, $output, $moreAssigns); |
|---|
| 534 | } catch(Exception $e) { |
|---|
| 535 | // we caught an exception... unlock the request and rethrow! |
|---|
| 536 | $request->toggleLock($key); |
|---|
| 537 | throw $e; |
|---|
| 538 | } |
|---|
| 539 | // and unlock the request again |
|---|
| 540 | $request->toggleLock($key); |
|---|
| 541 | |
|---|
| 542 | $response->setContent($nextOutput); |
|---|
| 543 | |
|---|
| 544 | if($isCacheable && !$isViewCached && $i === $lastCacheableLayer) { |
|---|
| 545 | $viewCache['response'] = clone $response; |
|---|
| 546 | } |
|---|
| 547 | |
|---|
| 548 | $output = array(); |
|---|
| 549 | if($assignInnerToSlots) { |
|---|
| 550 | $output[$layer->getName()] = $nextOutput; |
|---|
| 551 | } |
|---|
| 552 | } |
|---|
| 553 | } |
|---|
| 554 | |
|---|
| 555 | if($isCacheable && !$isViewCached) { |
|---|
| 556 | // we're writing the view cache first. this is just in case we get into a situation with really bad timing on the leap of a second |
|---|
| 557 | $viewCache['request_attributes'] = array(); |
|---|
| 558 | foreach($otConfig['request_attributes'] as $requestAttribute) { |
|---|
| 559 | $viewCache['request_attributes'][] = $requestAttribute + array('value' => $request->getAttribute($requestAttribute['name'], $requestAttribute['namespace'])); |
|---|
| 560 | } |
|---|
| 561 | $viewCache['request_attribute_namespaces'] = array(); |
|---|
| 562 | foreach($otConfig['request_attribute_namespaces'] as $requestAttributeNamespace) { |
|---|
| 563 | $viewCache['request_attribute_namespaces'][$requestAttributeNamespace] = $request->getAttributes($requestAttributeNamespace); |
|---|
| 564 | } |
|---|
| 565 | |
|---|
| 566 | $this->writeCache(array_merge($groups, array($outputType)), $viewCache, $config['lifetime']); |
|---|
| 567 | |
|---|
| 568 | // $lm->log('Writing View cache...'); |
|---|
| 569 | $isViewCached = true; |
|---|
| 570 | } |
|---|
| 571 | } |
|---|
| 572 | |
|---|
| 573 | // action cache writing must occur here, so actions that return AgaviView::NONE also get their cache written |
|---|
| 574 | if($isCacheable && !$isActionCached) { |
|---|
| 575 | $actionCache['action_attributes'] = array(); |
|---|
| 576 | foreach($config['action_attributes'] as $attributeName) { |
|---|
| 577 | $actionCache['action_attributes'][$attributeName] = $actionAttributes[$attributeName]; |
|---|
| 578 | } |
|---|
| 579 | |
|---|
| 580 | // $lm->log('Writing Action cache...'); |
|---|
| 581 | |
|---|
| 582 | $this->writeCache(array_merge($groups, array(self::ACTION_CACHE_ID)), $actionCache, $config[< |
|---|