Abstract::save()   A
last analyzed

Complexity

Conditions 4
Paths 5

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
rs 9.7
c 0
b 0
f 0
cc 4
nc 5
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
abstract class Nexcessnet_Turpentine_Model_Varnish_Configurator_Abstract {
23
24
    const VCL_CUSTOM_C_CODE_FILE = 'uuid.c';
25
26
    /**
27
     * Get the correct version of a configurator from a socket
28
     *
29
     * @param  Nexcessnet_Turpentine_Model_Varnish_Admin_Socket $socket
30
     * @return Nexcessnet_Turpentine_Model_Varnish_Configurator_Abstract
31
     */
32
    static public function getFromSocket($socket) {
33
        try {
34
            $version = $socket->getVersion();
35
        } catch (Mage_Core_Exception $e) {
36
            Mage::getSingleton('core/session')
37
                ->addError('Error determining Varnish version: '.
38
                    $e->getMessage());
39
            return null;
40
        }
41
        switch ($version) {
42
            case '4.0':
43
            case '4.1':
44
                return Mage::getModel(
45
                    'turpentine/varnish_configurator_version4',
46
                    array('socket' => $socket) );
47
48
            case '3.0':
49
                return Mage::getModel(
50
                    'turpentine/varnish_configurator_version3',
51
                        array('socket' => $socket) );
52
            case '2.1':
53
                return Mage::getModel(
54
                    'turpentine/varnish_configurator_version2',
55
                        array('socket' => $socket) );
56
            default:
57
                Mage::throwException('Unsupported Varnish version');
58
        }
59
    }
60
61
    /**
62
     * The socket this configurator is based on
63
     *
64
     * @var Nexcessnet_Turpentine_Model_Varnish_Admin_Socket
65
     */
66
    protected $_socket = null;
67
    /**
68
     * options array
69
     *
70
     * @var array
71
     */
72
    protected $_options = array(
73
        'vcl_template'  => null,
74
    );
75
76
    public function __construct($options = array()) {
77
        $this->_options = array_merge($this->_options, $options);
78
    }
79
80
    abstract public function generate($doClean = true);
81
    // abstract protected function _getTemplateVars();
82
83
    /**
84
     * Save the generated config to the file specified in Magento config
85
     *
86
     * @param  string $generatedConfig config generated by @generate
87
     * @return null
88
     */
89
    public function save($generatedConfig) {
90
        $filename = $this->_getVclFilename();
91
        $dir = dirname($filename);
92
        if ( ! is_dir($dir)) {
93
            // this umask is probably redundant, but just in case...
94
            if ( ! mkdir($dir, 0777 & ~umask(), true)) {
95
                $err = error_get_last();
96
                return array(false, $err);
97
            }
98
        }
99
        if (strlen($generatedConfig) !==
100
                file_put_contents($filename, $generatedConfig)) {
101
            $err = error_get_last();
102
            return array(false, $err);
103
        }
104
        return array(true, null);
105
    }
106
107
    /**
108
     * Get the full path for a given template filename
109
     *
110
     * @param  string $baseFilename
111
     * @return string
112
     */
113
    protected function _getVclTemplateFilename($baseFilename) {
114
            $extensionDir = Mage::getModuleDir('', 'Nexcessnet_Turpentine');
115
            return sprintf('%s/misc/%s', $extensionDir, $baseFilename);
116
    }
117
118
    /**
119
     * Get the name of the file to save the VCL to
120
     *
121
     * @return string
122
     */
123
    protected function _getVclFilename() {
124
        return $this->_formatTemplate(
125
            Mage::getStoreConfig('turpentine_varnish/servers/config_file'),
126
            array('root_dir' => Mage::getBaseDir()) );
127
    }
128
129
    /**
130
     * Get the name of the custom include VCL file
131
     *
132
     * @return string
133
     */
134
    protected function _getCustomIncludeFilename($position = '') {
135
        $key = 'custom_include_file';
136
        $key .= ($position) ? '_'.$position : '';
137
        return $this->_formatTemplate(
138
            Mage::getStoreConfig('turpentine_varnish/servers/'.$key),
139
            array('root_dir' => Mage::getBaseDir()) );
140
    }
141
142
143
    /**
144
     * Get the custom VCL template, if it exists
145
     * Returns 'null' if the file doesn't exist
146
     *
147
     * @return string
148
     */
149
    protected function _getCustomTemplateFilename() {
150
        $filePath = $this->_formatTemplate(
151
            Mage::getStoreConfig('turpentine_varnish/servers/custom_vcl_template'),
152
            array('root_dir' => Mage::getBaseDir())
153
        );
154
        if (is_file($filePath)) { return $filePath; } else { return null; }
155
    }
156
157
158
    /**
159
     * Format a template string, replacing {{keys}} with the appropriate values
160
     * and remove unspecified keys
161
     *
162
     * @param  string $template template string to operate on
163
     * @param  array  $vars     array of key => value replacements
164
     * @return string
165
     */
166
    protected function _formatTemplate($template, array $vars) {
167
        $needles = array_map(create_function('$k', 'return "{{".$k."}}";'),
168
            array_keys($vars));
169
        $replacements = array_values($vars);
170
        // do replacements, then delete unused template vars
171
        return preg_replace('~{{[^}]+}}~', '',
172
            str_replace($needles, $replacements, $template));
173
    }
174
175
    /**
176
     * Format a VCL subroutine call
177
     *
178
     * @param  string $subroutine subroutine name
179
     * @return string
180
     */
181
    protected function _vcl_call($subroutine) {
182
        return sprintf('call %s;', $subroutine);
183
    }
184
185
    /**
186
     * Get the Magento admin frontname
187
     *
188
     * This is just the plain string, not in URL format. ex:
189
     * http://example.com/magento/admin -> admin
190
     *
191
     * @return string
192
     */
193
    protected function _getAdminFrontname() {
194
        if (Mage::getStoreConfig('admin/url/use_custom_path')) {
195
            if (Mage::getStoreConfig('web/url/use_store')) {
196
                return Mage::getModel('core/store')->load(0)->getCode()."/".Mage::getStoreConfig('admin/url/custom_path');
197
            } else {
198
                return Mage::getStoreConfig('admin/url/custom_path');
199
            }
200
        } else {
201
            return (string) Mage::getConfig()->getNode(
202
                'admin/routers/adminhtml/args/frontName' );
203
        }
204
    }
205
206
    /**
207
     * Get the hostname for host normalization from Magento's base URL
208
     *
209
     * @return string
210
     */
211
    protected function _getNormalizeHostTarget() {
212
        $configHost = trim(Mage::getStoreConfig(
213
            'turpentine_vcl/normalization/host_target' ));
214
        if ($configHost) {
215
            return $configHost;
216
        } else {
217
            $baseUrl = parse_url(Mage::getBaseUrl());
218
            if (isset($baseUrl['port'])) {
219
                return sprintf('%s:%d', $baseUrl['host'], $baseUrl['port']);
220
            } else {
221
                return $baseUrl['host'];
222
            }
223
        }
224
    }
225
226
    /**
227
     * Get hosts as regex
228
     *
229
     * ex: base_url: example.com
230
     *     path_regex: (example.com|example.net)
231
     *
232
     * @return string
233
     */
234
    public function getAllowedHostsRegex() {
235
        $hosts = array();
236
        foreach (Mage::app()->getStores() as $store) {
237
            $hosts[] = parse_url($store->getBaseUrl(Mage_Core_Model_Store::URL_TYPE_WEB, false), PHP_URL_HOST);
238
        }
239
240
        $hosts = array_values(array_unique($hosts));
241
242
        $pattern = '('.implode('|', array_map("preg_quote", $hosts)).')';
243
        return $pattern;
244
    }
245
246
    /**
247
     * Get the Host normalization sub routine
248
     *
249
     * @return string
250
     */
251
    protected function _vcl_sub_allowed_hosts_regex() {
252
        $tpl = <<<EOS
253
# if host is not allowed in magento pass to backend
254
        if (req.http.host !~ "{{allowed_hosts_regex}}") {
255
            return (pass);
256
        }
257
EOS;
258
        return $this->_formatTemplate($tpl, array(
259
            'allowed_hosts_regex' => $this->getAllowedHostsRegex() ));
260
    }
261
262
    /**
263
     * Get the base url path regex
264
     *
265
     * ex: base_url: http://example.com/magento/
266
     *     path_regex: /magento/(?:(?:index|litespeed)\.php/)?
267
     *
268
     * @return string
269
     */
270
    public function getBaseUrlPathRegex() {
271
        $pattern = '^(%s)(?:(?:index|litespeed)\\.php/)?';
272
        return sprintf($pattern, implode('|',
273
            array_map(create_function('$x', 'return preg_quote($x,"|");'),
274
                $this->_getBaseUrlPaths())));
275
    }
276
277
    /**
278
     * Get the path part of each store's base URL and static file URLs
279
     *
280
     * @return array
281
     */
282
    protected function _getBaseUrlPaths() {
283
        $paths = array();
284
        $linkTypes = array(Mage_Core_Model_Store::URL_TYPE_LINK,
285
                            Mage_Core_Model_Store::URL_TYPE_JS,
286
                            Mage_Core_Model_Store::URL_TYPE_SKIN,
287
                            Mage_Core_Model_Store::URL_TYPE_MEDIA);
288
        foreach (Mage::app()->getStores() as $store) {
289
            foreach ($linkTypes as $linkType) {
290
                $paths[] = parse_url($store->getBaseUrl($linkType, false),
291
                    PHP_URL_PATH);
292
                $paths[] = parse_url($store->getBaseUrl($linkType, true),
293
                    PHP_URL_PATH);
294
            }
295
        }
296
        $paths = array_unique($paths);
297
        usort($paths, create_function('$a, $b',
298
            'return strlen( $b ) - strlen( $a );'));
299
        return array_values($paths);
300
    }
301
302
    /**
303
     * Format the URL exclusions for insertion in a regex. Admin frontname and
304
     * API are automatically added.
305
     *
306
     * @return string
307
     */
308
    protected function _getUrlExcludes() {
309
        $urls = Mage::getStoreConfig('turpentine_vcl/urls/url_blacklist');
310
        return implode('|', array_merge(array($this->_getAdminFrontname(), 'api'),
311
            Mage::helper('turpentine/data')->cleanExplode(PHP_EOL, $urls)));
312
    }
313
314
    /**
315
     * Get the default cache TTL from Magento config
316
     *
317
     * @return string
318
     */
319
    protected function _getDefaultTtl() {
320
        return Mage::helper('turpentine/varnish')->getDefaultTtl();
321
    }
322
323
    /**
324
     * Get the default backend configuration string
325
     *
326
     * @return string
327
     */
328 View Code Duplication
    protected function _getDefaultBackend() {
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...
329
        $timeout = Mage::getStoreConfig('turpentine_vcl/backend/frontend_timeout');
330
        $default_options = array(
331
            'first_byte_timeout'    => $timeout.'s',
332
            'between_bytes_timeout' => $timeout.'s',
333
        );
334
        if (Mage::getStoreConfig('turpentine_vcl/backend/load_balancing') != 'no') {
335
            return $this->_vcl_director('default', $default_options);
336
        } else {
337
            return $this->_vcl_backend('default',
338
                Mage::getStoreConfig('turpentine_vcl/backend/backend_host'),
339
                Mage::getStoreConfig('turpentine_vcl/backend/backend_port'),
340
                $default_options);
341
        }
342
    }
343
344
    /**
345
     * Get the admin backend configuration string
346
     *
347
     * @return string
348
     */
349 View Code Duplication
    protected function _getAdminBackend() {
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...
350
        $timeout = Mage::getStoreConfig('turpentine_vcl/backend/admin_timeout');
351
        $admin_options = array(
352
            'first_byte_timeout'    => $timeout.'s',
353
            'between_bytes_timeout' => $timeout.'s',
354
        );
355
        if (Mage::getStoreConfig('turpentine_vcl/backend/load_balancing') != 'no') {
356
            return $this->_vcl_director('admin', $admin_options);
357
        } else {
358
            return $this->_vcl_backend('admin',
359
                Mage::getStoreConfig('turpentine_vcl/backend/backend_host'),
360
                Mage::getStoreConfig('turpentine_vcl/backend/backend_port'),
361
                $admin_options);
362
        }
363
    }
364
365
    /**
366
     * Get the grace period for vcl_fetch
367
     *
368
     * This is curently hardcoded to 15 seconds, will be configurable at some
369
     * point
370
     *
371
     * @return string
372
     */
373
    protected function _getGracePeriod() {
374
        return Mage::getStoreConfig('turpentine_vcl/ttls/grace_period');
375
    }
376
377
    /**
378
     * Get whether debug headers should be enabled or not
379
     *
380
     * @return string
381
     */
382
    protected function _getEnableDebugHeaders() {
383
        return Mage::getStoreConfig('turpentine_varnish/general/varnish_debug')
384
            ? 'true' : 'false';
385
    }
386
387
    /**
388
     * Format the GET variable excludes for insertion in a regex
389
     *
390
     * @return string
391
     */
392
    protected function _getGetParamExcludes() {
393
        return implode('|', Mage::helper('turpentine/data')->cleanExplode(',',
394
            Mage::getStoreConfig('turpentine_vcl/params/get_params')));
395
    }
396
397
    protected function _getIgnoreGetParameters()
398
    {
399
        /** @var Nexcessnet_Turpentine_Helper_Data $helper */
400
        $helper = Mage::helper('turpentine');
401
        $ignoredParameters = $helper->cleanExplode(',', Mage::getStoreConfig('turpentine_vcl/params/ignore_get_params'));
402
        return implode('|', $ignoredParameters);
403
    }
404
405
    /**
406
     * @return boolean
407
     */
408
    protected function _sendUnModifiedUrlToBackend()
409
    {
410
        return Mage::getStoreConfigFlag('turpentine_vcl/params/transfer_unmodified_url');
411
    }
412
413
    /**
414
     * Get the Generate Session
415
     *
416
     * @return string
417
     */
418
    protected function _getGenerateSessionStart() {
419
        return Mage::getStoreConfig('turpentine_varnish/general/vcl_fix')
420
            ? '/* -- REMOVED' : '';
421
    }
422
423
    /**
424
     * Get the Generate Session
425
     *
426
     * @return string
427
     */
428
    protected function _getGenerateSessionEnd() {
429
        return Mage::getStoreConfig('turpentine_varnish/general/vcl_fix')
430
            ? '-- */' : '';
431
    }
432
433
434
    /**
435
     * Get the Generate Session
436
     *
437
     * @return string
438
     */
439
    protected function _getGenerateSession() {
440
        return Mage::getStoreConfigFlag('turpentine_varnish/general/vcl_fix')
441
            ? 'return (pipe);' : 'call generate_session;';
442
    }
443
444
445
    /**
446
     * Get the Generate Session Expires
447
     *
448
     * @return string
449
     */
450
    protected function _getGenerateSessionExpires() {
451
        return Mage::getStoreConfig('turpentine_varnish/general/vcl_fix')
452
            ? '# call generate_session_expires' : 'call generate_session_expires;';
453
    }
454
455
    /**
456
     * Get the Force Static Caching option
457
     *
458
     * @return string
459
     */
460
    protected function _getForceCacheStatic() {
461
        return Mage::getStoreConfig('turpentine_vcl/static/force_static')
462
            ? 'true' : 'false';
463
    }
464
465
    /**
466
     * Get the Force Static Caching option
467
     *
468
     * @return string
469
     */
470
    protected function _getSimpleHashStatic() {
471
        return Mage::getStoreConfig('turpentine_vcl/static/simple_hash')
472
            ? 'true' : 'false';
473
    }
474
475
    /**
476
     * Format the list of static cache extensions
477
     *
478
     * @return string
479
     */
480
    protected function _getStaticExtensions() {
481
        return implode('|', Mage::helper('turpentine/data')->cleanExplode(',',
482
            Mage::getStoreConfig('turpentine_vcl/static/exts')));
483
    }
484
485
    /**
486
     * Get the static caching TTL
487
     *
488
     * @return string
489
     */
490
    protected function _getStaticTtl() {
491
        return Mage::getStoreConfig('turpentine_vcl/ttls/static_ttl');
492
    }
493
494
    /**
495
     * Format the by-url TTL value list
496
     *
497
     * @return string
498
     */
499
    protected function _getUrlTtls() {
500
        $str = array();
501
        $configTtls = Mage::helper('turpentine/data')->cleanExplode(PHP_EOL,
502
            Mage::getStoreConfig('turpentine_vcl/ttls/url_ttls'));
503
        $ttls = array();
504
        foreach ($configTtls as $line) {
505
            $ttls[] = explode(',', trim($line));
506
        }
507
        foreach ($ttls as $ttl) {
508
            $str[] = sprintf('if (bereq.url ~ "%s%s") { set beresp.ttl = %ds; }',
509
                $this->getBaseUrlPathRegex(), $ttl[0], $ttl[1]);
510
        }
511
        $str = implode(' else ', $str);
512
        if ($str) {
513
            $str .= sprintf(' else { set beresp.ttl = %ds; }',
514
                $this->_getDefaultTtl());
515
        } else {
516
            $str = sprintf('set beresp.ttl = %ds;', $this->_getDefaultTtl());
517
        }
518
        return $str;
519
    }
520
521
    /**
522
     * Get the Enable Caching value
523
     *
524
     * @return string
525
     */
526
    protected function _getEnableCaching() {
527
        return Mage::helper('turpentine/varnish')->getVarnishEnabled() ?
528
            'true' : 'false';
529
    }
530
531
    /**
532
     * Get the list of allowed debug IPs
533
     *
534
     * @return array
535
     */
536
    protected function _getDebugIps() {
537
        return Mage::helper('turpentine/data')->cleanExplode(',',
538
            Mage::getStoreConfig('dev/restrict/allow_ips'));
539
    }
540
541
    /**
542
     * Get the list of crawler IPs
543
     *
544
     * @return array
545
     */
546
    protected function _getCrawlerIps() {
547
        return Mage::helper('turpentine/data')->cleanExplode(',',
548
            Mage::getStoreConfig('turpentine_vcl/backend/crawlers'));
549
    }
550
551
    /**
552
     * Get the regex formatted list of crawler user agents
553
     *
554
     * @return string
555
     */
556
    protected function _getCrawlerUserAgents() {
557
        return implode('|', Mage::helper('turpentine/data')
558
            ->cleanExplode(',',
559
                Mage::getStoreConfig(
560
                    'turpentine_vcl/backend/crawler_user_agents' )));
561
    }
562
563
    /**
564
     * Get the time to increase a cached objects TTL on cache hit (in seconds).
565
     *
566
     * This should be set very low since it gets added to every hit.
567
     *
568
     * @return string
569
     */
570
    protected function _getLruFactor() {
571
        return Mage::getStoreConfig('turpentine_vcl/ttls/lru_factor');
572
    }
573
574
    /**
575
     * Get the advanced session validation restrictions
576
     *
577
     * Note that if User-Agent Normalization is on then the normalized user-agent
578
     * is used for user-agent validation instead of the full user-agent
579
     *
580
     * @return string
581
     */
582
    protected function _getAdvancedSessionValidationTargets() {
583
        $validation = array();
584
        if (Mage::getStoreConfig('web/session/use_remote_addr')) {
585
            $validation[] = 'client.ip';
586
        }
587
        if (Mage::getStoreConfig('web/session/use_http_via')) {
588
            $validation[] = 'req.http.Via';
589
        }
590
        if (Mage::getStoreConfig('web/session/use_http_x_forwarded_for')) {
591
            $validation[] = 'req.http.X-Forwarded-For';
592
        }
593
        if (Mage::getStoreConfig(
594
                    'web/session/use_http_user_agent' ) &&
595
                ! Mage::getStoreConfig(
596
                    'turpentine_vcl/normalization/user_agent' )) {
597
            $validation[] = 'req.http.User-Agent';
598
        }
599
        return $validation;
600
    }
601
602
    /**
603
     * Remove empty and commented out lines from the generated VCL
604
     *
605
     * @param  string $dirtyVcl generated vcl
606
     * @return string
607
     */
608
    protected function _cleanVcl($dirtyVcl) {
609
        return implode(PHP_EOL,
610
            array_filter(
611
                Mage::helper('turpentine/data')
612
                    ->cleanExplode(PHP_EOL, $dirtyVcl),
613
                array($this, '_cleanVclHelper')
614
            )
615
        );
616
    }
617
618
    /**
619
     * Helper to filter out blank/commented lines for VCL cleaning
620
     *
621
     * @param  string $line
622
     * @return bool
623
     */
624
    protected function _cleanVclHelper($line) {
625
        return $line &&
626
            ((substr($line, 0, 1) != '#' &&
627
            substr($line, 0, 2) != '//') ||
628
            substr($line, 0, 8) == '#include');
629
    }
630
631
    /**
632
     * Format a VCL backend declaration
633
     *
634
     * @param  string $name    name of the backend
635
     * @param  string $host    backend host
636
     * @param  string $port    backend port
637
     * @param  array  $options options
638
     * @return string
639
     */
640
    protected function _vcl_backend($name, $host, $port, $options = array()) {
641
        $tpl = <<<EOS
642
backend {{name}} {
643
    .host = "{{host}}";
644
    .port = "{{port}}";
645
646
EOS;
647
        $vars = array(
648
            'host'  => $host,
649
            'port'  => $port,
650
            'name'  => $name,
651
        );
652
        $str = $this->_formatTemplate($tpl, $vars);
653
        foreach ($options as $key => $value) {
654
            $str .= sprintf('   .%s = %s;', $key, $value).PHP_EOL;
655
        }
656
        $str .= '}'.PHP_EOL;
657
        return $str;
658
    }
659
660
    /**
661
     * Format a VCL director declaration, for load balancing
662
     *
663
     * @param string $name           name of the director, also used to select config settings
664
     * @param array  $backendOptions options for each backend
665
     * @return string
666
     */
667 View Code Duplication
    protected function _vcl_director($name, $backendOptions) {
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...
668
        $tpl = <<<EOS
669
director {{name}} round-robin {
670
{{backends}}
671
}
672
EOS;
673
        if ('admin' == $name && 'yes_admin' == Mage::getStoreConfig('turpentine_vcl/backend/load_balancing')) {
674
            $backendNodes = Mage::helper('turpentine/data')->cleanExplode(PHP_EOL,
675
                Mage::getStoreConfig('turpentine_vcl/backend/backend_nodes_admin'));
676
            $probeUrl = Mage::getStoreConfig('turpentine_vcl/backend/backend_probe_url_admin');
677
            $prefix = 'admin';
678
        } else {
679
            $backendNodes = Mage::helper('turpentine/data')->cleanExplode(PHP_EOL,
680
                Mage::getStoreConfig('turpentine_vcl/backend/backend_nodes'));
681
            $probeUrl = Mage::getStoreConfig('turpentine_vcl/backend/backend_probe_url');
682
            if ('admin' == $name) {
683
                $prefix = 'admin';
684
            } else {
685
                $prefix = '';
686
            }
687
        }
688
        $backends = '';
689
		$number = 0;
690
        foreach ($backendNodes as $backendNode) {
691
            $parts = explode(':', $backendNode, 2);
692
            $host = (empty($parts[0])) ? '127.0.0.1' : $parts[0];
693
            $port = (empty($parts[1])) ? '80' : $parts[1];
694
            $backends .= $this->_vcl_director_backend($host, $port, $prefix.$number, $probeUrl, $backendOptions);
695
			$number++;
696
        }
697
        $vars = array(
698
            'name' => $name,
699
            'backends' => $backends
700
        );
701
        return $this->_formatTemplate($tpl, $vars);
702
    }
703
704
    /**
705
     * Format a VCL backend declaration to put inside director
706
     *
707
     * @param string $host     backend host
708
     * @param string $port     backend port
709
	 * @param string $descriptor backend descriptor
710
     * @param string $probeUrl URL to check if backend is up
711
     * @param array  $options  extra options for backend
712
     * @return string
713
     */
714 View Code Duplication
    protected function _vcl_director_backend($host, $port, $descriptor = '', $probeUrl = '', $options = array()) {
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...
715
        $tpl = <<<EOS
716
    {
717
        .backend {$descriptor} = {
718
            .host = "{{host}}";
719
            .port = "{{port}}";
720
{{probe}}
721
722
EOS;
723
        $vars = array(
724
            'host'  => $host,
725
            'port'  => $port,
726
            'probe' => ''
727
        );
728
        if ( ! empty($probeUrl)) {
729
            $vars['probe'] = $this->_vcl_get_probe($probeUrl);
730
        }
731
        $str = $this->_formatTemplate($tpl, $vars);
732
        foreach ($options as $key => $value) {
733
            $str .= sprintf('            .%s = %s;', $key, $value).PHP_EOL;
734
        }
735
        $str .= <<<EOS
736
        }
737
    }
738
EOS;
739
        return $str;
740
    }
741
742
    /**
743
     * Format a VCL probe declaration to put in backend which is in director
744
     *
745
     * @param string $probeUrl URL to check if backend is up
746
     *
747
     * @return string
748
     */
749
    protected function _vcl_get_probe($probeUrl) {
750
        $urlParts = parse_url($probeUrl);
751
        if (empty($urlParts)) {
752
            // Malformed URL
753
            return '';
754
        } else {
755
            $tpl = <<<EOS
756
            .probe = {
757
                .timeout = {{timeout}};
758
                .interval = {{interval}};
759
                .window = {{window}};
760
                .threshold = {{threshold}};
761
                .request =
762
                    "GET {{probe_path}} HTTP/1.1"
763
                    "Host: {{probe_host}}"
764
                    "Connection: close";
765
            }
766
EOS;
767
768
            $timeout = Mage::getStoreConfig('turpentine_vcl/backend/backend_probe_timeout');
769
            $interval = Mage::getStoreConfig('turpentine_vcl/backend/backend_probe_interval');
770
            $window = Mage::getStoreConfig('turpentine_vcl/backend/backend_probe_window');
771
            $threshold = Mage::getStoreConfig('turpentine_vcl/backend/backend_probe_threshold');
772
773
            return $this->_formatTemplate($tpl, array(
774
                'probe_host' => $urlParts['host'],
775
                'probe_path' => $urlParts['path'],
776
                'timeout'    => $timeout,
777
                'interval'   => $interval,
778
                'window'     => $window,
779
                'threshold'  => $threshold,
780
            ));
781
        }
782
    }
783
784
    /**
785
     * Format a VCL ACL declaration
786
     *
787
     * @param  string $name  ACL name
788
     * @param  array  $hosts list of hosts to add to the ACL
789
     * @return string
790
     */
791
    protected function _vcl_acl($name, array $hosts) {
792
        $tpl = <<<EOS
793
acl {{name}} {
794
    {{hosts}}
795
}
796
EOS;
797
        $fmtHost = create_function('$h', 'return sprintf(\'"%s";\',$h);');
798
        $vars = array(
799
            'name'  => $name,
800
            'hosts' => implode("\n    ", array_map($fmtHost, $hosts)),
801
        );
802
        return $this->_formatTemplate($tpl, $vars);
803
    }
804
805
    /**
806
     * Get the User-Agent normalization sub routine
807
     *
808
     * @return string
809
     */
810
    protected function _vcl_sub_normalize_user_agent() {
811
        /**
812
         * Mobile regex from
813
         * @link http://magebase.com/magento-tutorials/magento-design-exceptions-explained/
814
         */
815
        $tpl = <<<EOS
816
if (req.http.User-Agent ~ "iP(?:hone|ad|od)|BlackBerry|Palm|Googlebot-Mobile|Mobile|mobile|mobi|Windows Mobile|Safari Mobile|Android|Opera (?:Mini|Mobi)") {
817
        set req.http.X-Normalized-User-Agent = "mobile";
818
    } else {
819
        set req.http.X-Normalized-User-Agent = "other";
820
    }
821
822
EOS;
823
        return $tpl;
824
    }
825
826
    /**
827
     * Get the Accept-Encoding normalization sub routine
828
     *
829
     * @return string
830
     */
831
    protected function _vcl_sub_normalize_encoding() {
832
        $tpl = <<<EOS
833
if (req.http.Accept-Encoding) {
834
        if (req.http.Accept-Encoding ~ "\*|gzip") {
835
            set req.http.Accept-Encoding = "gzip";
836
        } else if (req.http.Accept-Encoding ~ "deflate") {
837
            set req.http.Accept-Encoding = "deflate";
838
        } else {
839
            # unknown algorithm
840
            unset req.http.Accept-Encoding;
841
        }
842
    }
843
844
EOS;
845
        return $tpl;
846
    }
847
848
    /**
849
     * Get the Host normalization sub routine
850
     *
851
     * @return string
852
     */
853
    protected function _vcl_sub_normalize_host() {
854
        $tpl = <<<EOS
855
set req.http.Host = "{{normalize_host_target}}";
856
857
EOS;
858
        return $this->_formatTemplate($tpl, array(
859
            'normalize_host_target' => $this->_getNormalizeHostTarget() ));
860
    }
861
862
    /**
863
     * Get the hostname for cookie normalization
864
     *
865
     * @return string
866
     */
867
    protected function _getNormalizeCookieTarget() {
868
        return trim(Mage::getStoreConfig(
869
            'turpentine_vcl/normalization/cookie_target' ));
870
    }
871
872
    /**
873
     * Get the regex for cookie normalization
874
     *
875
     * @return string
876
     */
877
    protected function _getNormalizeCookieRegex() {
878
        return trim(Mage::getStoreConfig(
879
            'turpentine_vcl/normalization/cookie_regex' ));
880
    }
881
882
    /**
883
     * Get the allowed IPs when in maintenance mode
884
     *
885
     * @return string
886
     */
887 View Code Duplication
    protected function _vcl_sub_maintenance_allowed_ips() {
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...
888
        if (( ! $this->_getDebugIps()) || ! Mage::getStoreConfig('turpentine_vcl/maintenance/custom_vcl_synth')) {
889
            return false;
890
        }
891
892
        switch (Mage::getStoreConfig('turpentine_varnish/servers/version')) {
893
            case 4.0:
894
            case 4.1:
895
                $tpl = <<<EOS
896
if (req.http.X-Forwarded-For) {
897
    if (req.http.X-Forwarded-For !~ "{{debug_ips}}") {
898
        return (synth(999, "Maintenance mode"));
899
    }
900
}
901
else {
902
    if (client.ip !~ debug_acl) {
903
        return (synth(999, "Maintenance mode"));
904
    }
905
}
906
907
EOS;
908
                break;
909
            default:
910
                $tpl = <<<EOS
911
if (req.http.X-Forwarded-For) {
912
    if(req.http.X-Forwarded-For !~ "{{debug_ips}}") {
913
        error 503;
914
    }
915
} else {
916
    if (client.ip !~ debug_acl) {
917
        error 503;
918
    }
919
}
920
EOS;
921
        }
922
923
        return $this->_formatTemplate($tpl, array(
924
            'debug_ips' => Mage::getStoreConfig('dev/restrict/allow_ips') ));
925
    }
926
927
    /**
928
     * When using Varnish on port 80 and Hitch listen on port 443 for HTTPS, the fix will set X-Forwarded-Proto to HTTPS to prevent redirect loop.
929
     *
930
     * @return string
931
     */
932
    protected function _vcl_sub_https_proto_fix() {
933
        $tpl = <<<EOS
934
if (std.port(server.ip) == 443) {
935
    set req.http.X-Forwarded-Proto = "https";
936
}
937
EOS;
938
        return $tpl;
939
    }
940
941
    /**
942
     * When using Varnish as front door listen on port 80 and Nginx/Apache listen on port 443 for HTTPS, the fix will keep the url parameters when redirect from HTTP to HTTPS.
943
     *
944
     * @return string
945
     */
946
    protected function _vcl_sub_https_redirect_fix() {
947
948
        $hostRegex = array();
949
        foreach ($this->_getHostNames() as $host) {
950
            $hostRegex[] = 'req.http.host ~ "^(?i)'.$host.'"';
951
        }
952
        $hostRegex = implode(' || ', $hostRegex);
953
954
        switch (Mage::getStoreConfig('turpentine_varnish/servers/version')) {
955
            case 4.0:
956
            case 4.1:
957
                $tpl = <<<EOS
958
if ( ($hostRegex) && req.http.X-Forwarded-Proto !~ "(?i)https") {
959
        return (synth(750, ""));
960
    }
961
EOS;
962
                break;
963
            default:
964
                $tpl = <<<EOS
965
if ( ($hostRegex) && req.http.X-Forwarded-Proto !~ "(?i)https") {
966
        error 750 "https://" + req.http.host + req.url;
967
    }
968
EOS;
969
        }
970
971
        return $tpl;
972
    }
973
974
975
    protected function _getHostNames() {
976
977
        $baseUrl = $this->_stripHost(Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_WEB));
978
        $hosts = array(
979
            $baseUrl => $baseUrl
980
        );
981
982
        foreach (Mage::app()->getWebsites() as $website) {
983
            foreach ($website->getGroups() as $group) {
984
                $stores = $group->getStores();
985
                foreach ($stores as $store) {
986
                    $baseUrl = $this->_stripHost(Mage::getStoreConfig('web/unsecure/base_url', $store->getId()));
987
                    $secureBaseUrl = $this->_stripHost(Mage::getStoreConfig('web/secure/base_url', $store->getId()));
988
989
                    $hosts[$baseUrl] = $baseUrl;
990
                    $hosts[$secureBaseUrl] = $secureBaseUrl;
991
                }
992
            }
993
        }
994
995
        return $hosts;
996
    }
997
998
    protected function _stripHost ($baseUrl){
999
        return  rtrim(str_replace(array('http://', 'https://'), '', $baseUrl), '/');
1000
    }
1001
1002
    /**
1003
     * Get the allowed IPs when in maintenance mode
1004
     *
1005
     * @return string
1006
     */
1007 View Code Duplication
    protected function _vcl_sub_synth()
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...
1008
    {
1009
        if (( ! $this->_getDebugIps()) || ! Mage::getStoreConfig('turpentine_vcl/maintenance/custom_vcl_synth')) {
1010
            return false;
1011
        }
1012
1013
        switch (Mage::getStoreConfig('turpentine_varnish/servers/version')) {
1014
            case 4.0:
1015
            case 4.1:
1016
                $tpl = <<<EOS
1017
sub vcl_synth {
1018
    if (resp.status == 999) {
1019
        set resp.status = 503;
1020
        set resp.http.Content-Type = "text/html; charset=utf-8";
1021
        synthetic({"{{vcl_synth_content}}"});
1022
        return (deliver);
1023
    }
1024
    return (deliver);
1025
}
1026
1027
EOS;
1028
                break;
1029
            default:
1030
                $tpl = <<<EOS
1031
sub vcl_error {
1032
    set obj.http.Content-Type = "text/html; charset=utf-8";
1033
    synthetic {"{{vcl_synth_content}}"};
1034
    return (deliver);
1035
}
1036
EOS;
1037
        }
1038
1039
        return $this->_formatTemplate($tpl, array(
1040
            'vcl_synth_content' => Mage::getStoreConfig('turpentine_vcl/maintenance/custom_vcl_synth')));
1041
    }
1042
1043
    /**
1044
     * vcl_synth for fixing https
1045
     *
1046
     * @return string
1047
     */
1048
    protected function _vcl_sub_synth_https_fix()
1049
    {
1050
        $tpl = $this->_vcl_sub_synth();
1051
1052
        if ( ! $tpl) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $tpl of type false|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1053
            $tpl = <<<EOS
1054
sub vcl_synth {
1055
    if (resp.status == 750) {
1056
        set resp.status = 301;
1057
        set resp.http.Location = "https://" + req.http.host + req.url;
1058
        return(deliver);
1059
    }
1060
}
1061
EOS;
1062
        } else{
1063
            $tpl_750 = '
1064
sub vcl_synth {
1065
    if (resp.status == 750) {
1066
        set resp.status = 301;
1067
        set resp.http.Location = "https://" + req.http.host + req.url;
1068
        return(deliver);
1069
    }';
1070
1071
        $tpl = str_ireplace('sub vcl_synth {', $tpl_750, $tpl);
1072
        }
1073
1074
        return $tpl;
1075
    }
1076
1077
1078
1079
    /**
1080
     * Build the list of template variables to apply to the VCL template
1081
     *
1082
     * @return array
1083
     */
1084
    protected function _getTemplateVars() {
1085
        $vars = array(
1086
            'default_backend'   => $this->_getDefaultBackend(),
1087
            'admin_backend'     => $this->_getAdminBackend(),
1088
            'admin_frontname'   => $this->_getAdminFrontname(),
1089
            'normalize_host_target' => $this->_getNormalizeHostTarget(),
1090
            'url_base_regex'    => $this->getBaseUrlPathRegex(),
1091
            'allowed_hosts_regex'   => $this->getAllowedHostsRegex(),
1092
            'url_excludes'  => $this->_getUrlExcludes(),
1093
            'get_param_excludes'    => $this->_getGetParamExcludes(),
1094
            'get_param_ignored' => $this->_getIgnoreGetParameters(),
1095
            'default_ttl'   => $this->_getDefaultTtl(),
1096
            'enable_get_excludes'   => ($this->_getGetParamExcludes() ? 'true' : 'false'),
1097
            'enable_get_ignored' => ($this->_getIgnoreGetParameters() ? 'true' : 'false'),
1098
            'send_unmodified_url' => ($this->_sendUnModifiedUrlToBackend() ? 'true' : 'false'),
1099
            'debug_headers' => $this->_getEnableDebugHeaders(),
1100
            'grace_period'  => $this->_getGracePeriod(),
1101
            'force_cache_static'    => $this->_getForceCacheStatic(),
1102
            'simple_hash_static'    => $this->_getSimpleHashStatic(),
1103
            'generate_session_expires'    => $this->_getGenerateSessionExpires(),
1104
            'generate_session'    => $this->_getGenerateSession(),
1105
            'generate_session_start'    => $this->_getGenerateSessionStart(),
1106
            'generate_session_end'    => $this->_getGenerateSessionEnd(),
1107
            'static_extensions' => $this->_getStaticExtensions(),
1108
            'static_ttl'    => $this->_getStaticTtl(),
1109
            'url_ttls'      => $this->_getUrlTtls(),
1110
            'enable_caching'    => $this->_getEnableCaching(),
1111
            'crawler_acl'   => $this->_vcl_acl('crawler_acl',
1112
                $this->_getCrawlerIps()),
1113
            'esi_cache_type_param'  =>
1114
                Mage::helper('turpentine/esi')->getEsiCacheTypeParam(),
1115
            'esi_method_param'  =>
1116
                Mage::helper('turpentine/esi')->getEsiMethodParam(),
1117
            'esi_ttl_param' => Mage::helper('turpentine/esi')->getEsiTtlParam(),
1118
            'secret_handshake'  => Mage::helper('turpentine/varnish')
1119
                ->getSecretHandshake(),
1120
            'crawler_user_agent_regex'  => $this->_getCrawlerUserAgents(),
1121
            // 'lru_factor'    => $this->_getLruFactor(),
1122
            'debug_acl'     => $this->_vcl_acl('debug_acl',
1123
                $this->_getDebugIps()),
1124
            'custom_c_code' => file_get_contents(
1125
                $this->_getVclTemplateFilename(self::VCL_CUSTOM_C_CODE_FILE) ),
1126
            'esi_private_ttl'   => Mage::helper('turpentine/esi')
1127
                ->getDefaultEsiTtl(),
1128
        );
1129
1130
        if ((bool) Mage::getStoreConfig('turpentine_vcl/urls/bypass_cache_store_url')) {
1131
            $vars['allowed_hosts'] = $this->_vcl_sub_allowed_hosts_regex();
1132
        }
1133
1134
        if (Mage::getStoreConfig('turpentine_vcl/normalization/encoding')) {
1135
            $vars['normalize_encoding'] = $this->_vcl_sub_normalize_encoding();
1136
        }
1137
        if (Mage::getStoreConfig('turpentine_vcl/normalization/user_agent')) {
1138
            $vars['normalize_user_agent'] = $this->_vcl_sub_normalize_user_agent();
1139
        }
1140
        if (Mage::getStoreConfig('turpentine_vcl/normalization/host')) {
1141
            $vars['normalize_host'] = $this->_vcl_sub_normalize_host();
1142
        }
1143
        if (Mage::getStoreConfig('turpentine_vcl/normalization/cookie_regex')) {
1144
            $vars['normalize_cookie_regex'] = $this->_getNormalizeCookieRegex();
1145
        }
1146
        if (Mage::getStoreConfig('turpentine_vcl/normalization/cookie_target')) {
1147
            $vars['normalize_cookie_target'] = $this->_getNormalizeCookieTarget();
1148
        }
1149
1150
        if (Mage::getStoreConfig('turpentine_vcl/maintenance/enable')) {
1151
            // in vcl_recv set the allowed IPs otherwise load the vcl_error (v3)/vcl_synth (v4)
1152
            $vars['maintenance_allowed_ips'] = $this->_vcl_sub_maintenance_allowed_ips();
1153
            // set the vcl_error from Magento database
1154
            $vars['vcl_synth'] = $this->_vcl_sub_synth();
1155
        }
1156
1157
        if (Mage::getStoreConfig('turpentine_varnish/general/https_proto_fix')) {
1158
            $vars['https_proto_fix'] = $this->_vcl_sub_https_proto_fix();
1159
        }
1160
        
1161
        if (Mage::getStoreConfig('turpentine_varnish/general/https_redirect_fix')) {
1162
            $vars['https_redirect'] = $this->_vcl_sub_https_redirect_fix();
1163
            if (Mage::getStoreConfig('turpentine_varnish/servers/version') == '4.0' || Mage::getStoreConfig('turpentine_varnish/servers/version') == '4.1') {
1164
                $vars['vcl_synth'] = $this->_vcl_sub_synth_https_fix();
1165
            }
1166
        }
1167
1168
        foreach (array('', 'top') as $position) {
1169
            $customIncludeFile = $this->_getCustomIncludeFilename($position);
1170
            if (is_readable($customIncludeFile)) {
1171
                $key = 'custom_vcl_include';
1172
                $key .= ($position) ? '_'.$position : '';
1173
                $vars[$key] = file_get_contents($customIncludeFile);
1174
            }
1175
        }
1176
1177
        return $vars;
1178
    }
1179
}
1180