Issues (1240)

Security Analysis    not enabled

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.

modules/kodoc/libraries/Kodoc.php (16 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 defined('SYSPATH') or die('No direct access allowed.');
2
/**
3
 * Kohana - The Swift PHP Framework
4
 *
5
 *  License:
6
 *  author    - Kohana Team
7
 *  copyright - (c) 2007 Kohana Team
8
 *  license   - <http://kohanaphp.com/license.html>
9
 */
10
11
/**
12
 * Provides self-generating documentation about Kohana.
13
 */
14
class Kodoc_Core
15
{
16
    protected static $types = array(
17
        'core',
18
        'config',
19
        'helpers',
20
        'libraries',
21
        'models',
22
        'views'
23
    );
24
25
    public static function get_types()
26
    {
27
        return self::$types;
28
    }
29
30
    public static function get_files()
31
    {
32
        // Extension length
33
        $ext_len = -(strlen(EXT));
34
35
        $files = array();
36
        foreach (self::$types as $type) {
37
            $files[$type] = array();
38
            foreach (Kohana::list_files($type, true) as $file) {
39
                // Not a source file
40
                if (substr($file, $ext_len) !== EXT) {
41
                    continue;
42
                }
43
44
                // Remove the dirs from the filename
45
                $file = preg_replace('!^.+'.$type.'/(.+)'.EXT.'$!', '$1', $file);
46
47
                // Skip utf8 function files
48
                if ($type === 'core' and substr($file, 0, 5) === 'utf8/') {
49
                    continue;
50
                }
51
52
                if ($type === 'libraries' and substr($file, 0, 8) === 'drivers/') {
53
                    // Remove the drivers directory from the file
54
                    $file = explode('_', substr($file, 8));
55
56
                    if (count($file) === 1) {
57
                        // Driver interface
58
                        $files[$type][current($file)][] = current($file);
59
                    } else {
60
                        // Driver is class suffix
61
                        $driver = array_pop($file);
62
63
                        // Library is everything else
64
                        $library = implode('_', $file);
65
66
                        // Library driver
67
                        $files[$type][$library][] = $driver;
68
                    }
69
                } else {
70
                    $files[$type][$file] = null;
71
                }
72
            }
73
        }
74
75
        return $files;
76
    }
77
78
    public static function remove_docroot($file)
0 ignored issues
show
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
79
    {
80
        return preg_replace('!^'.preg_quote(DOCROOT, '!').'!', '', $file);
81
    }
82
83
    public static function humanize_type($types)
84
    {
85
        $types = is_array($types) ? $types : explode('|', $types);
86
87
        $output = array();
88
        while ($t = array_shift($types)) {
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $t. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
89
            $output[] = '<tt>'.trim($t).'</tt>';
90
        }
91
92
        return implode(' or ', $output);
93
    }
94
95
    public static function humanize_value($value)
96
    {
97
        if ($value === null) {
98
            return 'NULL';
99
        } elseif (is_bool($value)) {
100
            return $value ? 'TRUE' : 'FALSE';
101
        } elseif (is_string($value)) {
102
            return 'string '.$value;
103
        } elseif (is_numeric($value)) {
104
            return (is_int($value) ? 'int' : 'float').' '.$value;
105
        } elseif (is_array($value)) {
106
            return 'array';
107
        } elseif (is_object($value)) {
108
            return 'object '.get_class($value);
109
        }
110
    }
111
112
    // All files to be parsed
113
    protected $file = array();
114
115
    public function __construct($type, $filename)
116
    {
117
        // Parse the file
118
        $this->file = $this->parse($type, $filename);
119
    }
120
121
    /**
122
     * Fetch documentation for all files parsed.
123
     *
124
     * Returns:
125
     *  array: file documentation
126
     */
127
    public function get()
128
    {
129
        return $this->file;
130
    }
131
132
    /**
133
     * Parse a file for Kodoc commands, classes, and methods.
134
     *
135
     * Parameters:
136
     *  string: file type
137
     *  string: absolute filename path
138
     */
139
    protected function parse($type, $filename)
140
    {
141
        // File definition
142
        $file = array(
143
            'type'      => $type,
144
            'comment'   => '',
145
            'file'      => self::remove_docroot($filename),
146
        );
147
148
        // Read the entire file into an array
149
        $data = file($filename);
150
151
        foreach ($data as $line) {
152
            if (strpos($line, 'class') !== false and preg_match('/(?:class|interface)\s+([a-z0-9_]+).+{$/i', $line, $matches)) {
153
                // Include the file if it has not already been included
154
                class_exists($matches[1], false) or include_once $filename;
155
156
                // Add class to file info
157
                $file['classes'][] = $this->parse_class($matches[1]);
158
            }
159
        }
160
161
        if (empty($file['classes'])) {
162
            $block  = null;
163
            $source = null;
164
165
            foreach ($data as $line) {
166
                switch (substr(trim($line), 0, 2)) {
167
                    case '/*':
168
                        $block = '';
169
                        continue 2;
170
                    break;
0 ignored issues
show
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
171
                    case '*/':
172
                        $source = true;
173
                        continue 2;
174
                    break;
0 ignored issues
show
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
175
                }
176
177
                if ($source === true) {
178
                    if (preg_match('/\$config\[\'(.+?)\'\]\s+=\s+([^;].+)/', $line, $matches)) {
179
                        $source = array(
180
                            $matches[1],
181
                            $matches[2]
182
                        );
183
                    } else {
184
                        $source = array();
185
                    }
186
187
                    $file['comments'][] = array_merge($this->parse_comment($block), array('source' => $source));
188
189
                    $block  = null;
190
                    $source = false;
191
                } elseif (is_string($block)) {
192
                    $block .= $line;
193
                }
194
            }
195
        }
196
197
        return $file;
198
    }
199
200
    /**
201
     * @param null|string $block
202
     */
203
    protected function parse_comment($block)
204
    {
205
        if (($block = trim($block)) == '') {
206
            return $block;
207
        }
208
209
        // Explode the lines into an array and trim them
210
        $block = array_map('trim', explode("\n", $block));
211
212
        if (current($block) === '/**') {
213
            // Remove comment opening
214
            array_shift($block);
215
        }
216
217
        if (end($block) === '*/') {
218
            // Remove comment closing
219
            array_pop($block);
220
        }
221
222
        // Start comment
223
        $comment = array();
224
225
        while ($line = array_shift($block)) {
226
            // Remove * from the line
227
            $line = trim(substr($line, 2));
228
229
            if ($line[0] === '$' and substr($line, -1) === '$') {
230
                // Skip SVN property inserts
231
                continue;
232
            }
233
234
            if ($line[0] === '@') {
235
                if (preg_match('/^@(.+?)\s+(.+)$/', $line, $matches)) {
236
                    $comment[$matches[1]][] = $matches[2];
237
                }
238
            } else {
239
                $comment['about'][] = $line;
240
            }
241
        }
242
243
        if (! empty($comment['about'])) {
244
            $token = '';
245
            $block = '';
246
            $about = '';
247
248
            foreach ($comment['about'] as $line) {
249
                if (strpos($line, '`') !== false) {
250
                    $line = preg_replace('/`([^`].+?)`/', '<tt>$1</tt>', $line);
251
                }
252
253
                if (substr($line, 0, 2) === '- ') {
254
                    if ($token !== 'ul') {
255
                        $about .= $this->comment_block($token, $block);
256
                        $block  = '';
257
                    }
258
259
                    $token = 'ul';
260
                    $line  = '<li>'.trim(substr($line, 2)).'</li>'."\n";
261
                } elseif (preg_match('/(.+?)\s+-\s+(.+)/', $line, $matches)) {
262
                    if ($token !== 'dl') {
263
                        $about .= $this->comment_block($token, $block);
264
                        $block  = '';
265
                    }
266
267
                    $token = 'dl';
268
                    $line = '<dt>'.$matches[1].'</dt>'."\n".'<dd>'.$matches[2].'</dd>'."\n";
269
                } else {
270
                    $token = 'p';
271
                    $line .= ' ';
272
                }
273
274
                if (trim($line) === '') {
275
                    $about .= $this->comment_block($token, $block);
276
                    $block = '';
277
                } else {
278
                    $block .= $line;
279
                }
280
            }
281
282
            if (! empty($block)) {
283
                $about .= $this->comment_block($token, $block);
284
            }
285
286
            $comment['about'] = $about;
287
        }
288
289
        return $comment;
290
    }
291
292
    /**
293
     * @param string $token
294
     * @param string $block
295
     */
296
    protected function comment_block($token, $block)
297
    {
298
        if (empty($token) or empty($block)) {
299
            return '';
300
        }
301
302
        $block = trim($block);
303
304
        if ($block[0] === '<') {
305
            // Insert newlines before and after the block
306
            $block = "\n".$block."\n";
307
        }
308
309
        return '<'.$token.'>'.$block.'</'.$token.'>'."\n";
310
    }
311
312
    /**
313
     * @param string $class
314
     */
315
    protected function parse_class($class)
316
    {
317
        // Use reflection to find information
318
        $reflection = new ReflectionClass($class);
319
320
        // Class definition
321
        $class = array(
322
            'name'       => $reflection->getName(),
0 ignored issues
show
Consider using $reflection->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
323
            'comment'    => $this->parse_comment($reflection->getDocComment()),
324
            'final'      => $reflection->isFinal(),
325
            'abstract'   => $reflection->isAbstract(),
326
            'interface'  => $reflection->isInterface(),
327
            'extends'    => '',
328
            'implements' => array(),
329
            'methods'    => array()
330
        );
331
332
        if ($implements = $reflection->getInterfaces()) {
333
            foreach ($implements as $interface) {
334
                // Get implemented interfaces
335
                $class['implements'][] = $interface->getName();
0 ignored issues
show
Consider using $interface->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
336
            }
337
        }
338
339
        if ($parent = $reflection->getParentClass()) {
340
            // Get parent class
341
            $class['extends'] = $parent->getName();
342
        }
343
344
        if ($methods = $reflection->getMethods()) {
345
            foreach ($methods as $method) {
346
                // Don't try to document internal methods
347
                if ($method->isInternal()) {
348
                    continue;
349
                }
350
351
                $class['methods'][] = array(
352
                    'name'       => $method->getName(),
0 ignored issues
show
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
353
                    'comment'    => $this->parse_comment($method->getDocComment()),
354
                    'class'      => $class['name'],
355
                    'final'      => $method->isFinal(),
356
                    'static'     => $method->isStatic(),
357
                    'abstract'   => $method->isAbstract(),
358
                    'visibility' => $this->visibility($method),
359
                    'parameters' => $this->parameters($method)
360
                );
361
            }
362
        }
363
364
        return $class;
365
    }
366
367
    /**
368
     * Finds the parameters for a ReflectionMethod.
369
     *
370
     * @param   object   ReflectionMethod
371
     * @return  array
372
     */
373
    protected function parameters(ReflectionMethod $method)
374
    {
375
        $params = array();
376
377
        if ($parameters = $method->getParameters()) {
378
            foreach ($parameters as $param) {
379
                // Parameter data
380
                $data = array(
381
                    'name' => $param->getName()
0 ignored issues
show
Consider using $param->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
382
                );
383
384
                if ($param->isOptional()) {
385
                    // Set default value
386
                    $data['default'] = $param->getDefaultValue();
387
                }
388
389
                $params[] = $data;
390
            }
391
        }
392
393
        return $params;
394
    }
395
396
    /**
397
     * Finds the visibility of a ReflectionMethod.
398
     *
399
     * @param   object   ReflectionMethod
400
     * @return  string
0 ignored issues
show
Should the return type not be string|false?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
401
     */
402
    protected function visibility(ReflectionMethod $method)
403
    {
404
        $vis = array_flip(Reflection::getModifierNames($method->getModifiers()));
405
406
        if (isset($vis['public'])) {
407
            return 'public';
408
        }
409
410
        if (isset($vis['protected'])) {
411
            return 'protected';
412
        }
413
414
        if (isset($vis['private'])) {
415
            return 'private';
416
        }
417
418
        return false;
419
    }
420
} // End Kodoc
421
class Kodoc_xCore
422
{
423
424
    /**
425
     * libraries, helpers, etc
426
     */
427
    protected $files = array(
428
        'core'      => array(),
429
        'config'    => array(),
430
        'helpers'   => array(),
431
        'libraries' => array(),
432
        'models'    => array(),
433
        'views'     => array()
434
    );
435
436
    /**
437
     * $classes[$name] = array $properties;
438
     * $properties = array
439
     * (
440
     *     'drivers'    => array $drivers
441
     *     'properties' => array $properties
442
     *     'methods'    => array $methods
443
     * )
444
     */
445
    protected $classes = array();
446
447
    // Holds the current data until parsed
448
    protected $current_class;
449
450
    // $packages[$name] = array $files;
451
    protected $packages = array();
452
453
    // PHP's visibility types
454
    protected static $php_visibility = array(
455
        'public',
456
        'protected',
457
        'private'
458
    );
459
460
    public function __construct()
461
    {
462
        if (isset(self::$php_visibility[0])) {
463
            self::$php_visibility = array_flip(self::$php_visibility);
464
        }
465
466
        foreach ($this->files as $type => $files) {
467
            foreach (Kohana::list_files($type) as $filepath) {
468
                // Get the filename with no extension
469
                $file = pathinfo($filepath, PATHINFO_FILENAME);
470
471
                // Skip indexes and drivers
472
                if ($file === 'index' or strpos($filepath, 'libraries/drivers') !== false) {
473
                    continue;
474
                }
475
476
                // Add the file
477
                $this->files[$type][$file] = $filepath;
478
479
                // Parse the file
480
                $this->parse_file($filepath);
481
            }
482
        }
483
484
        Kohana::log('debug', 'Kodoc Library initialized');
485
    }
486
487
    public function get_docs($format = 'html')
0 ignored issues
show
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
488
    {
489
        switch ($format) {
490
            default:
0 ignored issues
show
default: // Generate...)->render(); break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
491
                // Generate HTML via a View
492
                $docs = new View('kodoc_html');
493
494
                $docs->set('classes', $this->classes)->render();
495
            break;
496
        }
497
498
        return $docs;
0 ignored issues
show
The variable $docs seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
499
    }
500
501
    protected function parse_file($file)
502
    {
503
        $file = fopen($file, 'r');
504
505
        $i = 1;
506
        while ($line = fgets($file)) {
507
            if (substr(trim($line), 0, 2) === '/*') {
508
                // Reset vars
509
                unset($current_doc, $section, $p);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $p. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
510
511
                // Prepare for a new doc section
512
                $current_doc = array();
513
                $closing_tag = '*/';
514
515
                $current_block = 'description';
516
                $p = 0;
517
518
                // Assign the current doc
519
                $this->current_doc =& $current_doc;
0 ignored issues
show
The property current_doc does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
520
            } elseif (isset($closing_tag)) {
521
                if (substr(trim($line), 0, 1) === '*') {
522
                    // Remove the leading comment
523
                    $line = substr(ltrim($line), 2);
524
525
                    if (preg_match('/^([a-z ]+):/i', $line, $matches)) {
526
                        $current_block = trim($matches[1]);
527
                    } elseif (isset($current_doc)) {
528
                        $line = ltrim($line);
529
530
                        if (preg_match('/^\-\s+(.+)/', $line, $matches)) {
531
                            // An unordered list
532
                            $current_doc['html'][$current_block]['ul'][] = $matches[1];
0 ignored issues
show
The variable $current_block 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...
533
                        } elseif (preg_match('/^[0-9]+\.\s+(.+)/', $line, $matches)) {
534
                            // An ordered list
535
                            $current_doc['html'][$current_block]['ol'][] = $matches[1];
536
                        } elseif (preg_match('/^([a-zA-Z ]+)\s+\-\s+(.+)/', $line, $matches)) {
537
                            // Definition list
538
                            $current_doc['html'][$current_block]['dl'][trim($matches[1])] = trim($matches[2]);
539
                        } else {
540
                            if (trim($line) === '') {
541
                                // Start a new paragraph
542
                                $p++;
0 ignored issues
show
The variable $p 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...
543
                            } else {
544
                                // Make sure the current paragraph is set
545
                                if (! isset($current_doc['html'][$current_block]['p'][$p])) {
546
                                    $current_doc['html'][$current_block]['p'][$p] = '';
547
                                }
548
549
                                // Add to the current paragraph
550
                                $current_doc['html'][$current_block]['p'][$p] .= str_replace("\n", ' ', $line);
551
                            }
552
                        }
553
                    }
554
                } else {
555
                    switch (substr(trim($line), 0, 2)) {
556
                        case '//':
557
                        case '* ': break;
558
                        default:
559
                            $line = trim($line);
560
561
                            if ($this->is_function($line) or $this->is_property($line) or $this->is_class($line)) {
562
                                $clear = null;
563
                                $this->current_doc =& $clear;
564
565
                                // Restarts searching
566
                                unset($closing_tag, $current_doc);
567
                            }
568
                        break;
569
                    }
570
                }
571
            }
572
573
            $i++;
574
        }
575
576
        // Close the file
577
        fclose($file);
578
    }
579
580
    /**
581
     * Method:
582
     *  Checks if a line is a class, and parses the data out.
583
     *
584
     * Parameters:
585
     *  line - a line from a file
586
     *
587
     * Returns:
588
     *  TRUE or FALSE.
589
     * @param string $line
590
     */
591
    protected function is_class($line)
592
    {
593
        if (strpos($line, 'class') === false) {
594
            return false;
595
        }
596
597
        $line = explode(' ', trim($line));
598
599
        $class = array(
600
            'name'    => '',
601
            'final'   => false,
602
            'extends' => false,
603
            'drivers' => false
604
        );
605
606
        if (current($line) === 'final') {
607
            $class['final'] = (bool) array_shift($line);
608
        }
609
610
        if (current($line) === 'class') {
611
            // Remove "class"
612
            array_shift($line);
613
614
            $name = array_shift($line);
615
        }
616
617
        if (count($line) > 1) {
618
            // Remove "extends"
619
            array_shift($line);
620
621
            $class['extends'] = array_shift($line);
622
        }
623
624
        if (isset($name)) {
625
            // Add the class into the docs
626
            $this->classes[$name] = array_merge($this->current_doc, $class);
627
628
            // Set the current class
629
            $this->current_class =& $this->classes[$name];
630
631
            return true;
632
        }
633
634
        return false;
635
    }
636
637
    /**
638
     * Method:
639
     *  Checks if a line is a property, and parses the data out.
640
     *
641
     * Parameters:
642
     *  line - a line from a file
643
     *
644
     * Returns:
645
     *  TRUE or FALSE.
646
     * @param string $line
647
     */
648
    protected function is_property($line)
649
    {
650
        static $preg_vis;
651
652
        if ($preg_vis === null) {
653
            $preg_vis = 'var|'.implode('|', self::$php_visibility);
654
        }
655
656
        if (strpos($line, '$') === false or ! preg_match('/^(?:'.$preg_vis.')/', $line)) {
657
            return false;
658
        }
659
660
        $line = explode(' ', $line);
661
662
        $var = array(
663
            'visibility' => false,
664
            'static'     => false,
665
            'default'    => null
666
        );
667
668
        if (current($line) === 'var') {
669
            // Remove "var"
670
            array_shift($line);
671
672
            $var['visibility'] = 'public';
673
        }
674
675
        if (current($line) === 'static') {
676
            $var['visibility'] = (bool) array_shift($line);
677
        }
678
679
        // If the visibility is not set, this is not a
680
        if ($var['visibility'] === false) {
681
            return false;
682
        }
683
684
        if (substr(current($line), 0, 1) === '$') {
685
            $name = substr(array_shift($line), 1);
686
            $name = rtrim($name, ';');
687
        }
688
689
        if (count($line) and current($line) === '=') {
690
            array_shift($line);
691
692
            $var['default'] = implode(' ', $line);
693
        }
694
695
        if (isset($name)) {
696
            // Add property to class
697
            $this->current_class['properties'][$name] = array_merge($this->current_doc, $var);
698
699
            return true;
700
        }
701
702
        return false;
703
    }
704
705
    /**
706
     * Method:
707
     *  Checks if a line is a function, and parses the data out.
708
     *
709
     * Parameters:
710
     *  line - a line from a file
711
     *
712
     * Returns:
713
     *  TRUE or FALSE.
714
     * @param string $line
715
     */
716
    protected function is_function($line)
717
    {
718
        if (strpos($line, 'function') === false) {
719
            return false;
720
        }
721
722
        $line = explode(' ', trim(strtolower($line)));
723
724
        $func = array(
725
            'final'      => false,
726
            'visibility' => 'public',
727
            'static'     => false,
728
        );
729
730
        if (current($line) === 'final') {
731
            $func['final'] = true;
732
        }
733
734
        if (isset(self::$php_visibility[current($line)])) {
735
            $func['visibility'] = array_shift($line);
736
        }
737
738
        if (current($line) === 'static') {
739
            $func['static'] = (bool) array_shift($line);
740
        }
741
742
        if (current($line) === 'function') {
743
            // Remove "function"
744
            array_shift($line);
745
746
            // Get name
747
            $name = array_shift($line);
748
749
            // Remove arguments
750
            if (strpos($name, '(') !== false) {
751
                $name = current(explode('(', $name, 2));
752
            }
753
754
            // Register the method
755
            $this->current_class['methods'][$name] = array_merge($this->current_doc, $func);
756
757
            return true;
758
        }
759
760
        return false;
761
    }
762
} // End Kodoc
763