Completed
Push — devel ( 130b16...5c12d3 )
by Miguel
05:50
created

getEsiLayoutBlockNode()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 8
nc 2
nop 2
1
<?php
2
3
/**
4
 * Nexcess.net Turpentine Extension for Magento
5
 * Copyright (C) 2012  Nexcess.net L.L.C.
6
 *
7
 * This program is free software; you can redistribute it and/or modify
8
 * it under the terms of the GNU General Public License as published by
9
 * the Free Software Foundation; either version 2 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License along
18
 * with this program; if not, write to the Free Software Foundation, Inc.,
19
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
 */
21
22
class Nexcessnet_Turpentine_Helper_Esi extends Mage_Core_Helper_Abstract {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
23
    const ESI_DATA_PARAM            = 'data';
24
    const ESI_TTL_PARAM             = 'ttl';
25
    const ESI_CACHE_TYPE_PARAM      = 'access';
26
    const ESI_SCOPE_PARAM           = 'scope';
27
    const ESI_METHOD_PARAM          = 'method';
28
    const ESI_HMAC_PARAM            = 'hmac';
29
    const MAGE_CACHE_NAME           = 'turpentine_esi_blocks';
30
31
    /**
32
     * Cache for layout XML
33
     *
34
     * @var Mage_Core_Model_Layout_Element|SimpleXMLElement
35
     */
36
    protected $_layoutXml = null;
37
38
    /**
39
     * Get whether ESI includes are enabled or not
40
     *
41
     * @return bool
42
     */
43
    public function getEsiEnabled() {
44
        return Mage::app()->useCache($this->getMageCacheName());
45
    }
46
47
    /**
48
     * Get if ESI should be used for this request
49
     *
50
     * @return bool
51
     */
52
    public function shouldResponseUseEsi() {
53
        return Mage::helper('turpentine/varnish')->shouldResponseUseVarnish() &&
54
            $this->getEsiEnabled();
55
    }
56
57
    /**
58
     * Check if ESI includes are enabled and throw an exception if not
59
     *
60
     * @return null
61
     */
62
    public function ensureEsiEnabled() {
63
        if ( ! $this->shouldResponseUseEsi()) {
64
            Mage::throwException('ESI includes are not enabled');
65
        }
66
    }
67
68
    /**
69
     * Get the name of the URL param that holds the ESI block hash
70
     *
71
     * @return string
72
     */
73
    public function getEsiDataParam() {
74
        return self::ESI_DATA_PARAM;
75
    }
76
77
    /**
78
     * Get the URL param name for the ESI block cache type
79
     *
80
     * @return string
81
     */
82
    public function getEsiCacheTypeParam() {
83
        return self::ESI_CACHE_TYPE_PARAM;
84
    }
85
86
    /**
87
     * Get the URL param name for the ESI block scope
88
     *
89
     * @return string
90
     */
91
    public function getEsiScopeParam() {
92
        return self::ESI_SCOPE_PARAM;
93
    }
94
95
    /**
96
     * Get the URL param name for the ESI block TTL
97
     *
98
     * @return string
99
     */
100
    public function getEsiTtlParam() {
101
        return self::ESI_TTL_PARAM;
102
    }
103
104
    /**
105
     * Get the URL param name for the ESI inclusion method
106
     *
107
     * @return string
108
     */
109
    public function getEsiMethodParam() {
110
        return self::ESI_METHOD_PARAM;
111
    }
112
113
    /**
114
     * Get the URL param name for the ESI HMAC
115
     *
116
     * @return string
117
     */
118
    public function getEsiHmacParam() {
119
        return self::ESI_HMAC_PARAM;
120
    }
121
122
    /**
123
     * Get referrer param
124
     *
125
     * @return string
126
     */
127
    public function getEsiReferrerParam() {
128
        return Mage_Core_Controller_Varien_Action::PARAM_NAME_BASE64_URL;
129
    }
130
131
    /**
132
     * Get whether ESI debugging is enabled or not
133
     *
134
     * @return bool
135
     */
136
    public function getEsiDebugEnabled() {
137
        return Mage::helper('turpentine/varnish')
138
            ->getVarnishDebugEnabled();
139
    }
140
141
    /**
142
     * Get whether block name logging is enabled or not
143
     *
144
     * @return bool
145
     */
146
    public function getEsiBlockLogEnabled() {
147
        return (bool) Mage::getStoreConfig(
148
            'turpentine_varnish/general/block_debug' );
149
    }
150
151
    /**
152
     * Check if the flash messages are enabled and we're not in the admin section
153
     *
154
     * @return bool
155
     */
156
    public function shouldFixFlashMessages() {
157
        return Mage::helper('turpentine/data')->useFlashMessagesFix() &&
158
            Mage::app()->getStore()->getCode() !== 'admin';
159
    }
160
161
    /**
162
     * Get URL for redirects and dummy requests
163
     *
164
     * @return string
165
     */
166
    public function getDummyUrl() {
167
        return Mage::getUrl('checkout/cart');
168
    }
169
170
    /**
171
     * Get mock request
172
     *
173
     * Used to pretend that the request was for the base URL instead of
174
     * turpentine/esi/getBlock while rendering ESI blocks. Not perfect, but may
175
     * be good enough
176
     *
177
     * @return Mage_Core_Controller_Request_Http
178
     */
179
    public function getDummyRequest($url = null) {
180
        if ($url === null) {
181
            $url = $this->getDummyUrl();
182
        }
183
        $request = Mage::getModel('turpentine/dummy_request', $url);
184
        $request->fakeRouterDispatch();
185
        return $request;
186
    }
187
188
    /**
189
     * Get the cache type Magento uses
190
     *
191
     * @return string
192
     */
193
    public function getMageCacheName() {
194
        return self::MAGE_CACHE_NAME;
195
    }
196
197
    /**
198
     * Get the list of cache clear events to include with every ESI block
199
     *
200
     * @return string[]
201
     */
202
    public function getDefaultCacheClearEvents() {
203
        $events = array(
204
            'customer_login',
205
            'customer_logout',
206
        );
207
        return $events;
208
    }
209
210
    /**
211
     * Get the list of events that should cause the ESI cache to be cleared
212
     *
213
     * @return string[]
214
     */
215
    public function getCacheClearEvents() {
216
        Varien_Profiler::start('turpentine::helper::esi::getCacheClearEvents');
217
        $cacheKey = $this->getCacheClearEventsCacheKey();
218
        $events = @unserialize(Mage::app()->loadCache($cacheKey));
219
        if (is_null($events) || $events === false) {
220
            $events = $this->_loadEsiCacheClearEvents();
221
            Mage::app()->saveCache(serialize($events), $cacheKey,
222
                array('LAYOUT_GENERAL_CACHE_TAG'));
223
        }
224
        Varien_Profiler::stop('turpentine::helper::esi::getCacheClearEvents');
225
        return array_merge($this->getDefaultCacheClearEvents(), $events);
226
    }
227
228
    /**
229
     * Get the default private ESI block TTL
230
     *
231
     * @return string
232
     */
233
    public function getDefaultEsiTtl() {
234
        return trim(Mage::getStoreConfig('web/cookie/cookie_lifetime'));
235
    }
236
237
    /**
238
     * Get the CORS origin field from the unsecure base URL
239
     *
240
     * If this isn't added to AJAX responses they won't load properly
241
     *
242
     * @return string
243
     */
244
    public function getCorsOrigin($url = null) {
245
        if (is_null($url)) {
246
            $baseUrl = Mage::getBaseUrl();
247
        } else {
248
            $baseUrl = $url;
249
        }
250
        $path = parse_url($baseUrl, PHP_URL_PATH);
251
        $domain = parse_url($baseUrl, PHP_URL_HOST);
252
        // there has to be a better way to just strip the path off
253
        return substr($baseUrl, 0,
254
            strpos($baseUrl, $path,
255
                strpos($baseUrl, $domain)));
256
    }
257
258
    /**
259
     * Get the layout's XML structure
260
     *
261
     * This is cached because it's expensive to load for each ESI'd block
262
     *
263
     * @return Mage_Core_Model_Layout_Element|SimpleXMLElement
264
     */
265
    public function getLayoutXml() {
266
        Varien_Profiler::start('turpentine::helper::esi::getLayoutXml');
267
        if (is_null($this->_layoutXml)) {
268
            if ($useCache = Mage::app()->useCache('layout')) {
269
                $cacheKey = $this->getFileLayoutUpdatesXmlCacheKey();
270
                $this->_layoutXml = simplexml_load_string(
271
                    Mage::app()->loadCache($cacheKey) );
272
            }
273
            // this check is redundant if the layout cache is disabled
274
            if ( ! $this->_layoutXml) {
275
                $this->_layoutXml = $this->_loadLayoutXml();
276
                if ($useCache) {
277
                    Mage::app()->saveCache($this->_layoutXml->asXML(),
278
                        $cacheKey, array('LAYOUT_GENERAL_CACHE_TAG'));
0 ignored issues
show
Bug introduced by
The variable $cacheKey does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
279
                }
280
            }
281
        }
282
        Varien_Profiler::stop('turpentine::helper::esi::getLayoutXml');
283
        return $this->_layoutXml;
284
    }
285
286
    /**
287
     * Get the cache key for the cache clear events
288
     *
289
     * @return string
290
     */
291 View Code Duplication
    public function getCacheClearEventsCacheKey() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
292
        $design = Mage::getDesign();
293
        return Mage::helper('turpentine/data')
294
            ->getCacheKeyHash(array(
295
                'FILE_LAYOUT_ESI_CACHE_EVENTS',
296
                $design->getArea(),
297
                $design->getPackageName(),
298
                $design->getTheme('layout'),
299
                Mage::app()->getStore()->getId(),
300
            ));
301
    }
