/** * Generate the ESI block * * @param Varien_Object $esiData * @return Mage_Core_Block_Template|null */ protected function _getEsiBlock($esiData) { $block = null; Varien_Profiler::start('turpentine::controller::esi::_getEsiBlock'); foreach ($esiData->getSimpleRegistry() as $key => $value) { Mage::register($key, $value, true); } foreach ($esiData->getComplexRegistry() as $key => $data) { $value = Mage::getModel($data['model']); if (!is_object($value)) { Mage::helper('turpentine/debug')->logWarn('Failed to register key/model: %s as %s(%s)', $key, $data['model'], $data['id']); continue; } else { $value->load($data['id']); Mage::register($key, $value, true); } } $layout = Mage::getSingleton('core/layout'); // dispatch event for adding handles to layout update Mage::dispatchEvent('controller_action_layout_load_before', array('action' => $this, 'layout' => $layout)); $layoutUpdate = $layout->getUpdate(); $layoutUpdate->load($this->_swapCustomerHandles($esiData->getLayoutHandles())); foreach ($esiData->getDummyBlocks() as $blockName) { $layout->createBlock('Mage_Core_Block_Template', $blockName); } if (!$this->getFlag('', self::FLAG_NO_DISPATCH_BLOCK_EVENT)) { Mage::dispatchEvent('controller_action_layout_generate_xml_before', array('action' => $this, 'layout' => $layout)); } $layout->generateXml(); /** @var Nexcessnet_Turpentine_Helper_Data $turpentineHelper */ $turpentineHelper = Mage::helper('turpentine/data')->setLayout($layout); $blockNode = current($layout->getNode()->xpath(sprintf('//block[@name=\'%s\']', $esiData->getNameInLayout()))); if (!$blockNode instanceof Mage_Core_Model_Layout_Element) { Mage::helper('turpentine/debug')->logWarn('No block node found with @name="%s"', $esiData->getNameInLayout()); return null; } $nodesToGenerate = $turpentineHelper->getChildBlockNames($blockNode); Mage::getModel('turpentine/shim_mage_core_layout')->shim_generateFullBlock($blockNode); //find addional blocks that aren't defined in the <block/> but via <reference name="%s"> $referenceNodes = $layout->getNode()->xpath(sprintf('//reference[@name=\'%s\']', $esiData->getNameInLayout())); if ($referenceNodes) { foreach ($referenceNodes as $referenceNode) { if ($referenceNode instanceof Mage_Core_Model_Layout_Element) { $referencesToGenerate = $turpentineHelper->getChildBlockNames($referenceNode); $nodesToGenerate = array_merge($nodesToGenerate, $referencesToGenerate); } } } // dispatch event for adding xml layout elements if (!$this->getFlag('', self::FLAG_NO_DISPATCH_BLOCK_EVENT)) { Mage::dispatchEvent('controller_action_layout_generate_blocks_before', array('action' => $this, 'layout' => $layout)); } foreach (array_unique($nodesToGenerate) as $nodeName) { foreach ($layout->getNode()->xpath(sprintf('//reference[@name=\'%s\']', $nodeName)) as $node) { $layout->generateBlocks($node); } } $block = $layout->getBlock($esiData->getNameInLayout()); if (!$this->getFlag('', self::FLAG_NO_DISPATCH_BLOCK_EVENT)) { Mage::dispatchEvent('controller_action_layout_generate_blocks_after', array('action' => $this, 'layout' => $layout)); } $this->_isLayoutLoaded = true; Varien_Profiler::stop('turpentine::controller::esi::_getEsiBlock'); return $block; }
protected function _watch($observer) { $block = $observer->getBlock(); if ($this->_isUnwantedBlock($block)) { return; } $style = ' [style=solid]'; $parent = $block->getParentBlock(); $name_block = $block->getNameInLayout(); $name_parent = 'unknown'; $this->_checkAssumptions($parent, $block); if ($this->_isParentlessAnonymous($parent, $block)) { $style = ' [style=dashed]'; $parent = new Varien_Object(); $parent->setNameInLayout($this->_getAssumedParentNameFromStack()); $name_parent = $parent->getNameInLayout(); } else { if ($this->_isAssumeParentAndRealParentMismatch($parent, $block)) { $style = ' [style=dotted]'; $parent = new Varien_Object(); $parent->setNameInLayout($this->_getAssumedParentNameFromStack()); $name_parent = $parent->getNameInLayout(); } else { if ($this->_isGoofyFacade($parent, $block)) { $style = ' [style=dotted]'; $parent = new Varien_Object(); $parent->setNameInLayout($this->_getAssumedParentNameFromStack()); $name_parent = $parent->getNameInLayout(); } else { if (is_object($parent)) { $name_parent = $parent->getNameInLayout(); } } } } if (!$parent) { self::$_graphs[] = '"' . $block->getNameInLayout() . '"' . $style; } else { self::$_graphs[] = '"' . $parent->getNameInLayout() . '"' . '->' . '"' . $block->getNameInLayout() . '"' . $style; } $template = $block->getTemplate() ? $block->getTemplate() : 'NO TEMPLATE'; $definition = '"' . $name_block . '"' . '[label="' . $name_block . '\\\\n' . get_class($block) . '\\\\n' . $template . '"]'; self::$_definitions[$name_block] = $definition; }
/** * Generate the ESI block * * @param Varien_Object $esiData * @return Mage_Core_Block_Template */ protected function _getEsiBlock($esiData) { $block = null; Varien_Profiler::start('turpentine::controller::esi::_getEsiBlock'); foreach ($esiData->getSimpleRegistry() as $key => $value) { Mage::register($key, $value, true); } foreach ($esiData->getComplexRegistry() as $key => $data) { $value = Mage::getModel($data['model']); if (!is_object($value)) { Mage::helper('turpentine/debug')->logWarn('Failed to register key/model: %s as %s(%s)', $key, $data['model'], $data['id']); continue; } else { $value->load($data['id']); Mage::register($key, $value, true); } } $layout = Mage::getSingleton('core/layout'); Mage::getSingleton('core/design_package')->setPackageName($esiData->getDesignPackage())->setTheme($esiData->getDesignTheme()); $layoutUpdate = $layout->getUpdate(); $layoutUpdate->load($this->_swapCustomerHandles($esiData->getLayoutHandles())); foreach ($esiData->getDummyBlocks() as $blockName) { $layout->createBlock('Mage_Core_Block_Template', $blockName); } $layout->generateXml(); $blockNode = current($layout->getNode()->xpath(sprintf('//block[@name=\'%s\']', $esiData->getNameInLayout()))); if ($blockNode instanceof Varien_Simplexml_Element) { $nodesToGenerate = Mage::helper('turpentine/data')->setLayout($layout)->getChildBlockNames($blockNode); Mage::getModel('turpentine/shim_mage_core_layout')->shim_generateFullBlock($blockNode); foreach ($nodesToGenerate as $nodeName) { foreach ($layout->getNode()->xpath(sprintf('//reference[@name=\'%s\']', $nodeName)) as $node) { $layout->generateBlocks($node); } } $block = $layout->getBlock($esiData->getNameInLayout()); } else { Mage::helper('turpentine/debug')->logWarn('No block node found with @name="%s"', $esiData->getNameInLayout()); } Varien_Profiler::stop('turpentine::controller::esi::_getEsiBlock'); return $block; }
/** * Called on * core_block_abstract_to_html_before * * Checks if the "esi" variable is set on a block. * If yes, the template of the block is replaced by varnish/esi.phtml which contains the <esi:include> tag * it also adds to the response the header X-magento-doesi which will be interpreted by varnish and tell it to do the esi processing * * @param array $eventObject */ public function injectEsi($eventObject) { //No ESI injection if the module is disabled or the request is made on HTTPS if (!Mage::helper('varnish')->isVarnishModuleEnabled() || Mage::app()->getRequest()->isSecure()) { return; } $block = $eventObject->getBlock(); if ($block instanceof Mage_Core_Block_Template) { $esi = $block->getEsi(); if ($esi == true) { //We don't allow ESI in admin, for now. Maybe in future releases. if (Mage::app()->getStore()->getCode() == 'admin') { throw new Mage_Adminhtml_Exception("ESI includes are forbidden in Admin"); } // We replace the template of the block by the varnish/esi.phtml template // The HTML of our template will replace the real HTML of the block $block->setTemplate('varnish/esi.phtml'); $src = new Varien_Object(); //Blocks change depending on the store id, so we keep track of that $src->setStoreId(Mage::app()->getStore()->getId()); //Blocks also change depending on the design so we keep track of the package and the theme of the current block $src->setDesignPackage(Mage::getDesign()->getPackageName()); $src->setDesignTheme(Mage::getDesign()->getTheme('layout')); $src->setNameInLayout($block->getNameInLayout()); /* * Set the cache type * per-client * per-page * global * * * per-client tells varnish to cache the block per-client basis. * per-page tells varnish to cache the content based on the url of the page. * global tells varnish to serve the same cached version to every client. * * The per-client cache is based on the frontend cookie of the client. * * We default the cache type to "global" */ if (empty($esi['cache_type'])) { $esi['cache_type'] = 'global'; } $src->setCacheType($esi['cache_type']); /** * If the block is cached on a per-page basis * we create an entry in our ESI table to keep track the URLs where the block appear * */ if ($src->getCacheType() === 'per-page') { $parentUrl = Mage::app()->getRequest()->getRequestString(); $src->setParentUrl($parentUrl); } /** * Expiry (or TTL in Varnish lingo). How lon will the object be stored in Varnish? * TODO: make sure the expiry is in format 1d 24h 1140m 86400s */ if (!empty($esi['expiry'])) { $src->setExpiry($esi['expiry']); } else { if ($src->getCacheType() === 'per-client') { $src->setExpiry(Mage::getStoreConfig('varnish/cache/per_client_default_expiry')); } else { if ($src->getCacheType() === 'per-page') { $src->setExpiry(Mage::getStoreConfig('varnish/cache/per_page_default_expiry')); } else { $src->setExpiry(Mage::getStoreConfig('varnish/cache/global_default_expiry')); } } } //We create a unique fingerprint with all our values foreach ($src->getData() as $value) { $this->_hash($value); } // $src is the source for our <esi:include> it is composed of all the above variables $src->setUrl("/varnish/cache/getBlock/cachetype/{$src->getCacheType()}/expiry/{$src->getExpiry()}/fingerprint/{$this->_hash()}"); /** * Registry save: * some block rely on values stored in the Mage::registry(). * For example, the product page relies on Mage::registry('current_product'); * The problem is that the Mage::registry is not persistent between requests. This means that once the request is served, * the registry looses its data. * This means that when Varnish makes the ESI request, the registry is empty and if the block makes a call to Mage::registry('current_product') * the function will return null. * In order to strive this problem, when you setEsi on a block using the mage registry, you need to specify in the layout which keys you want to keep. * These keys will be saved in the magento cache and thus be accessible when the ESI request is made */ if (!empty($esi['registry_keys'])) { // We create an array with the <registry_keys></registry_keys> set in the layout $registryKeys = explode(',', $esi['registry_keys']); // We iterate through each of the registrykey... foreach ($registryKeys as $registryKey) { $registryContent = Mage::registry($registryKey); if ($registryContent !== null) { // If the key exist we save the content in an array $registry[] = array("key" => $registryKey, "content" => $registryContent); } } $src->setRegistry($registry); } $src->setBlockType(get_class($block)); // and we save the content in the cache with the hash as the id $cache = Mage::helper('varnish')->getRedisCache(); $tags = array("VARNISH_CACHETYPE_{$src->getCacheType()}", "VARNISH_BLOCKTYPE_{$src->getBlockType()}", "VARNISH_BLOCKNAME_{$src->getNameInLayout()}"); $cache->save(serialize($src), $this->_hash(), $tags, null); $block->setSrc($src); // Tell varnish to do esi processing on the page Mage::getSingleton('varnish/cache')->setDoEsi(true); } } }