Kohana::user_agent()   F
last analyzed

Complexity

Conditions 25
Paths 251

Size

Total Lines 90
Code Lines 50

Duplication

Lines 18
Ratio 20 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 25
eloc 50
c 2
b 1
f 0
nc 251
nop 2
dl 18
loc 90
rs 3.5466

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php defined('SYSPATH') or die('No direct access allowed.');
2
/**
3
 * Provides Kohana-specific helper functions. This is where the magic happens!
4
 *
5
 * $Id: Kohana.php 4372 2009-05-28 17:00:34Z ixmatus $
6
 *
7
 * @package    Core
8
 * @author     Kohana Team
9
 * @copyright  (c) 2007-2008 Kohana Team
10
 * @license    http://kohanaphp.com/license.html
11
 */
12
final class Kohana
13
{
14
15
    // The singleton instance of the controller
16
    public static $instance;
17
18
    // Output buffering level
19
    private static $buffer_level;
20
21
    // Will be set to TRUE when an exception is caught
22
    public static $has_error = false;
23
24
    // The final output that will displayed by Kohana
25
    public static $output = '';
26
27
    // The current user agent
28
    public static $user_agent;
29
30
    // The current locale
31
    public static $locale;
32
33
    // Configuration
34
    private static $configuration;
35
36
    // Include paths
37
    private static $include_paths;
38
39
    // Logged messages
40
    private static $log;
41
42
    // Cache lifetime
43
    private static $cache_lifetime;
44
45
    // Log levels
46
    private static $log_levels = array(
47
        'error' => 1,
48
        'alert' => 2,
49
        'info'  => 3,
50
        'debug' => 4,
51
    );
52
53
    // Internal caches and write status
54
    private static $internal_cache = array();
55
    private static $write_cache;
56
    private static $internal_cache_path;
57
    private static $internal_cache_key;
58
    private static $internal_cache_encrypt;
59
60
    /**
61
     * Sets up the PHP environment. Adds error/exception handling, output
62
     * buffering, and adds an auto-loading method for loading classes.
63
     *
64
     * This method is run immediately when this file is loaded, and is
65
     * benchmarked as environment_setup.
66
     *
67
     * For security, this function also destroys the $_REQUEST global variable.
68
     * Using the proper global (GET, POST, COOKIE, etc) is inherently more secure.
69
     * The recommended way to fetch a global variable is using the Input library.
70
     * @see http://www.php.net/globals
71
     *
72
     * @return  void
73
     */
74
    public static function setup()
0 ignored issues
show
Coding Style introduced by
setup uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
75
    {
76
        static $run;
77
78
        // This function can only be run once
79
        if ($run === true) {
80
            return;
81
        }
82
83
        // Start the environment setup benchmark
84
        Benchmark::start(SYSTEM_BENCHMARK.'_environment_setup');
85
86
        // Define Kohana error constant
87
        define('E_KOHANA', 42);
88
89
        // Define 404 error constant
90
        define('E_PAGE_NOT_FOUND', 43);
91
92
        // Define database error constant
93
        define('E_DATABASE_ERROR', 44);
94
95
        if (self::$cache_lifetime = self::config('core.internal_cache')) {
96
            // Are we using encryption for caches?
97
            self::$internal_cache_encrypt    = self::config('core.internal_cache_encrypt');
98
99
            if (self::$internal_cache_encrypt===true) {
100
                self::$internal_cache_key = self::config('core.internal_cache_key');
101
102
                // Be sure the key is of acceptable length for the mcrypt algorithm used
103
                self::$internal_cache_key = substr(self::$internal_cache_key, 0, 24);
104
            }
105
106
            // Set the directory to be used for the internal cache
107
            if (! self::$internal_cache_path = self::config('core.internal_cache_path')) {
108
                self::$internal_cache_path = APPPATH.'cache/';
109
            }
110
111
            // Load cached configuration and language files
112
            self::$internal_cache['configuration'] = self::cache('configuration', self::$cache_lifetime);
113
            self::$internal_cache['language']      = self::cache('language', self::$cache_lifetime);
114
115
            // Load cached file paths
116
            self::$internal_cache['find_file_paths'] = self::cache('find_file_paths', self::$cache_lifetime);
117
118
            // Enable cache saving
119
            Event::add('system.shutdown', array(__CLASS__, 'internal_cache_save'));
120
        }
121
122
        // Disable notices and "strict" errors
123
        $ER = error_reporting(~E_NOTICE & ~E_STRICT);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ER. 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...
124
125
        // Set the user agent
126
        self::$user_agent = (! empty($_SERVER['HTTP_USER_AGENT']) ? trim($_SERVER['HTTP_USER_AGENT']) : '');
127
128
        if (function_exists('date_default_timezone_set')) {
129
            $timezone = self::config('locale.timezone');
130
131
            // Set default timezone, due to increased validation of date settings
132
            // which cause massive amounts of E_NOTICEs to be generated in PHP 5.2+
133
            date_default_timezone_set(empty($timezone) ? date_default_timezone_get() : $timezone);
134
        }
135
136
        // Restore error reporting
137
        error_reporting($ER);
138
139
        // Start output buffering
140
        ob_start(array(__CLASS__, 'output_buffer'));
141
142
        // Save buffering level
143
        self::$buffer_level = ob_get_level();
144
145
        // Set autoloader
146
        spl_autoload_register(array('Kohana', 'auto_load'));
147
148
        // Set error handler
149
        set_error_handler(array('Kohana', 'exception_handler'));
150
151
        // Set exception handler
152
        set_exception_handler(array('Kohana', 'exception_handler'));
153
154
        // Send default text/html UTF-8 header
155
        header('Content-Type: text/html; charset=UTF-8');
156
157
        // Load locales
158
        $locales = self::config('locale.language');
159
160
        // Make first locale UTF-8
161
        $locales[0] .= '.UTF-8';
162
163
        // Set locale information
164
        self::$locale = setlocale(LC_ALL, $locales);
165
166
        if (self::$configuration['core']['log_threshold'] > 0) {
167
            // Set the log directory
168
            self::log_directory(self::$configuration['core']['log_directory']);
169
170
            // Enable log writing at shutdown
171
            register_shutdown_function(array(__CLASS__, 'log_save'));
172
        }
173
174
        // Enable Kohana routing
175
        Event::add('system.routing', array('Router', 'find_uri'));
176
        Event::add('system.routing', array('Router', 'setup'));
177
178
        // Enable Kohana controller initialization
179
        Event::add('system.execute', array('Kohana', 'instance'));
180
181
        // Enable Kohana 404 pages
182
        Event::add('system.404', array('Kohana', 'show_404'));
183
184
        // Enable Kohana output handling
185
        Event::add('system.shutdown', array('Kohana', 'shutdown'));
186
187
        if (self::config('core.enable_hooks') === true) {
188
            // Find all the hook files
189
            $hooks = self::list_files('hooks', true);
190
191
            foreach ($hooks as $file) {
192
                // Load the hook
193
                include $file;
194
            }
195
        }
196
197
        // Setup is complete, prevent it from being run again
198
        $run = true;
199
200
        // Stop the environment setup routine
201
        Benchmark::stop(SYSTEM_BENCHMARK.'_environment_setup');
202
    }
203
204
    /**
205
     * Loads the controller and initializes it. Runs the pre_controller,
206
     * post_controller_constructor, and post_controller events. Triggers
207
     * a system.404 event when the route cannot be mapped to a controller.
208
     *
209
     * This method is benchmarked as controller_setup and controller_execution.
210
     *
211
     * @return  object  instance of controller
212
     */
213
    public static function & instance()
214
    {
215
        if (self::$instance === null) {
216
            Benchmark::start(SYSTEM_BENCHMARK.'_controller_setup');
217
218
            // Include the Controller file
219
            require Router::$controller_path;
220
221
            try {
222
                // Start validation of the controller
223
                $class = new ReflectionClass(ucfirst(Router::$controller).'_Controller');
224
            } catch (ReflectionException $e) {
225
                // Controller does not exist
226
                Event::run('system.404');
227
            }
228
229
            if ($class->isAbstract() or (IN_PRODUCTION and $class->getConstant('ALLOW_PRODUCTION') == false)) {
230
                // Controller is not allowed to run in production
231
                Event::run('system.404');
232
            }
233
234
            // Run system.pre_controller
235
            Event::run('system.pre_controller');
236
237
            // Create a new controller instance
238
            $controller = $class->newInstance();
239
240
            // Controller constructor has been executed
241
            Event::run('system.post_controller_constructor');
242
243
            try {
244
                // Load the controller method
245
                $method = $class->getMethod(Router::$method);
246
247
                // Method exists
248
                if (Router::$method[0] === '_') {
249
                    // Do not allow access to hidden methods
250
                    Event::run('system.404');
251
                }
252
253
                if ($method->isProtected() or $method->isPrivate()) {
254
                    // Do not attempt to invoke protected methods
255
                    throw new ReflectionException('protected controller method');
256
                }
257
258
                // Default arguments
259
                $arguments = Router::$arguments;
260
            } catch (ReflectionException $e) {
261
                // Use __call instead
262
                $method = $class->getMethod('__call');
263
264
                // Use arguments in __call format
265
                $arguments = array(Router::$method, Router::$arguments);
266
            }
267
268
            // Stop the controller setup benchmark
269
            Benchmark::stop(SYSTEM_BENCHMARK.'_controller_setup');
270
271
            // Start the controller execution benchmark
272
            Benchmark::start(SYSTEM_BENCHMARK.'_controller_execution');
273
274
            // Execute the controller method
275
            $method->invokeArgs($controller, $arguments);
276
277
            // Controller method has been executed
278
            Event::run('system.post_controller');
279
280
            // Stop the controller execution benchmark
281
            Benchmark::stop(SYSTEM_BENCHMARK.'_controller_execution');
282
        }
283
284
        return self::$instance;
285
    }
286
287
    /**
288
     * Get all include paths. APPPATH is the first path, followed by module
289
     * paths in the order they are configured, follow by the SYSPATH.
290
     *
291
     * @param   boolean  re-process the include paths
292
     * @return  array
293
     */
294
    public static function include_paths($process = false)
295
    {
296
        if ($process === true) {
297
            // Add APPPATH as the first path
298
            self::$include_paths = array(APPPATH);
299
300
            foreach (self::$configuration['core']['modules'] as $path) {
301
                if ($path = str_replace('\\', '/', realpath($path))) {
302
                    // Add a valid path
303
                    self::$include_paths[] = $path.'/';
304
                }
305
            }
306
307
            // Add SYSPATH as the last path
308
            self::$include_paths[] = SYSPATH;
309
        }
310
311
        return self::$include_paths;
312
    }
313
314
    /**
315
     * Get a config item or group.
316
     *
317
     * @param   string   item name
318
     * @param   boolean  force a forward slash (/) at the end of the item
319
     * @param   boolean  is the item required?
320
     * @return  string
321
     */
322
    public static function config($key, $slash = false, $required = true)
323
    {
324
        if (self::$configuration === null) {
325
            // Load core configuration
326
            self::$configuration['core'] = self::config_load('core');
327
328
            // Re-parse the include paths
329
            self::include_paths(true);
330
        }
331
332
        // Get the group name from the key
333
        $group = explode('.', $key, 2);
334
        $group = $group[0];
335
336
        if (! isset(self::$configuration[$group])) {
337
            // Load the configuration group
338
            self::$configuration[$group] = self::config_load($group, $required);
339
        }
340
341
        // Get the value of the key string
342
        $value = self::key_string(self::$configuration, $key);
343
344
        if ($slash === true and is_string($value) and $value !== '') {
345
            // Force the value to end with "/"
346
            $value = rtrim($value, '/').'/';
347
        }
348
349
        return $value;
350
    }
351
352
    /**
353
     * Sets a configuration item, if allowed.
354
     *
355
     * @param   string   config key string
356
     * @param   string   config value
357
     * @return  boolean
358
     */
359
    public static function config_set($key, $value)
360
    {
361
        // Do this to make sure that the config array is already loaded
362
        self::config($key);
363
364
        if (substr($key, 0, 7) === 'routes.') {
365
            // Routes cannot contain sub keys due to possible dots in regex
366
            $keys = explode('.', $key, 2);
367
        } else {
368
            // Convert dot-noted key string to an array
369
            $keys = explode('.', $key);
370
        }
371
372
        // Used for recursion
373
        $conf =& self::$configuration;
374
        $last = count($keys) - 1;
375
376
        foreach ($keys as $i => $k) {
377
            if ($i === $last) {
378
                $conf[$k] = $value;
379
            } else {
380
                $conf =& $conf[$k];
381
            }
382
        }
383
384
        if ($key === 'core.modules') {
385
            // Reprocess the include paths
386
            self::include_paths(true);
387
        }
388
389
        return true;
390
    }
391
392
    /**
393
     * Load a config file.
394
     *
395
     * @param   string   config filename, without extension
396
     * @param   boolean  is the file required?
397
     * @return  array
398
     */
399
    public static function config_load($name, $required = true)
400
    {
401
        if ($name === 'core') {
402
            // Load the application configuration file
403
            require APPPATH.'config/config'.EXT;
404
405
            if (! isset($config['site_domain'])) {
0 ignored issues
show
Bug introduced by
The variable $config seems to never exist, and therefore isset should always return false. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
406
                // Invalid config file
407
                die('Your Kohana application configuration file is not valid.');
0 ignored issues
show
Coding Style Compatibility introduced by
The method config_load() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
408
            }
409
410
            return $config;
411
        }
412
413
        if (isset(self::$internal_cache['configuration'][$name])) {
414
            return self::$internal_cache['configuration'][$name];
415
        }
416
417
        // Load matching configs
418
        $configuration = array();
419
420
        if ($files = self::find_file('config', $name, $required)) {
421
            foreach ($files as $file) {
0 ignored issues
show
Bug introduced by
The expression $files of type string is not traversable.
Loading history...
422
                require $file;
423
424
                if (isset($config) and is_array($config)) {
425
                    // Merge in configuration
426
                    $configuration = array_merge($configuration, $config);
427
                }
428
            }
429
        }
430
431
        if (! isset(self::$write_cache['configuration'])) {
432
            // Cache has changed
433
            self::$write_cache['configuration'] = true;
434
        }
435
436
        return self::$internal_cache['configuration'][$name] = $configuration;
437
    }
438
439
    /**
440
     * Clears a config group from the cached configuration.
441
     *
442
     * @param   string  config group
443
     * @return  void
444
     */
445
    public static function config_clear($group)
446
    {
447
        // Remove the group from config
448
        unset(self::$configuration[$group], self::$internal_cache['configuration'][$group]);
449
450
        if (! isset(self::$write_cache['configuration'])) {
451
            // Cache has changed
452
            self::$write_cache['configuration'] = true;
453
        }
454
    }
455
456
    /**
457
     * Add a new message to the log.
458
     *
459
     * @param   string  type of message
460
     * @param   string  message text
461
     * @return  void
462
     */
463
    public static function log($type, $message)
464
    {
465
        if (self::$log_levels[$type] <= self::$configuration['core']['log_threshold']) {
466
            $message = array(date('Y-m-d H:i:s P'), $type, $message);
467
468
            // Run the system.log event
469
            Event::run('system.log', $message);
470
471
            self::$log[] = $message;
472
        }
473
    }
474
475
    /**
476
     * Save all currently logged messages.
477
     *
478
     * @return  void
479
     */
480
    public static function log_save()
481
    {
482
        if (empty(self::$log) or self::$configuration['core']['log_threshold'] < 1) {
483
            return;
484
        }
485
486
        // Filename of the log
487
        $filename = self::log_directory().date('Y-m-d').'.log'.EXT;
488
489
        if (! is_file($filename)) {
490
            // Write the SYSPATH checking header
491
            file_put_contents($filename,
492
                '<?php defined(\'SYSPATH\') or die(\'No direct script access.\'); ?>'.PHP_EOL.PHP_EOL);
493
494
            // Prevent external writes
495
            chmod($filename, 0644);
496
        }
497
498
        // Messages to write
499
        $messages = array();
500
501
        do {
502
            // Load the next mess
503
            list($date, $type, $text) = array_shift(self::$log);
504
505
            // Add a new message line
506
            $messages[] = $date.' --- '.$type.': '.$text;
507
        } while (! empty(self::$log));
508
509
        // Write messages to log file
510
        file_put_contents($filename, implode(PHP_EOL, $messages).PHP_EOL, FILE_APPEND);
511
    }
512
513
    /**
514
     * Get or set the logging directory.
515
     *
516
     * @param   string  new log directory
517
     * @return  string
518
     */
519
    public static function log_directory($dir = null)
520
    {
521
        static $directory;
522
523
        if (! empty($dir)) {
524
            // Get the directory path
525
            $dir = realpath($dir);
526
527
            if (is_dir($dir) and is_writable($dir)) {
528
                // Change the log directory
529
                $directory = str_replace('\\', '/', $dir).'/';
530
            } else {
531
                // Log directory is invalid
532
                throw new Kohana_Exception('core.log_dir_unwritable', $dir);
533
            }
534
        }
535
536
        return $directory;
537
    }
538
539
    /**
540
     * Load data from a simple cache file. This should only be used internally,
541
     * and is NOT a replacement for the Cache library.
542
     *
543
     * @param   string   unique name of cache
544
     * @param   integer  expiration in seconds
545
     * @param string $name
546
     * @param string $lifetime
547
     * @return  mixed
548
     */
549
    public static function cache($name, $lifetime)
550
    {
551
        if ($lifetime > 0) {
552
            $path = self::$internal_cache_path.'kohana_'.$name;
553
554
            if (is_file($path)) {
555
                // Check the file modification time
556
                if ((time() - filemtime($path)) < $lifetime) {
557
                    // Cache is valid! Now, do we need to decrypt it?
558
                    if (self::$internal_cache_encrypt===true) {
559
                        $data        = file_get_contents($path);
560
561
                        $iv_size    = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
562
                        $iv            = mcrypt_create_iv($iv_size, MCRYPT_RAND);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $iv. 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...
563
564
                        $decrypted_text    = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, self::$internal_cache_key, $data, MCRYPT_MODE_ECB, $iv);
565
566
                        $cache    = unserialize($decrypted_text);
567
568
                        // If the key changed, delete the cache file
569
                        if (!$cache) {
570
                            unlink($path);
571
                        }
572
573
                        // If cache is false (as above) return NULL, otherwise, return the cache
574
                        return ($cache ? $cache : null);
575
                    } else {
576
                        return unserialize(file_get_contents($path));
577
                    }
578
                } else {
579
                    // Cache is invalid, delete it
580
                    unlink($path);
581
                }
582
            }
583
        }
584
585
        // No cache found
586
        return null;
587
    }