302
303
    /**
304
     * Get the cache key for the file layouts xml
305
     *
306
     * @return string
307
     */
308 View Code Duplication
    public function getFileLayoutUpdatesXmlCacheKey() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
309
        $design = Mage::getDesign();
310
        return Mage::helper('turpentine/data')
311
            ->getCacheKeyHash(array(
312
                'FILE_LAYOUT_UPDATES_XML',
313
                $design->getArea(),
314
                $design->getPackageName(),
315
                $design->getTheme('layout'),
316
                Mage::app()->getStore()->getId(),
317
            ));
318
    }
319
320
    /**
321
     * Generate an ESI tag to be replaced by the content from the given URL
322
     *
323
     * Generated tag looks like:
324
     *     <esi:include src="$url" />
325
     *
326
     * @param  string $url url to pull content from
327
     * @return string
328
     */
329
    public function buildEsiIncludeFragment($url) {
330
        return sprintf('<esi:include src="%s" />', $url);
331
    }
332
333
    /**
334
     * Generate an ESI tag with content that is removed when ESI processed, and
335
     * visible when not
336
     *
337
     * Generated tag looks like:
338
     *     <esi:remove>$content</esi>
339
     *
340
     * @param  string $content content to be removed
341
     * @return string
342
     */
343
    public function buildEsiRemoveFragment($content) {
344
        return sprintf('<esi:remove>%s</esi>', $content);
345
    }
