Completed
Push — master ( d37fab...069828 )
by Igor
02:14
created

Native::removeFromDir()   B

Complexity

Conditions 8
Paths 13

Size

Total Lines 21
Code Lines 13

Duplication

Lines 21
Ratio 100 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 21
loc 21
rs 7.1428
cc 8
eloc 13
nc 13
nop 2
1
<?php
2
/**
3
 * @license MIT
4
 * @author Igor Sorokin <[email protected]>
5
 */
6
namespace Dspbee\Bundle\Template;
7
8
use Dspbee\Bundle\Common\TFileSystem;
9
use Dspbee\Bundle\Debug\Wrap;
10
use Dspbee\Bundle\Template\Exception\FileNotFoundException;
11
use Dspbee\Core\Request;
12
13
/**
14
 * Class Native
15
 * @package Dspbee\Bundle\Template
16
 */
17
class Native
18
{
19
    use TFileSystem;
20
21
    /**
22
     * @param string $packageRoot
23
     * @param Request|null $request
24
     * @param bool $cache
25
     */
26
    public function __construct($packageRoot, Request $request = null, $cache = true)
27
    {
28
        $this->packageRoot = rtrim($packageRoot, '/');
29
        $this->request = $request;
30
31
        if (class_exists('Dspbee\Bundle\Debug\Wrap')) {
32
            $this->cache = !Wrap::$debugEnabled;
33
        } else {
34
            $this->cache = $cache;
35
        }
36
    }
37
38
    /**
39
     * Create content from template and data.
40
     *
41
     * @param string $name
42
     * @param array $data
43
     *
44
     * @return string|null
45
     */
46
    public function getContent($name, array $data = [])
47
    {
48
        $path = $this->packageRoot . '/view/_cache/' . str_replace('/', '_', $name);
49
50
        if (!file_exists($path) || !$this->cache) {
51
            $code = $this->compile($name, true, true);
52
            if (empty($code)) {
53
                return null;
54
            }
55
56
            $fh = fopen($path, 'wb');
57
            if (flock($fh, LOCK_EX)) {
58
                fwrite($fh, $code);
59
                flock($fh, LOCK_UN);
60
            }
61
            fflush($fh);
62
            fclose($fh);
63
        }
64
65
        $fh = fopen($path, 'rb');
66
        flock($fh, LOCK_SH);
67
68
        if (null !== $this->request) {
69
            $data = array_replace($data, ['request' => $this->request]);
70
        }
71
72
        $html = self::renderTemplate($path, $data);
73
74
        flock($fh, LOCK_UN);
75
        fclose($fh);
76
77
        return $html;
78
    }
79
80
    /**
81
     * Delete all cached templates.
82
     */
83
    public function clearCache()
84
    {
85
        self::removeFromDir($this->packageRoot . '/view/_cache');
86
    }
87
88
    /**
89
     * Create solid template.
90
     *
91
     * @param string $name
92
     * @param bool $processInclude
93
     * @param bool $processExtends
94
     *
95
     * @return string|null
96
     *
97
     * @throws FileNotFoundException
98
     */
99
    private function compile($name, $processInclude, $processExtends)
100
    {
101
        $path = $this->packageRoot . '/view/' . $name;
102
103
        if (file_exists($path)) {
104
            ob_start();
105
            readfile($path);
0 ignored issues
show
Security File Exposure introduced by
$path can contain request data and is used in file inclusion context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
106
            $code = ob_get_clean();
107
        } else {
108
            throw new FileNotFoundException($path);
109
        }
110
111
        if ($processInclude) {
112
            preg_match_all('/<!-- include (.*) -->/', $code, $matchList);
113
            if (count($matchList)) {
114
                foreach ($matchList[1] as $key => $template) {
115
                    if (!empty($matchList[0][$key]) && false !== strpos($code, $matchList[0][$key])) {
116
                        $template = trim($template);
117
                        $code = str_replace($matchList[0][$key], $this->compile($template, true, false), $code);
118
                    }
119
                }
120
            }
121
        }
122
123
        if ($processExtends) {
124
            preg_match_all('/<!-- extends (.*) -->/', $code, $matchList);
125
            if (isset($matchList[1][0])) {
126
                $template = trim($matchList[1][0]);
127
                $parentHtml = $this->compile($template, true, false);
128
129
                $code = str_replace($matchList[0][0], '', $code);
130
                $parentHtml = str_replace('<!-- section -->', $code, $parentHtml);
131
                $code = $parentHtml;
132
            }
133
        }
134
135
        return $code;
136
    }
137
138
    /**
139
     * Safe include. Used for scope isolation.
140
     *
141
     * @param string $__file__  File to include
142
     * @param array  $data      Data passed to template
143
     *
144
     * @return string
145
     */
146
    private static function renderTemplate($__file__, array $data)
147
    {
148
        ob_start();
149
        extract($data);
150
        include $__file__;
151
        return ob_get_clean();
152
    }
153
154
    private $request;
155
    private $packageRoot;
156
    private $cache;
157
}
158
159