Completed
Push — master ( bf2930...494091 )
by David
07:08 queued 03:26
created

lib/Dwoo/Template/File.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Copyright (c) 2013-2016
4
 *
5
 * @category  Library
6
 * @package   Dwoo\Template
7
 * @author    Jordi Boggiano <[email protected]>
8
 * @author    David Sanchez <[email protected]>
9
 * @copyright 2008-2013 Jordi Boggiano
10
 * @copyright 2013-2016 David Sanchez
11
 * @license   http://dwoo.org/LICENSE Modified BSD License
12
 * @version   1.4.0
13
 * @date      2016-12-16
14
 * @link      http://dwoo.org/
15
 */
16
17
namespace Dwoo\Template;
18
19
use Dwoo\Exception as DwooException;
20
use Dwoo\Core as Core;
21
use Dwoo\ITemplate as ITemplate;
22
use Dwoo\Security\Exception as SecurityException;
23
use Dwoo\Template\File as TemplateFile;
24
25
/**
26
 * Represents a Dwoo template contained in a file.
27
 * This software is provided 'as-is', without any express or implied warranty.
28
 * In no event will the authors be held liable for any damages arising from the use of this software.
29
 */
30
class File extends Str
31
{
32
    /**
33
     * Template filename.
34
     *
35
     * @var string
36
     */
37
    protected $file;
38
39
    /**
40
     * Include path(s) to look into to find this template.
41
     *
42
     * @var array
43
     */
44
    protected $includePath = null;
45
46
    /**
47
     * Resolved path cache when looking for a file in multiple include paths.
48
     * this is reset when the include path is changed
49
     *
50
     * @var string
51
     */
52
    protected $resolvedPath = null;
53
54
    /**
55
     * Creates a template from a file.
56
     *
57
     * @param string $file        the path to the template file, make sure it exists
58
     * @param int    $cacheTime   duration of the cache validity for this template,
59
     *                            if null it defaults to the Dwoo instance that will
60
     *                            render this template
61
     * @param string $cacheId     the unique cache identifier of this page or anything else that
62
     *                            makes this template's content unique, if null it defaults
63
     *                            to the current url
64
     * @param string $compileId   the unique compiled identifier, which is used to distinguish this
65
     *                            template from others, if null it defaults to the filename+bits of the path
66
     * @param mixed  $includePath a string for a single path to look into for the given file, or an array of paths
67
     */
68
    public function __construct($file, $cacheTime = null, $cacheId = null, $compileId = null, $includePath = null)
69
    {
70
        $this->file      = $file;
71
        $this->name      = basename($file);
72
        $this->cacheTime = $cacheTime;
73
74 View Code Duplication
        if ($compileId !== null) {
0 ignored issues
show
This code seems to be duplicated across 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...
75
            $this->compileId = str_replace('../', '__', strtr($compileId, '\\%?=!:;' . PATH_SEPARATOR, '/-------'));
76
        }
77
78 View Code Duplication
        if ($cacheId !== null) {
0 ignored issues
show
This code seems to be duplicated across 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...
79
            $this->cacheId = str_replace('../', '__', strtr($cacheId, '\\%?=!:;' . PATH_SEPARATOR, '/-------'));
80
        }
81
82
        if (is_string($includePath)) {
83
            $this->includePath = array($includePath);
84
        } elseif (is_array($includePath)) {
85
            $this->includePath = $includePath;
86
        }
87
    }
88
89
    /**
90
     * Sets the include path(s) to where the given template filename must be looked up.
91
     *
92
     * @param mixed $paths the path to look into, can be string for a single path or an array of paths
93
     */
94
    public function setIncludePath($paths)
95
    {
96
        if (is_array($paths) === false) {
97
            $paths = array($paths);
98
        }
99
100
        $this->includePath  = $paths;
101
        $this->resolvedPath = null;
102
    }
103
104
    /**
105
     * Return the current include path(s).
106
     *
107
     * @return array
108
     */
109
    public function getIncludePath()
110
    {
111
        return $this->includePath;
112
    }
113
114
    /**
115
     * Checks if compiled file is valid (exists and it's the modification is greater or
116
     * equal to the modification time of the template file).
117
     *
118
     * @param string file
119
     *
120
     * @return bool True cache file existance and it's modification time
121
     */
122
    protected function isValidCompiledFile($file)
123
    {
124
        return parent::isValidCompiledFile($file) && (int)$this->getUid() <= filemtime($file);
125
    }
126
127
    /**
128
     * Returns the template source of this template.
129
     *
130
     * @return string
131
     */
132
    public function getSource()
133
    {
134
        return file_get_contents($this->getResourceIdentifier());
135
    }
136
137
    /**
138
     * Returns the resource name for this template class.
139
     *
140
     * @return string
141
     */
142
    public function getResourceName()
143
    {
144
        return 'file';
145
    }
146
147
    /**
148
     * Returns this template's source filename.
149
     *
150
     * @return string
151
     * @throws DwooException
152
     */
153
    public function getResourceIdentifier()
154
    {
155
        if ($this->resolvedPath !== null) {
156
            return $this->resolvedPath;
157
        } elseif ($this->includePath === null) {
158
            return $this->file;
159
        } else {
160
            foreach ($this->includePath as $path) {
161
                $path = rtrim($path, DIRECTORY_SEPARATOR);
162
                if (file_exists($path . DIRECTORY_SEPARATOR . $this->file) === true) {
163
                    $this->resolvedPath = $path . DIRECTORY_SEPARATOR . $this->file;
164
165
                    return $this->resolvedPath;
0 ignored issues
show
Bug Best Practice introduced by DSanchez
The return type of return $this->resolvedPath; (string) is incompatible with the return type of the parent method Dwoo\Template\Str::getResourceIdentifier of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
166
                }
167
            }
168
169
            throw new DwooException('Template "' . $this->file . '" could not be found in any of your include path(s)');
170
        }
171
    }
172
173
    /**
174
     * Returns an unique value identifying the current version of this template,
175
     * in this case it's the unix timestamp of the last modification.
176
     *
177
     * @return string
178
     */
179
    public function getUid()
180
    {
181
        return (string)filemtime($this->getResourceIdentifier());
182
    }
183
184
    /**
185
     * Returns a new template object from the given include name, null if no include is
186
     * possible (resource not found), or false if include is not permitted by this resource type.
187
     *
188
     * @param Core      $core           the dwoo instance requiring it
189
     * @param mixed     $resourceId     the filename (relative to this template's dir) of the template to
190
     *                                  include
191
     * @param int       $cacheTime      duration of the cache validity for this template, if null it defaults
192
     *                                  to the Dwoo instance that will render this template if null it
193
     *                                  defaults to the Dwoo instance that will render this template if null
194
     *                                  it defaults to the Dwoo instance that will render this template
195
     * @param string    $cacheId        the unique cache identifier of this page or anything else that makes
196
     *                                  this template's content unique, if null it defaults to the current
197
     *                                  url makes this template's content unique, if null it defaults to the
198
     *                                  current url makes this template's content unique, if null it defaults
199
     *                                  to the current url
200
     * @param string    $compileId      the unique compiled identifier, which is used to distinguish this
201
     *                                  template from others, if null it defaults to the filename+bits of the
202
     *                                  path template from others, if null it defaults to the filename+bits
203
     *                                  of the path template from others, if null it defaults to the
204
     *                                  filename+bits of the path
205
     * @param ITemplate $parentTemplate the template that is requesting a new template object (through an
206
     *                                  include, extends or any other plugin) an include, extends or any
207
     *                                  other plugin) an include, extends or any other plugin)
208
     *
209
     * @return TemplateFile|null
210
     * @throws DwooException
211
     * @throws SecurityException
212
     */
213
    public static function templateFactory(Core $core, $resourceId, $cacheTime = null, $cacheId = null, $compileId = null, ITemplate $parentTemplate = null)
214
    {
215
        if (DIRECTORY_SEPARATOR === '\\') {
216
            $resourceId = str_replace(
217
                array("\t", "\n", "\r", "\f", "\v"), array(
218
                '\\t',
219
                '\\n',
220
                '\\r',
221
                '\\f',
222
                '\\v'
223
                ), $resourceId
224
            );
225
        }
226
        $resourceId = strtr($resourceId, '\\', '/');
227
228
        $includePath = null;
229
230
        if (file_exists($resourceId) === false) {
231
            if ($parentTemplate === null) {
232
                $parentTemplate = $core->getTemplate();
233
            }
234
            if ($parentTemplate instanceof self) {
235
                if ($includePath = $parentTemplate->getIncludePath()) {
236
                    if (strstr($resourceId, '../')) {
237
                        throw new DwooException('When using an include path you can not reference a template into a parent directory (using ../)');
238
                    }
239
                } else {
240
                    $resourceId = dirname($parentTemplate->getResourceIdentifier()) . DIRECTORY_SEPARATOR . $resourceId;
241
                    if (file_exists($resourceId) === false) {
242
                        return null;
243
                    }
244
                }
245
            } else {
246
                return null;
247
            }
248
        }
249
250
        if ($policy = $core->getSecurityPolicy()) {
251
            while (true) {
252
                if (preg_match('{^([a-z]+?)://}i', $resourceId)) {
253
                    throw new SecurityException('The security policy prevents you to read files from external sources : <em>' . $resourceId . '</em>.');
254
                }
255
256
                if ($includePath) {
257
                    break;
258
                }
259
260
                $resourceId = realpath($resourceId);
261
                $dirs       = $policy->getAllowedDirectories();
262
                foreach ($dirs as $dir => $dummy) {
263
                    if (strpos($resourceId, $dir) === 0) {
264
                        break 2;
265
                    }
266
                }
267
                throw new SecurityException('The security policy prevents you to read <em>' . $resourceId . '</em>');
268
            }
269
        }
270
271
        $class = 'Dwoo\Template\File';
272
        if ($parentTemplate) {
273
            $class = get_class($parentTemplate);
274
        }
275
276
        return new $class($resourceId, $cacheTime, $cacheId, $compileId, $includePath);
277
    }
278
279
    /**
280
     * Returns the full compiled file name and assigns a default value to it if
281
     * required.
282
     *
283
     * @param Core $core the dwoo instance that requests the file name
284
     *
285
     * @return string the full path to the compiled file
286
     */
287
    protected function getCompiledFilename(Core $core)
288
    {
289
        // no compile id was provided, set default
290
        if ($this->compileId === null) {
291
            $this->compileId = str_replace('../', '__', strtr($this->getResourceIdentifier(), '\\:', '/-'));
292
        }
293
294
        return $this->compileId . '.d' . Core::RELEASE_TAG . '.php';
295
    }
296
297
    /**
298
     * Returns some php code that will check if this template has been modified or not.
299
     * if the function returns null, the template will be instanciated and then the Uid checked
300
     *
301
     * @return string
302
     */
303
    public function getIsModifiedCode()
304
    {
305
        return '"' . $this->getUid() . '" == filemtime(' . var_export($this->getResourceIdentifier(), true) . ')';
306
    }
307
}
308