346
347
    /**
348
     * Get URL for grabbing form key via ESI
349
     *
350
     * @return string
351
     */
352
    public function getFormKeyEsiUrl() {
353
        $urlOptions = array(
354
            $this->getEsiTtlParam()         => $this->getDefaultEsiTtl(),
355
            $this->getEsiMethodParam()      => 'esi',
356
            $this->getEsiScopeParam()       => 'global',
357
            $this->getEsiCacheTypeParam()   => 'private',
358
        );
359
        $esiUrl = Mage::getUrl('turpentine/esi/getFormKey', $urlOptions);
360
        // setting [web/unsecure/base_url] can be https://... but ESI can never be HTTPS
361
        $esiUrl = preg_replace('|^https://|i', 'http://', $esiUrl);
362
        return $esiUrl;
363
    }
364
365
    /**
366
     * Grab a block node by name from the layout XML.
367
     *
368
     * Multiple blocks with the same name may exist in the layout, because some themes
369
     * use 'unsetChild' to remove a block and create it with the same name somewhere
370
     * else. For example Ultimo does this.
371
     *
372
     * @param Mage_Core_Model_Layout $layout
373
     * @param string $blockName value of name= attribute in layout XML
374
     * @return Mage_Core_Model_Layout_Element
375
     */
376
    public function getEsiLayoutBlockNode($layout,$blockName) {
377
        // first try very specific by checking for action setEsiOptions inside block
378
        $blockNode = current($layout->getNode()->xpath(
379
            sprintf('//block[@name=\'%s\'][action[@method=\'setEsiOptions\']]',
380
                $blockName)
381
        ));
382
        // fallback: only match name
383
        if ( ! ($blockNode instanceof Mage_Core_Model_Layout_Element)) {
0 ignored issues
show
Bug introduced by
The class Mage_Core_Model_Layout_Element does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
384
            $blockNode = current($layout->getNode()->xpath(
385
                sprintf('//block[@name=\'%s\']', $blockName)
386
            ));
387
        }
388
        return $blockNode;
389
    }
390
391
    /**
392
     * Load the ESI cache clear events from the layout
393
     *
394
     * @return array
395
     */
396
    protected function _loadEsiCacheClearEvents() {
397
        Varien_Profiler::start('turpentine::helper::esi::_loadEsiCacheClearEvents');
398
        $layoutXml = $this->getLayoutXml();
399
        $events = $layoutXml->xpath(
400
            '//action[@method=\'setEsiOptions\']/params/flush_events/*' );
401
        if ($events) {
402
            $events = array_unique(array_map(
403
                create_function('$e',
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
404
                    'return (string)$e->getName();'),
405
                $events ));
406
        } else {
407
            $events = array();
408
        }
409
        Varien_Profiler::stop('turpentine::helper::esi::_loadEsiCacheClearEvents');
410
        return $events;
411
    }
412
413
    /**
414
     * Load the layout's XML structure, bypassing any caching
415
     *
416
     * @return Mage_Core_Model_Layout_Element
417
     */
418
    protected function _loadLayoutXml() {
419
        Varien_Profiler::start('turpentine::helper::esi::_loadLayoutXml');
420
        $design = Mage::getDesign();
421
        $layoutXml = Mage::getSingleton('core/layout')
422
            ->getUpdate()
423
            ->getFileLayoutUpdatesXml(
424
                $design->getArea(),
425
                $design->getPackageName(),
426
                $design->getTheme('layout'),
427
                Mage::app()->getStore()->getId() );
428
        Varien_Profiler::stop('turpentine::helper::esi::_loadLayoutXml');
429
        return $layoutXml;
430
    }
431
}
432