588
589
    /**
590
     * Save data to a simple cache file. This should only be used internally, and
591
     * is NOT a replacement for the Cache library.
592
     *
593
     * @param   string   cache name
594
     * @param   mixed    data to cache
595
     * @param   integer  expiration in seconds
596
     * @return  boolean
597
     */
598
    public static function cache_save($name, $data, $lifetime)
599
    {
600
        if ($lifetime < 1) {
601
            return false;
602
        }
603
604
        $path = self::$internal_cache_path.'kohana_'.$name;
605
606
        if ($data === null) {
607
            // Delete cache
608
            return (is_file($path) and unlink($path));
609
        } else {
610
            // Using encryption? Encrypt the data when we write it
611
            if (self::$internal_cache_encrypt===true) {
612
                // Encrypt and write data to cache file
613
                $iv_size    = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
614
                $iv            = mcrypt_create_iv($iv_size, MCRYPT_RAND);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $iv. 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...
615
616
                // Serialize and encrypt!
617
                $encrypted_text    = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, self::$internal_cache_key, serialize($data), MCRYPT_MODE_ECB, $iv);
618
619
                return (bool) file_put_contents($path, $encrypted_text);
620
            } else {
621
                // Write data to cache file
622
                return (bool) file_put_contents($path, serialize($data));
623
            }
624
        }
