Nexcessnet_Turpentine_Helper_Esi::getCorsOrigin()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 9.8333
c 0
b 0
f 0
cc 2
nc 2
nop 1
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 {
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
        $defaultLifeTime = trim(Mage::getStoreConfig('web/cookie/cookie_lifetime'));
235
        if ($defaultLifeTime < 60) {
236
            $defaultLifeTime = ini_get('session.gc_maxlifetime');
237
        }
238
        return $defaultLifeTime;
239
    }
240
241
    /**
242
     * Get the CORS origin field from the unsecure base URL
243
     *
244
     * If this isn't added to AJAX responses they won't load properly
245
     *
246
     * @return string
247
     */
248
    public function getCorsOrigin($url = null) {
249
        if (is_null($url)) {
250
            $baseUrl = Mage::getBaseUrl();
251
        } else {
252
            $baseUrl = $url;
253
        }
254
        $path = parse_url($baseUrl, PHP_URL_PATH);
255
        $domain = parse_url($baseUrl, PHP_URL_HOST);
256
        // there has to be a better way to just strip the path off
257
        return substr($baseUrl, 0,
258
            strpos($baseUrl, $path,
259
                strpos($baseUrl, $domain)));
260
    }
261
262
    /**
263
     * Get the layout's XML structure
264
     *
265
     * This is cached because it's expensive to load for each ESI'd block
266
     *
267
     * @return Mage_Core_Model_Layout_Element|SimpleXMLElement
268
     */
269
    public function getLayoutXml() {
270
        Varien_Profiler::start('turpentine::helper::esi::getLayoutXml');
271
        if (is_null($this->_layoutXml)) {
272
            if ($useCache = Mage::app()->useCache('layout')) {
273
                $cacheKey = $this->getFileLayoutUpdatesXmlCacheKey();
274
                $this->_layoutXml = simplexml_load_string(
275
                    Mage::app()->loadCache($cacheKey) );
276
            }
277
            // this check is redundant if the layout cache is disabled
278
            if ( ! $this->_layoutXml) {
279
                $this->_layoutXml = $this->_loadLayoutXml();
280
                if ($useCache) {
281
                    Mage::app()->saveCache($this->_layoutXml->asXML(),
282
                        $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...
283
                }
284
            }
285
        }
286
        Varien_Profiler::stop('turpentine::helper::esi::getLayoutXml');
287
        return $this->_layoutXml;
288
    }
289
290
    /**
291
     * Get the cache key for the cache clear events
292
     *
293
     * @return string
294
     */
295 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...
296
        $design = Mage::getDesign();
297
        return Mage::helper('turpentine/data')
298
            ->getCacheKeyHash(array(
299
                'FILE_LAYOUT_ESI_CACHE_EVENTS',
300
                $design->getArea(),
301
                $design->getPackageName(),
302
                $design->getTheme('layout'),
303
                Mage::app()->getStore()->getId(),
304
            ));
305
    }
306
307
    /**
308
     * Get the cache key for the file layouts xml
309
     *
310
     * @return string
311
     */
312 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...
313
        $design = Mage::getDesign();
314
        return Mage::helper('turpentine/data')
315
            ->getCacheKeyHash(array(
316
                'FILE_LAYOUT_UPDATES_XML',
317
                $design->getArea(),
318
                $design->getPackageName(),
319
                $design->getTheme('layout'),
320
                Mage::app()->getStore()->getId(),
321
            ));
322
    }
323
324
    /**
325
     * Generate an ESI tag to be replaced by the content from the given URL
326
     *
327
     * Generated tag looks like:
328
     *     <esi:include src="$url" />
329
     *
330
     * @param  string $url url to pull content from
331
     * @return string
332
     */
333
    public function buildEsiIncludeFragment($url) {
334
        return sprintf('<esi:include src="%s" />', $url);
335
    }
