Issues (4)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Generator.php (2 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
 * Created by Vitaly Iegorov <[email protected]>.
4
 * on 18.02.16 at 14:17
5
 */
6
namespace samsonframework\view;
7
8
use samsonframework\view\exception\GeneratedViewPathHasReservedWord;
9
10
/**
11
 * Views generator, this class scans resource for view files and creates
12
 * appropriate View class ancestors with namespace as relative view location
13
 * and file name as View class name ending with "View".
14
 *
15
 * Generator also analyzes view files content and creates protected class field
16
 * members for every variable used inside with chainable setter for this field,
17
 * to help IDE and developer in creating awesome code.
18
 *
19
 * TODO: Check for reserved keywords(like list) in namespaces
20
 * TODO: Somehow know view variable type(typehint??) and add comments and type-hints to generated classes.
21
 * TODO: Clever analysis for foreach, if, and so on language structures, we do not need to create variables for loop iterator.
22
 * TODO: If a variable is used in foreach - this is an array or Iteratable ancestor - we can add typehint automatically
23
 * TODO: Analyze view file php doc comments to get variable types
24
 * TODO: If a token variable is not $this and has "->" - this is object, maybe type-hint needs to be added.
25
 * TODO: Add caching logic to avoid duplicate file reading
26
 * TODO: Do not generate class fields with empty values
27
 * TODO: Generate constants with field names
28
 *
29
 * @package samsonframework\view
30
 */
31
class Generator
32
{
33
    /** string All generated view classes will end with this suffix */
34
    const VIEW_CLASSNAME_SUFFIX = 'View';
35
36
    /** @var array Collection of PHP reserved words */
37
    protected static $reservedWords = array('list');
38
39
    /** @var Metadata[] Collection of view metadata */
40
    public $metadata = array();
41
42
    /** @var \samsonphp\generator\Generator */
43
    protected $generator;
44
45
    /** @var string Generated classes namespace prefix */
46
    protected $namespacePrefix;
47
48
    /** @var string Collection of namespace parts to be ignored in generated namespaces */
49
    protected $ignoreNamespace = array();
50
51
    /** @var array Collection of view files */
52
    protected $files = array();
53
54
    /** @var string Scanning entry path */
55
    protected $entryPath;
56
57
    /** @var string Parent view class name */
58
    protected $parentViewClass;
59
60
    /**
61
     * Generator constructor.
62
     *
63
     * @param \samsonphp\generator\Generator $generator PHP code generator instance
64
     * @param string $namespacePrefix Generated classes namespace will have it
65
     * @param array $ignoreNamespace Namespace parts that needs to ignored
66
     * @param string $parentViewClass Generated classes will extend it
67
     */
68 4
    public function __construct(
69
        \samsonphp\generator\Generator $generator,
70
        $namespacePrefix,
71
        array $ignoreNamespace = array(),
72
        $parentViewClass = \samsonframework\view\View::class
73
    ) {
74 4
        $this->generator = $generator;
75 4
        $this->parentViewClass = $parentViewClass;
76 4
        $this->ignoreNamespace = $ignoreNamespace;
0 ignored issues
show
Documentation Bug introduced by
It seems like $ignoreNamespace of type array is incompatible with the declared type string of property $ignoreNamespace.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
77 4
        $this->namespacePrefix = rtrim(ltrim($namespacePrefix, '\\'), '\\') . '\\';
78 4
    }
79
80
    /**
81
     * Recursively scan path for files with specified extensions.
82
     *
83
     * @param string $source Entry point path
84
     * @param string $path Entry path for scanning
85
     * @param array $extensions Collection of file extensions without dot
86
     */
87 4
    public function scan($source, array $extensions = array(View::DEFAULT_EXT), $path = null)
88
    {
89 4
        $this->entryPath = $source;
90
91 4
        $path = isset($path) ? $path : $source;
92
93
        // Recursively go deeper into inner folders for scanning
94 4
        $folders = glob($path . '/*', GLOB_ONLYDIR);
95 4
        foreach ($folders as $folder) {
96 3
            $this->scan($source, $extensions, $folder);
97 4
        }
98
99
        // Iterate file extensions
100 4
        foreach ($extensions as $extension) {
101 4
            foreach (glob(rtrim($path, '/') . '/*.' . $extension) as $file) {
102 4
                $this->files[str_replace($source, '', $file)] = $file;
103 4
            }
104 4
        }
105 4
    }
106
107
    /**
108
     * Generate view classes.
109
     *
110
     * @param string $path Entry path for generated classes and folders
111
     * @param null|callable $viewHandler View code handler
112
     *
113
     * @throws GeneratedViewPathHasReservedWord
114
     */
115 3
    public function generate($path = __DIR__, $viewHandler = null)
116
    {
117 3
        foreach ($this->files as $relativePath => $absolutePath) {
118 3
            $this->metadata[$absolutePath] = $this->analyze($absolutePath);
119 3
            $this->metadata[$absolutePath]->path = $absolutePath;
120 3
            list($this->metadata[$absolutePath]->className,
121 3
                $this->metadata[$absolutePath]->namespace) = $this->generateClassName($absolutePath, $this->entryPath);
122 3
        }
123
124 2
        foreach ($this->metadata as $metadata) {
125 2
            $this->generateViewClass($metadata, $path, $viewHandler);
126 2
        }
127 2
    }
128
129
    /**
130
     * Analyze view file and create its metadata.
131
     *
132
     * @param string $file Path to view file
133
     *
134
     * @return Metadata View file metadata
135
     */
136 3
    public function analyze($file)
137
    {
138 3
        $metadata = new Metadata();
139 3
        $fileText = file_get_contents($file);
140
        // Use PHP tokenizer to find variables
141 3
        foreach ($tokens = token_get_all($fileText) as $idx => $token) {
142 3
            if (!is_string($token) && $token[0] === T_VARIABLE) {
143
                // Store variable
144 3
                $variableText = $token[1];
145
                // Store variable name
146 3
                $variableName = ltrim($token[1], '$');
147
148
                // Ignore static variables
149 3
                if (isset($tokens[$idx - 1]) && $tokens[$idx - 1][0] === T_DOUBLE_COLON) {
150 1
                    $metadata->static[$variableName] = $variableText;
151 1
                    continue;
152
                }
153
154
                // If next token is object operator
155 3
                if ($tokens[$idx + 1][0] === T_OBJECT_OPERATOR) {
156
                    // Ignore $this
157 3
                    if ($variableName === 'this') {
158 3
                        continue;
159
                    }
160
161
                    // And two more tokens
162 1
                    $variableText .= $tokens[$idx + 1][1] . $tokens[$idx + 2][1];
163
164
                    // Store object variable
165 1
                    $metadata->variables[$this->changeName($variableName)] = $variableText;
166
                    // Store view variable key - actual object name => full variable usage
167 1
                    $metadata->originalVariables[$this->changeName($variableName)] = $variableName;
168 1
                } else {
169
                    // Store original variable name
170 3
                    $metadata->originalVariables[$this->changeName($variableName)] = $variableName;
171
                    // Store view variable key - actual object name => full variable usage
172 3
                    $metadata->variables[$this->changeName($variableName)] = $variableText;
173
                }
174 3
            } elseif ($token[0] === T_DOC_COMMENT) { // Match doc block comments
175
                // Parse variable type and name
176 3
                if (preg_match('/@var\s+(?<type>[^ ]+)\s+(?<variable>[^*]+)/', $token[1], $matches)) {
177 3
                    $metadata->types[substr(trim($matches['variable']), 1)] = $matches['type'];
178 3
                }
179 3
            }
180 3
        }
181 3
        if (preg_match_all('/\$this->block\(\'(?<block>[^ ]+)\'/', $fileText, $matches)) {
182 2
            $metadata->blocks = $matches['block'];
183 2
        }
184
185 3
        if (preg_match('/\$this->extend\((?<class>[^ ]+\:\:class)\s*\,\s*\'(?<block>[^ ]+)\'\s*\)/', $fileText,
186 3
            $matches)) {
187 2
            $metadata->parentClass = $matches['class'];
188 2
            $metadata->parentBlock = $matches['block'];
189 2
        }
190
191 3
        return $metadata;
192
    }
193
194
    /**
195
     * Change variable name to camel caps format.
196
     *
197
     * @param string $variable
198
     *
199
     * @return string Changed variable name
200
     */
201 3
    public function changeName($variable)
202
    {
203 3
        return lcfirst(
204 3
            implode(
205 3
                '',
206 3
                array_map(
207 3
                    function ($element) {
208 3
                        return ucfirst($element);
209 3
                    },
210 3
                    explode('_', str_replace('-', '_', $variable))
211 3
                )
212 3
            )
213
0 ignored issues
show
Empty lines are not allowed in multi-line function calls
Loading history...
214 3
        );
215
    }
216
217
    /**
218
     * Generic class name and its name space generator.
219
     *
220
     * @param string $file Full path to view file
221
     * @param string $entryPath Entry path
222
     *
223
     * @return array Class name[0] and namespace[1]
224
     * @throws GeneratedViewPathHasReservedWord
225
     */
226 3
    protected function generateClassName($file, $entryPath)
227
    {
228
        // Get only file name as a class name with suffix
229 3
        $className = ucfirst($this->changeName(pathinfo($file, PATHINFO_FILENAME)). self::VIEW_CLASSNAME_SUFFIX);
230
231
        // Get namespace as part of file path relatively to entry path
232 3
        $nameSpace = strtolower(
233 3
            rtrim(
234 3
                ltrim(
235 3
                    str_replace(
236 3
                        '/',
237 3
                        '\\',
238 3
                        str_replace(array('-', '_'), '', str_replace($entryPath, '', pathinfo($file, PATHINFO_DIRNAME)))
239 3
                    ),
240
                    '\\'
241 3
                ),
242
                '\\'
243 3
            )
244 3
        );
245
246
        // Remove ignored parts from namespaces
247 3
        $nameSpace = str_replace($this->ignoreNamespace, '', $nameSpace);
248
249
        // Check generated namespaces
250 3
        foreach (static::$reservedWords as $reservedWord) {
251 3
            if (strpos($nameSpace, '\\' . $reservedWord) !== false) {
252 1
                throw new GeneratedViewPathHasReservedWord($file . '(' . $reservedWord . ')');
253
            }
254 3
        }
255
256
        // Return collection for further usage
257 3
        return array($className, rtrim($this->namespacePrefix . $nameSpace, '\\'));
258
    }
259
260
    /**
261
     * Create View class ancestor.
262
     *
263
     * @param Metadata $metadata View file metadata
264
     * @param string $path Entry path for generated classes and folders
265
     * @param null|callable $viewHandler View code handler
266
     */
267 2
    protected function generateViewClass(Metadata $metadata, $path, $viewHandler = null)
268
    {
269 2
        $metadataParentClass = eval('return ' . $metadata->parentClass . ';');
270
271
        // Read view file
272 2
        $viewCode = trim(file_get_contents($metadata->path));
273
274
        // If we have external handler - pass view code to it for conversion
275 2
        if (is_callable($viewHandler)) {
276
            $viewCode = call_user_func($viewHandler, $viewCode);
277
        }
278
279
        // Convert to string for defining
280 2
        $viewCode = '<<<\'EOT\'' . "\n" . $viewCode . "\n" . 'EOT';
281
282 2
        $parentClass = !isset($metadata->parentClass) ? $this->parentViewClass : $metadataParentClass;
283 2
        $this->generator
284 2
            ->defNamespace($metadata->namespace)
285 2
            ->multiComment(array('Class for view "' . $metadata->path . '" rendering'))
286 2
            ->defClass($metadata->className, '\\' . $parentClass)
287 2
            ->commentVar('string', 'Path to view file')
288 2
            ->defClassVar('$file', 'protected', $metadata->path)
289 2
            ->commentVar('string', 'Parent block name')
290 2
            ->defClassVar('$parentBlock', 'protected', $metadata->parentBlock)
291 2
            ->commentVar('array', 'Blocks list')
292 2
            ->defClassVar('$blocks', 'protected', $metadata->blocks)
293 2
            ->commentVar('string', 'View source code')
294 2
            ->defClassVar('$source', 'protected', $viewCode);
295
        //->commentVar('array', 'Collection of view variables')
296
        //->defClassVar('$variables', 'public static', array_keys($metadata->variables))
297
        //->commentVar('array', 'Collection of view variable types')
298
        //->defClassVar('$types', 'public static', $metadata->types)
299 2
        ;
300
301
        // Iterate all view variables
302 2
        foreach (array_keys($metadata->variables) as $name) {
303 2
            $type = array_key_exists($name, $metadata->types) ? $metadata->types[$name] : 'mixed';
304 2
            $static = array_key_exists($name, $metadata->static) ? ' static' : '';
305 2
            $this->generator
306 2
                ->commentVar($type, 'View variable')
307 2
                ->defClassVar('$' . $name, 'public' . $static);
308
309
            // Do not generate setters for static variables
310 2
            if ($static !== ' static') {
311 2
                $this->generator->text($this->generateViewVariableSetter(
312 2
                    $name,
313 2
                    $metadata->originalVariables[$name],
314
                    $type
315 2
                ));
316 2
            }
317 2
        }
318
319
        // Iterate namespace and create folder structure
320 2
        $path .= '/' . str_replace('\\', '/', $metadata->namespace);
321 2
        if (!is_dir($path)) {
322 1
            mkdir($path, 0777, true);
323 1
        }
324
325 2
        $newClassFile = $path . '/' . $metadata->className . '.php';
326 2
        file_put_contents(
327 2
            $newClassFile,
328 2
            '<?php' . $this->generator->endClass()->flush()
329 2
        );
330
331
        // Store path to generated class
332 2
        $metadata->generatedPath = $newClassFile;
333
334
        // Make generated cache files accessible
335 2
        chmod($newClassFile, 0777);
336 2
    }
337
338
    /**
339
     * Generate constructor for application class.
340
     *
341
     * @param string $variable View variable name
342
     * @param string $original Original view variable name
343
     * @param string $type Variable type
344
     *
345
     * @return string View variable setter method
346
     */
347 2
    protected function generateViewVariableSetter($variable, $original, $type = 'mixed')
348
    {
349
        // Define type hint
350 2
        $typeHint = strpos($type, '\\') !== false ? $type . ' ' : '';
351
352 2
        $class = "\n\t" . '/**';
353 2
        $class .= "\n\t" . ' * Setter for ' . $variable . ' view variable';
354 2
        $class .= "\n\t" . ' *';
355 2
        $class .= "\n\t" . ' * @param ' . $type . ' $value View variable value';
356 2
        $class .= "\n\t" . ' * @return $this Chaining';
357 2
        $class .= "\n\t" . ' */';
358 2
        $class .= "\n\t" . 'public function ' . $variable . '(' . $typeHint . '$value)';
359 2
        $class .= "\n\t" . '{';
360 2
        $class .= "\n\t\t" . 'return parent::set($value, \'' . $original . '\');';
361 2
        $class .= "\n\t" . '}' . "\n";
362
363 2
        return $class;
364
    }
365
366
    /** @return string Hash representing generator state */
367 1
    public function hash()
368
    {
369 1
        $hash = '';
370 1
        foreach ($this->files as $relativePath => $absolutePath) {
371 1
            $hash .= md5($relativePath . filemtime($absolutePath));
372 1
        }
373
374 1
        return md5($hash);
375
    }
376
}
377