625
    }
626
627
    /**
628
     * Kohana output handler. Called during ob_clean, ob_flush, and their variants.
629
     *
630
     * @param   string  current output buffer
631
     * @return  string
632
     */
633
    public static function output_buffer($output)
634
    {
635
        // Could be flushing, so send headers first
636
        if (! Event::has_run('system.send_headers')) {
637
            // Run the send_headers event
638
            Event::run('system.send_headers');
639
        }
640
641
        self::$output    = $output;
642
643
        // Set and return the final output
644
        return self::$output;
645
    }
646
647
    /**
648
     * Closes all open output buffers, either by flushing or cleaning, and stores the Kohana
649
     * output buffer for display during shutdown.
650
     *
651
     * @param   boolean  disable to clear buffers, rather than flushing
652
     * @return  void
653
     */
654
    public static function close_buffers($flush = true)
655
    {
656
        if (ob_get_level() >= self::$buffer_level) {
657
            // Set the close function
658
            $close = ($flush === true) ? 'ob_end_flush' : 'ob_end_clean';
659
            if (ob_get_level() >= self::$buffer_level) {
660
                // Flush or clean the buffer
661
                $close();
662
            }
663
664
            // Store the Kohana output buffer
665
            ob_end_clean();
666
        }
667
    }
668
669
    /**
670
     * Triggers the shutdown of Kohana by closing the output buffer, runs the system.display event.
671
     *
672
     * @return  void
673
     */