336
337
    /**
338
     * Generate an ESI tag with content that is removed when ESI processed, and
339
     * visible when not
340
     *
341
     * Generated tag looks like:
342
     *     <esi:remove>$content</esi>
343
     *
344
     * @param  string $content content to be removed
345
     * @return string
346
     */
347
    public function buildEsiRemoveFragment($content) {
348
        return sprintf('<esi:remove>%s</esi>', $content);
349
    }
350
351
    /**
352
     * Get URL for grabbing form key via ESI
353
     *
354
     * @return string
355
     */
356
    public function getFormKeyEsiUrl() {
357
        $urlOptions = array(
358
            $this->getEsiTtlParam()         => $this->getDefaultEsiTtl(),
359
            $this->getEsiMethodParam()      => 'esi',
360
            $this->getEsiScopeParam()       => 'global',
361
            $this->getEsiCacheTypeParam()   => 'private',
362
        );
363
        $esiUrl = Mage::getUrl('turpentine/esi/getFormKey', $urlOptions);
364
        // setting [web/unsecure/base_url] can be https://... but ESI can never be HTTPS
365
        $esiUrl = preg_replace('|^https://|i', 'http://', $esiUrl);
366
        return $esiUrl;
367
    }
368
369
    /**
370
     * Grab a block node by name from the layout XML.
371
     *
372
     * Multiple blocks with the same name may exist in the layout, because some themes
373
     * use 'unsetChild' to remove a block and create it with the same name somewhere
374
     * else. For example Ultimo does this.
375
     *
376
     * @param Mage_Core_Model_Layout $layout
377
     * @param string $blockName value of name= attribute in layout XML
378
     * @return Mage_Core_Model_Layout_Element
379
     */
380
    public function getEsiLayoutBlockNode($layout, $blockName) {
381
        // first try very specific by checking for action setEsiOptions inside block
382
        $blockNodes = $layout->getNode()->xpath(
383
            sprintf('//block[@name=\'%s\'][action[@method=\'setEsiOptions\']]',
384
                $blockName)
385
        );
386
        $blockNode = end($blockNodes);
387
        // fallback: only match name
388
        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...
389
            $blockNodes = $layout->getNode()->xpath(
390
                sprintf('//block[@name=\'%s\']', $blockName)
391
            );
392
            $blockNode = end($blockNodes);
393
        }
394
        return $blockNode;
395
    }
396
397
    /**
398
     * Load the ESI cache clear events from the layout
399
     *
400
     * @return array
401
     */
402
    protected function _loadEsiCacheClearEvents() {
403
        Varien_Profiler::start('turpentine::helper::esi::_loadEsiCacheClearEvents');
404
        $layoutXml = $this->getLayoutXml();
405
        $events = $layoutXml->xpath(
406
            '//action[@method=\'setEsiOptions\']/params/flush_events/*' );
407
        if ($events) {
408
            $events = array_unique(array_map(
409
                create_function('$e',
410
                    'return (string)$e->getName();'),
411
                $events ));
412
        } else {
413
            $events = array();
414
        }
415
        Varien_Profiler::stop('turpentine::helper::esi::_loadEsiCacheClearEvents');
416
        return $events;
417
    }
418
419
    /**
420
     * Load the layout's XML structure, bypassing any caching
421
     *
422
     * @return Mage_Core_Model_Layout_Element
423
     */
424
    protected function _loadLayoutXml() {
425
        Varien_Profiler::start('turpentine::helper::esi::_loadLayoutXml');
426
        $design = Mage::getDesign();
427
        $layoutXml = Mage::getSingleton('core/layout')
428
            ->getUpdate()
429
            ->getFileLayoutUpdatesXml(
430
                $design->getArea(),
431
                $design->getPackageName(),
432
                $design->getTheme('layout'),
433
                Mage::app()->getStore()->getId() );
434
        Varien_Profiler::stop('turpentine::helper::esi::_loadLayoutXml');
435
        return $layoutXml;
436
    }
437
}
438