674
    public static function shutdown()
675
    {
676
        // Close output buffers
677
        //self::close_buffers(TRUE);
678
679
        // Run the output event
680
        Event::run('system.display', self::$output);
681
682
        // Render the final output
683
        self::render(self::$output);
684
    }
685
686
    /**
687
     * Inserts global Kohana variables into the generated output and prints it.
688
     *
689
     * @param   string  final output that will displayed
690
     * @return  void
691
     */
692
    public static function render($output)
0 ignored issues
show
Coding Style introduced by
render uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
693
    {
694
        if (self::config('core.render_stats') === true) {
695
            // Fetch memory usage in MB
696
            $memory = function_exists('memory_get_usage') ? (memory_get_usage() / 1024 / 1024) : 0;
697
698
            // Fetch benchmark for page execution time
699
            $benchmark = Benchmark::get(SYSTEM_BENCHMARK.'_total_execution');
700
701
            // Replace the global template variables
702
            $output = str_replace(
703
                array(
704
                    '{kohana_version}',
705
                    '{kohana_codename}',
706
                    '{execution_time}',
707
                    '{memory_usage}',
708
                    '{included_files}',
709
                ),
710
                array(
711
                    KOHANA_VERSION,
712
                    KOHANA_CODENAME,
713
                    $benchmark['time'],
714
                    number_format($memory, 2).'MB',
715
                    count(get_included_files()),
716
                ),
717
                $output
718
            );
719
        }
720
721
        if ($level = self::config('core.output_compression') and ini_get('output_handler') !== 'ob_gzhandler' and (int) ini_get('zlib.output_compression') === 0) {
722
            if ($level < 1 or $level > 9) {
723
                // Normalize the level to be an integer between 1 and 9. This
724
                // step must be done to prevent gzencode from triggering an error
725
                $level = max(1, min($level, 9));
726
            }
727
728
            if (stripos(@$_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false) {
729
                $compress = 'gzip';
730
            } elseif (stripos(@$_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate') !== false) {
731
                $compress = 'deflate';
732
            }
733
        }
734
735
        if (isset($compress) and $level > 0) {
736
            switch ($compress) {
737
                case 'gzip':
738
                    // Compress output using gzip
739
                    $output = gzencode($output, $level);
740
                break;
741
                case 'deflate':
742
                    // Compress output using zlib (HTTP deflate)
743
                    $output = gzdeflate($output, $level);
744
                break;
745
            }
746
747
            // This header must be sent with compressed content to prevent
748
            // browser caches from breaking
749
            header('Vary: Accept-Encoding');
750
751
            // Send the content encoding header
752
            header('Content-Encoding: '.$compress);
753
754
            // Sending Content-Length in CGI can result in unexpected behavior
755
            if (stripos(PHP_SAPI, 'cgi') === false) {
756
                header('Content-Length: '.strlen($output));
757
            }
758
        }
759
760
        echo $output;
761
    }
762
763
    /**
764
     * Displays a 404 page.
765
     *
766
     * @throws  Kohana_404_Exception
767
     * @param   string  URI of page
768
     * @param   string  custom template
769
     * @return  void
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use NoType.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
770
     */
771
    public static function show_404($page = false, $template = false)
772
    {
773
        throw new Kohana_404_Exception($page, $template);
774
    }
775
776
    /**
777
     * Dual-purpose PHP error and exception handler. Uses the kohana_error_page
778
     * view to display the message.
779
     *
780
     * @param   integer|object  exception object or error code
781
     * @param   string          error message
782
     * @param   string          filename
783
     * @param   integer         line number
784
     * @return  void
785
     */
786
    public static function exception_handler($exception, $message = null, $file = null, $line = null)
787
    {
788
        try {
789
            // PHP errors have 5 args, always
790
            $PHP_ERROR = (func_num_args() === 5);
791
792
            // Test to see if errors should be displayed
793
            if ($PHP_ERROR and (error_reporting() & $exception) === 0) {
794
                return;
795
            }
796
797
            // This is useful for hooks to determine if a page has an error
798
            self::$has_error = true;
799
800
            // Error handling will use exactly 5 args, every time
801
            if ($PHP_ERROR) {
802
                $code     = $exception;
803
                $type     = 'PHP Error';
804
                $template = 'kohana_error_page';
805
            } else {
806
                $code     = $exception->getCode();
807
                $type     = get_class($exception);
808
                $message  = $exception->getMessage();
809
                $file     = $exception->getFile();
810
                $line     = $exception->getLine();
811
                $template = ($exception instanceof Kohana_Exception) ? $exception->getTemplate() : 'kohana_error_page';
812
            }
813
814
            if (is_numeric($code)) {
815
                $codes = self::lang('errors');
816
817
                if (! empty($codes[$code])) {
818
                    list($level, $error, $description) = $codes[$code];
819
                } else {
820
                    $level = 1;
821
                    $error = $PHP_ERROR ? 'Unknown Error' : get_class($exception);
822
                    $description = '';
823
                }
824
            } else {
825
                // Custom error message, this will never be logged
826
                $level = 5;
827
                $error = $code;
828
                $description = '';
829
            }
830
831
            // Remove the DOCROOT from the path, as a security precaution
832
            $file = str_replace('\\', '/', realpath($file));
833
            $file = preg_replace('|^'.preg_quote(DOCROOT).'|', '', $file);
834
835
            if ($level <= self::$configuration['core']['log_threshold']) {
836
                // Log the error
837
                self::log('error', self::lang('core.uncaught_exception', $type, $message, $file, $line));
838
            }
839
840
            if ($PHP_ERROR) {
841
                $description = self::lang('errors.'.E_RECOVERABLE_ERROR);
842
                $description = is_array($description) ? $description[2] : '';
843
844
                if (! headers_sent()) {
845
                    // Send the 500 header
846
                    header('HTTP/1.1 500 Internal Server Error');
847
                }
848
            } else {
849
                if (method_exists($exception, 'sendHeaders') and ! headers_sent()) {
850
                    // Send the headers if they have not already been sent
851
                    $exception->sendHeaders();
852
                }
853
            }
854
855
            // Close all output buffers except for Kohana
856
            while (ob_get_level() > self::$buffer_level) {
857
                ob_end_clean();
858
            }
859
860
            // Test if display_errors is on
861
            if (self::$configuration['core']['display_errors'] === true) {
862
                if (! IN_PRODUCTION and $line != false) {
863
                    // Remove the first entry of debug_backtrace(), it is the exception_handler call
864
                    $trace = $PHP_ERROR ? array_slice(debug_backtrace(), 1) : $exception->getTrace();
865
866
                    // Beautify backtrace
867
                    $trace = self::backtrace($trace);
868
                }
869
870
                // Load the error
871
                require self::find_file('views', empty($template) ? 'kohana_error_page' : $template);
872
            } else {
873
                // Get the i18n messages
874
                $error   = self::lang('core.generic_error');
875
                $message = self::lang('core.errors_disabled', url::site(), url::site(Router::$current_uri));
876
877
                // Load the errors_disabled view
878
                require self::find_file('views', 'kohana_error_disabled');
879
            }
880
881
            if (! Event::has_run('system.shutdown')) {
882
                // Run the shutdown even to ensure a clean exit
883
                Event::run('system.shutdown');
884
            }
885
886
            // Turn off error reporting
887
            error_reporting(0);
888
            exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method exception_handler() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
889
        } catch (Exception $e) {
890
            if (IN_PRODUCTION) {
891
                die('Fatal Error');
0 ignored issues
show
Coding Style Compatibility introduced by
The method exception_handler() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
892
            } else {
893
                die('Fatal Error: '.$e->getMessage().' File: '.$e->getFile().' Line: '.$e->getLine());
0 ignored issues
show
Coding Style Compatibility introduced by
The method exception_handler() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
894
            }
895
        }
896
    }
897
898
    /**
899
     * Provides class auto-loading.
900
     *
901
     * @throws  Kohana_Exception
902
     * @param   string  name of class
903
     * @param string $class
904
     * @return  bool
905
     */
906
    public static function auto_load($class)
907
    {
908
        if (class_exists($class, false)) {
909
            return true;
910
        }
911
912
        if (($suffix = strrpos($class, '_')) > 0) {
913
            // Find the class suffix
914
            $suffix = substr($class, $suffix + 1);
915
        } else {
916
            // No suffix
917
            $suffix = false;
918
        }
919
920
        if ($suffix === 'Core') {
921
            $type = 'libraries';
922
            $file = substr($class, 0, -5);
923
        } elseif ($suffix === 'Controller') {
924
            $type = 'controllers';
925
            // Lowercase filename
926
            $file = strtolower(substr($class, 0, -11));
927
        } elseif ($suffix === 'Model') {
928
            $type = 'models';
929
            // Lowercase filename
930
            $file = strtolower(substr($class, 0, -6));
931
        } elseif ($suffix === 'Driver') {
932
            $type = 'libraries/drivers';
933
            $file = str_replace('_', '/', substr($class, 0, -7));
934
        } else {
935
            // This could be either a library or a helper, but libraries must
936
            // always be capitalized, so we check if the first character is
937
            // uppercase. If it is, we are loading a library, not a helper.
938
            $type = ($class[0] < 'a') ? 'libraries' : 'helpers';
939
            $file = $class;
940
        }
941
942
        if ($filename = self::find_file($type, $file)) {
943
            // Load the class
944
            require $filename;
945
        } else {
946
            // The class could not be found
947
            return false;
948
        }
949
950
        if ($filename = self::find_file($type, self::$configuration['core']['extension_prefix'].$class)) {
951
            // Load the class extension
952
            require $filename;
953
        } elseif ($suffix !== 'Core' and class_exists($class.'_Core', false)) {
954
            // Class extension to be evaluated
955
            $extension = 'class '.$class.' extends '.$class.'_Core { }';
956
957
            // Start class analysis
958
            $core = new ReflectionClass($class.'_Core');
959
960
            if ($core->isAbstract()) {
961
                // Make the extension abstract
962
                $extension = 'abstract '.$extension;
963
            }
964
965
            // Transparent class extensions are handled using eval. This is
966
            // a disgusting hack, but it gets the job done.
967
            eval($extension);
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
968
        }
969
970
        return true;
971
    }
972
973
    /**
974
     * Find a resource file in a given directory. Files will be located according
975
     * to the order of the include paths. Configuration and i18n files will be
976
     * returned in reverse order.
977
     *
978
     * @throws  Kohana_Exception  if file is required and not found
979
     * @param   string   directory to search in
980
     * @param   string   filename to look for (without extension)
981
     * @param   boolean  file required
982
     * @param   string   file extension
983
     * @return  string    if the type is config, i18n or l10n
984
     * @return  string   if the file is found
985
     * @return  string    if the file is not found
986
     */
987
    public static function find_file($directory, $filename, $required = false, $ext = false)
988
    {
989
        // NOTE: This test MUST be not be a strict comparison (===), or empty
990
        // extensions will be allowed!
991
        if ($ext == '') {
992
            // Use the default extension
993
            $ext = EXT;
994
        } else {
995
            // Add a period before the extension
996
            $ext = '.'.$ext;
997
        }
998
999
        // Search path
1000
        $search = $directory.'/'.$filename.$ext;
1001
1002
        if (isset(self::$internal_cache['find_file_paths'][$search])) {
1003
            return self::$internal_cache['find_file_paths'][$search];
1004
        }
1005
1006
        // Load include paths
1007
        $paths = self::$include_paths;
1008
1009
        // Nothing found, yet
1010
        $found = null;
1011
1012
        if ($directory === 'config' or $directory === 'i18n') {
1013
            // Search in reverse, for merging
1014
            $paths = array_reverse($paths);
1015
1016
            foreach ($paths as $path) {
1017
                if (is_file($path.$search)) {
1018
                    // A matching file has been found
1019
                    $found[] = $path.$search;
1020
                }
1021
            }
1022
        } else {
1023
            foreach ($paths as $path) {
1024
                if (is_file($path.$search)) {
1025
                    // A matching file has been found
1026
                    $found = $path.$search;
1027
1028
                    // Stop searching
1029
                    break;
1030
                }
1031
            }
1032
        }
1033
1034
        if ($found === null) {
1035
            if ($required === true) {
1036
                // Directory i18n key
1037
                $directory = 'core.'.inflector::singular($directory);
1038
1039
                // If the file is required, throw an exception
1040
                throw new Kohana_Exception('core.resource_not_found', self::lang($directory), $filename);
1041
            } else {
1042
                // Nothing was found, return FALSE
1043
                $found = false;
1044
            }
1045
        }
1046
1047
        if (! isset(self::$write_cache['find_file_paths'])) {
1048
            // Write cache at shutdown
1049
            self::$write_cache['find_file_paths'] = true;
1050
        }
1051
1052
        return self::$internal_cache['find_file_paths'][$search] = $found;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression self::$internal_cache['f...hs'][$search] = $found; of type false|string adds false to the return on line 1052 which is incompatible with the return type documented by Kohana::find_file of type string. It seems like you forgot to handle an error condition.
Loading history...
1053
    }
1054
1055
    /**
1056
     * Lists all files and directories in a resource path.
1057
     *
1058
     * @param   string   directory to search
1059
     * @param   boolean  list all files to the maximum depth?
1060
     * @param   string   full path to search (used for recursion, *never* set this manually)
1061
     * @return  array    filenames and directories
1062
     */
1063
    public static function list_files($directory, $recursive = false, $path = false)
1064
    {
1065
        $files = array();
1066
1067
        if ($path === false) {
1068
            $paths = array_reverse(self::include_paths());
1069
1070
            foreach ($paths as $path) {
1071
                // Recursively get and merge all files
1072
                $files = array_merge($files, self::list_files($directory, $recursive, $path.$directory));
0 ignored issues
show
Documentation introduced by
$path . $directory is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1073
            }
1074
        } else {
1075
            $path = rtrim($path, '/').'/';
1076
1077
            if (is_readable($path)) {
1078
                $items = (array) glob($path.'*');
1079
1080
                if (! empty($items)) {
1081
                    foreach ($items as $index => $item) {
1082
                        $files[] = $item = str_replace('\\', '/', $item);
1083
1084
                        // Handle recursion
1085
                        if (is_dir($item) and $recursive == true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1086
                            // Filename should only be the basename
1087
                            $item = pathinfo($item, PATHINFO_BASENAME);
1088
1089
                            // Append sub-directory search
1090
                            $files = array_merge($files, self::list_files($directory, true, $path.$item));
0 ignored issues
show
Documentation introduced by
$path . $item is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1091
                        }
1092
                    }
1093
                }
1094
            }
1095
        }
1096
1097
        return $files;
1098
    }
1099
1100
    /**
1101
     * Fetch an i18n language item.
1102
     *
1103
     * @param   string  language key to fetch
1104
     * @param   array   additional information to insert into the line
1105
     * @return  string  i18n language string, or the requested key if the i18n item is not found
1106
     */
1107
    public static function lang($key, $args = array())
1108
    {
1109
        // Extract the main group from the key
1110
        $group = explode('.', $key, 2);
1111
        $group = $group[0];
1112
1113
        // Get locale name
1114
        $locale = self::config('locale.language.0');
1115
1116
        if (! isset(self::$internal_cache['language'][$locale][$group])) {
1117
            // Messages for this group
1118
            $messages = array();
1119
1120
            if ($files = self::find_file('i18n', $locale.'/'.$group)) {
1121
                foreach ($files as $file) {
0 ignored issues
show
Bug introduced by
The expression $files of type string is not traversable.
Loading history...
1122
                    include $file;
1123
1124
                    // Merge in configuration
1125
                    if (! empty($lang) and is_array($lang)) {
1126
                        foreach ($lang as $k => $v) {
1127
                            $messages[$k] = $v;
1128
                        }
1129
                    }
1130
                }
1131
            }
1132
1133
            if (! isset(self::$write_cache['language'])) {
1134
                // Write language cache
1135
                self::$write_cache['language'] = true;
1136
            }
1137
1138
            self::$internal_cache['language'][$locale][$group] = $messages;
1139
        }
1140
1141
        // Get the line from cache
1142
        $line = self::key_string(self::$internal_cache['language'][$locale], $key);
1143
1144
        if ($line === null) {
1145
            self::log('error', 'Missing i18n entry '.$key.' for language '.$locale);
1146
1147
            // Return the key string as fallback
1148
            return $key;
1149
        }
1150
1151
        if (is_string($line) and func_num_args() > 1) {
1152
            $args = array_slice(func_get_args(), 1);
1153
1154
            // Add the arguments into the line
1155
            $line = vsprintf($line, is_array($args[0]) ? $args[0] : $args);
1156
        }
1157
1158
        return $line;
1159
    }
1160
1161
    /**
1162
     * Returns the value of a key, defined by a 'dot-noted' string, from an array.
1163
     *
1164
     * @param   array   array to search
1165
     * @param   string  dot-noted string: foo.bar.baz
1166
     * @return  string  if the key is found
1167
     * @return  void    if the key is not found
1168
     */
1169
    public static function key_string($array, $keys)
1170
    {
1171
        if (empty($array)) {
1172
            return null;
1173
        }
1174
1175
        // Prepare for loop
1176
        $keys = explode('.', $keys);
1177
1178
        do {
1179
            // Get the next key
1180
            $key = array_shift($keys);
1181
1182
            if (isset($array[$key])) {
1183
                if (is_array($array[$key]) and ! empty($keys)) {
1184
                    // Dig down to prepare the next loop
1185
                    $array = $array[$key];
1186
                } else {
1187
                    // Requested key was found
1188
                    return $array[$key];
1189
                }
1190
            } else {
1191
                // Requested key is not set
1192
                break;
1193
            }
1194
        } while (! empty($keys));
1195
1196
        return null;
1197
    }
1198
1199
    /**
1200
     * Sets values in an array by using a 'dot-noted' string.
1201
     *
1202
     * @param   array   array to set keys in (reference)
1203
     * @param   string  dot-noted string: foo.bar.baz
1204
     * @return  mixed   fill value for the key
1205
     * @return  void
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array|null.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
1206
     */
1207
    public static function key_string_set(& $array, $keys, $fill = null)
1208
    {
1209
        if (is_object($array) and ($array instanceof ArrayObject)) {
1210
            // Copy the array
1211
            $array_copy = $array->getArrayCopy();
1212
1213
            // Is an object
1214
            $array_object = true;
1215
        } else {
1216
            if (! is_array($array)) {
1217
                // Must always be an array
1218
                $array = (array) $array;
1219
            }
1220
1221
            // Copy is a reference to the array
1222
            $array_copy =& $array;
1223
        }
1224
1225
        if (empty($keys)) {
1226
            return $array;
1227
        }
1228
1229
        // Create keys
1230
        $keys = explode('.', $keys);
1231
1232
        // Create reference to the array
1233
        $row =& $array_copy;
1234
1235
        for ($i = 0, $end = count($keys) - 1; $i <= $end; $i++) {
1236
            // Get the current key
1237
            $key = $keys[$i];
1238
1239
            if (! isset($row[$key])) {
1240
                if (isset($keys[$i + 1])) {
1241
                    // Make the value an array
1242
                    $row[$key] = array();
1243
                } else {
1244
                    // Add the fill key
1245
                    $row[$key] = $fill;
1246
                }
1247
            } elseif (isset($keys[$i + 1])) {
1248
                // Make the value an array
1249
                $row[$key] = (array) $row[$key];
1250
            }
1251
1252
            // Go down a level, creating a new row reference
1253
            $row =& $row[$key];
1254
        }
1255
1256
        if (isset($array_object)) {
1257
            // Swap the array back in
1258
            $array->exchangeArray($array_copy);
0 ignored issues
show
Bug introduced by
The method exchangeArray cannot be called on $array (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
1259
        }
1260
    }
1261
1262
    /**
1263
     * Retrieves current user agent information:
1264
     * keys:  browser, version, platform, mobile, robot, referrer, languages, charsets
1265
     * tests: is_browser, is_mobile, is_robot, accept_lang, accept_charset
1266
     *
1267
     * @param   string   key or test name
1268
     * @param   string   used with "accept" tests: user_agent(accept_lang, en)
1269
     * @return  array    languages and charsets
1270
     * @return  string   all other keys
1271
     * @return  boolean  all tests
1272
     */
1273
    public static function user_agent($key = 'agent', $compare = null)
0 ignored issues
show
Coding Style introduced by
user_agent uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1274
    {
1275
        static $info;
1276
1277
        // Return the raw string
1278
        if ($key === 'agent') {
1279
            return self::$user_agent;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return self::$user_agent; (string) is incompatible with the return type documented by Kohana::user_agent of type array.

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...
1280
        }
1281
1282
        if ($info === null) {
1283
            // Parse the user agent and extract basic information
1284
            $agents = self::config('user_agents');
1285
1286
            foreach ($agents as $type => $data) {
0 ignored issues
show
Bug introduced by
The expression $agents of type string is not traversable.
Loading history...
1287
                foreach ($data as $agent => $name) {
1288
                    if (stripos(self::$user_agent, $agent) !== false) {
1289
                        if ($type === 'browser' and preg_match('|'.preg_quote($agent).'[^0-9.]*+([0-9.][0-9.a-z]*)|i', self::$user_agent, $match)) {
1290
                            // Set the browser version
1291
                            $info['version'] = $match[1];
1292
                        }
1293
1294
                        // Set the agent name
1295
                        $info[$type] = $name;
1296
                        break;
1297
                    }
1298
                }
1299
            }
1300
        }
1301
1302
        if (empty($info[$key])) {
1303
            switch ($key) {
1304
                case 'is_robot':
1305
                case 'is_browser':
1306
                case 'is_mobile':
1307
                    // A boolean result
1308
                    $return = ! empty($info[substr($key, 3)]);
1309
                break;
1310 View Code Duplication
                case 'languages':
0 ignored issues
show
Duplication introduced by
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...
1311
                    $return = array();
1312
                    if (! empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
1313
                        if (preg_match_all('/[-a-z]{2,}/', strtolower(trim($_SERVER['HTTP_ACCEPT_LANGUAGE'])), $matches)) {
1314
                            // Found a result
1315
                            $return = $matches[0];
1316
                        }
1317
                    }
1318
                break;
1319 View Code Duplication
                case 'charsets':
0 ignored issues
show
Duplication introduced by
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...
1320
                    $return = array();
1321
                    if (! empty($_SERVER['HTTP_ACCEPT_CHARSET'])) {
1322
                        if (preg_match_all('/[-a-z0-9]{2,}/', strtolower(trim($_SERVER['HTTP_ACCEPT_CHARSET'])), $matches)) {
1323
                            // Found a result
1324
                            $return = $matches[0];
1325
                        }
1326
                    }
1327
                break;
1328
                case 'referrer':
1329
                    if (! empty($_SERVER['HTTP_REFERER'])) {
1330
                        // Found a result
1331
                        $return = trim($_SERVER['HTTP_REFERER']);
1332
                    }
1333
                break;
1334
            }
1335
1336
            // Cache the return value
1337
            isset($return) and $info[$key] = $return;
1338
        }
1339
1340
        if (! empty($compare)) {
1341
            // The comparison must always be lowercase
1342
            $compare = strtolower($compare);
1343
1344
            switch ($key) {
1345
                case 'accept_lang':
1346
                    // Check if the lange is accepted
1347
                    return in_array($compare, self::user_agent('languages'));
0 ignored issues
show
Bug Best Practice introduced by
The return type of return in_array($compare...er_agent('languages')); (boolean) is incompatible with the return type documented by Kohana::user_agent of type array.

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...
1348
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
1349
                case 'accept_charset':
1350
                    // Check if the charset is accepted
1351
                    return in_array($compare, self::user_agent('charsets'));
0 ignored issues
show
Bug Best Practice introduced by
The return type of return in_array($compare...ser_agent('charsets')); (boolean) is incompatible with the return type documented by Kohana::user_agent of type array.

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...
1352
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
1353
                default:
1354
                    // Invalid comparison
1355
                    return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Kohana::user_agent of type array.

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...
1356
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
1357
            }
1358
        }
1359
1360
        // Return the key, if set
1361
        return isset($info[$key]) ? $info[$key] : null;
1362
    }
1363
1364
    /**
1365
     * Quick debugging of any variable. Any number of parameters can be set.
1366
     *
1367
     * @return  string
0 ignored issues
show
Documentation introduced by
Should the return type not be null|string?

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...
1368
     */
1369
    public static function debug()
1370
    {
1371
        if (func_num_args() === 0) {
1372
            return;
1373
        }
1374
1375
        // Get params
1376
        $params = func_get_args();
1377
        $output = array();
1378
1379
        foreach ($params as $var) {
1380
            $output[] = '<pre>('.gettype($var).') '.html::specialchars(print_r($var, true)).'</pre>';
1381
        }
1382
1383
        return implode("\n", $output);
1384
    }
1385
1386
    /**
1387
     * Displays nice backtrace information.
1388
     * @see http://php.net/debug_backtrace
1389
     *
1390
     * @param   array   backtrace generated by an exception or debug_backtrace
1391
     * @return  string
0 ignored issues
show
Documentation introduced by
Should the return type not be null|string?

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...
1392
     */
1393
    public static function backtrace($trace)
1394
    {
1395
        if (! is_array($trace)) {
1396
            return;
1397
        }
1398
1399
        // Final output
1400
        $output = array();
1401
1402
        foreach ($trace as $entry) {
1403
            $temp = '<li>';
1404
1405
            if (isset($entry['file'])) {
1406
                $temp .= self::lang('core.error_file_line', preg_replace('!^'.preg_quote(DOCROOT).'!', '', $entry['file']), $entry['line']);
1407
            }
1408
1409
            $temp .= '<pre>';
1410
1411
            if (isset($entry['class'])) {
1412
                // Add class and call type
1413
                $temp .= $entry['class'].$entry['type'];
1414
            }
1415
1416
            // Add function
1417
            $temp .= $entry['function'].'( ';
1418
1419
            // Add function args
1420
            if (isset($entry['args']) and is_array($entry['args'])) {
1421
                // Separator starts as nothing
1422
                $sep = '';
1423
1424
                while ($arg = array_shift($entry['args'])) {
1425
                    if (is_string($arg) and is_file($arg)) {
1426
                        // Remove docroot from filename
1427
                        $arg = preg_replace('!^'.preg_quote(DOCROOT).'!', '', $arg);
1428
                    }
1429
1430
                    $temp .= $sep.html::specialchars(print_r($arg, true));
1431
1432
                    // Change separator to a comma
1433
                    $sep = ', ';
1434
                }
1435
            }
1436
1437
            $temp .= ' )</pre></li>';
1438
1439
            $output[] = $temp;
1440
        }
1441
1442
        return '<ul class="backtrace">'.implode("\n", $output).'</ul>';
1443
    }
1444
1445
    /**
1446
     * Saves the internal caches: configuration, include paths, etc.
1447
     *
1448
     * @return  boolean
1449
     */
1450
    public static function internal_cache_save()
1451
    {
1452
        if (! is_array(self::$write_cache)) {
1453
            return false;
1454
        }
1455
1456
        // Get internal cache names
1457
        $caches = array_keys(self::$write_cache);
1458
1459
        // Nothing written
1460
        $written = false;
1461
1462
        foreach ($caches as $cache) {
1463
            if (isset(self::$internal_cache[$cache])) {
1464
                // Write the cache file
1465
                self::cache_save($cache, self::$internal_cache[$cache], self::$configuration['core']['internal_cache']);
1466
1467
                // A cache has been written
1468
                $written = true;
1469
            }
1470
        }
1471
1472
        return $written;
1473
    }
1474
} // End Kohana
1475
1476
/**
1477
 * Creates a generic i18n exception.
1478
 */
1479
class Kohana_Exception extends Exception
1480
{
1481
1482
    // Template file
1483
    protected $template = 'kohana_error_page';
1484
1485
    // Header
1486
    protected $header = false;
1487
1488
    // Error code
1489
    protected $code = E_KOHANA;
1490
1491
    /**
1492
     * Set exception message.
1493
     *
1494
     * @param  string  i18n language key for the message
1495
     * @param  array   addition line parameters
1496
     * @param string $error
1497
     */
1498
    public function __construct($error)
1499
    {
1500
        $args = array_slice(func_get_args(), 1);
1501
1502
        // Fetch the error message
1503
        $message = Kohana::lang($error, $args);
1504
1505
        if ($message === $error or empty($message)) {
1506
            // Unable to locate the message for the error
1507
            $message = 'Unknown Exception: '.$error;
1508
        }
1509
1510
        // Sets $this->message the proper way
1511
        parent::__construct($message);
1512
    }
1513
1514
    /**
1515
     * Magic method for converting an object to a string.
1516
     *
1517
     * @return  string  i18n message
1518
     */
1519
    public function __toString()
1520
    {
1521
        return (string) $this->message;
1522
    }
1523
1524
    /**
1525
     * Fetch the template name.
1526
     *
1527
     * @return  string
1528
     */
1529
    public function getTemplate()
1530
    {
1531
        return $this->template;
1532
    }
1533
1534
    /**
1535
     * Sends an Internal Server Error header.
1536
     *
1537
     * @return  void
1538
     */
1539
    public function sendHeaders()
1540
    {
1541
        // Send the 500 header
1542
        header('HTTP/1.1 500 Internal Server Error');
1543
    }
1544
} // End Kohana Exception
1545
1546
/**
1547
 * Creates a custom exception.
1548
 */
1549
class Kohana_User_Exception extends Kohana_Exception
1550
{
1551
1552
    /**
1553
     * Set exception title and message.
1554
     *
1555
     * @param   string  exception title string
1556
     * @param   string  exception message string
1557
     * @param   string  custom error template
1558
     * @param string $title
1559
     * @param null|string $message
1560
     */
1561
    public function __construct($title, $message, $template = false)
1562
    {
1563
        Exception::__construct($message);
1564
1565
        $this->code = $title;
1566
1567
        if ($template !== false) {
1568
            $this->template = $template;
0 ignored issues
show
Documentation Bug introduced by
The property $template was declared of type string, but $template is of type boolean. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
1569
        }
1570
    }
1571
} // End Kohana PHP Exception
1572
1573
/**
1574
 * Creates a Page Not Found exception.
1575
 */
1576
class Kohana_404_Exception extends Kohana_Exception
1577
{
1578
    protected $code = E_PAGE_NOT_FOUND;
1579
1580
    /**
1581
     * Set internal properties.
1582
     *
1583
     * @param  string  URL of page
1584
     * @param  string  custom error template
1585
     */
1586
    public function __construct($page = false, $template = false)
1587
    {
1588
        if ($page === false) {
1589
            // Construct the page URI using Router properties
1590
            $page = Router::$current_uri.Router::$url_suffix.Router::$query_string;
1591
        }
1592
1593
        Exception::__construct(Kohana::lang('core.page_not_found', $page));
1594
1595
        $this->template = $template;
0 ignored issues
show
Documentation Bug introduced by
The property $template was declared of type string, but $template is of type boolean. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
1596
    }
1597
1598
    /**
1599
     * Sends "File Not Found" headers, to emulate server behavior.
1600
     *
1601
     * @return void
1602
     */
1603
    public function sendHeaders()
1604
    {
1605
        // Send the 404 header
1606
        header('HTTP/1.1 404 File Not Found');
1607
    }
1608
} // End Kohana 404 Exception
1609