diff --git a/.gitignore b/.gitignore index 11fb6d67c4f..a035c2b2702 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ application/logs/* user_guide_src/build/* user_guide_src/cilexer/build/* user_guide_src/cilexer/dist/* -user_guide_src/cilexer/pycilexer.egg-info/* \ No newline at end of file +user_guide_src/cilexer/pycilexer.egg-info/* +/vendor/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 2496def0bc8..ab0aa5616c6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,9 +3,10 @@ language: php php: - 5.3 - 5.4 - + env: - DB=mysql + - DB=mysqli - DB=pgsql - DB=sqlite - DB=pdo/mysql @@ -13,11 +14,10 @@ env: - DB=pdo/sqlite before_script: - - curl -s http://getcomposer.org/installer | php - - php composer.phar install + - composer install --dev --no-progress - sh -c "if [ '$DB' = 'pgsql' ] || [ '$DB' = 'pdo/pgsql' ]; then psql -c 'DROP DATABASE IF EXISTS ci_test;' -U postgres; fi" - sh -c "if [ '$DB' = 'pgsql' ] || [ '$DB' = 'pdo/pgsql' ]; then psql -c 'create database ci_test;' -U postgres; fi" - - sh -c "if [ '$DB' = 'mysql' ] || [ '$DB' = 'pdo/mysql' ]; then mysql -e 'create database IF NOT EXISTS ci_test;'; fi" + - sh -c "if [ '$DB' = 'mysql' ] || [ '$DB' = 'mysqli' ] || [ '$DB' = 'pdo/mysql' ]; then mysql -e 'create database IF NOT EXISTS ci_test;'; fi" script: phpunit --coverage-text --configuration tests/travis/$DB.phpunit.xml diff --git a/application/config/autoload.php b/application/config/autoload.php index b3e63cbf60e..5a20c943a2d 100644 --- a/application/config/autoload.php +++ b/application/config/autoload.php @@ -18,7 +18,7 @@ * * @package CodeIgniter * @author EllisLab Dev Team - * @copyright Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/) + * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/) * @license http://opensource.org/licenses/AFL-3.0 Academic Free License (AFL 3.0) * @link http://codeigniter.com * @since Version 1.0 @@ -46,16 +46,17 @@ | | 1. Packages | 2. Libraries -| 3. Helper files -| 4. Custom config files -| 5. Language files -| 6. Models +| 3. Drivers +| 4. Helper files +| 5. Custom config files +| 6. Language files +| 7. Models | */ /* | ------------------------------------------------------------------- -| Auto-load Packges +| Auto-load Packages | ------------------------------------------------------------------- | Prototype: | @@ -75,12 +76,28 @@ | | Prototype: | -| $autoload['libraries'] = array('database', 'session', 'xmlrpc'); +| $autoload['libraries'] = array('database', 'email', 'xmlrpc'); */ $autoload['libraries'] = array(); +/* +| ------------------------------------------------------------------- +| Auto-load Drivers +| ------------------------------------------------------------------- +| These classes are located in the system/libraries folder or in your +| application/libraries folder within their own subdirectory. They +| offer multiple interchangeable driver options. +| +| Prototype: +| +| $autoload['drivers'] = array('session', 'cache'); +*/ + +$autoload['drivers'] = array(); + + /* | ------------------------------------------------------------------- | Auto-load Helper Files @@ -131,8 +148,12 @@ | ------------------------------------------------------------------- | Prototype: | -| $autoload['model'] = array('model1', 'model2'); +| $autoload['model'] = array('first_model', 'second_model'); +| +| You can also supply an alternative model name to be assigned +| in the controller: | +| $autoload['model'] = array('first_model' => 'first'); */ $autoload['model'] = array(); diff --git a/application/config/config.php b/application/config/config.php index 28fc406d185..0608348c627 100644 --- a/application/config/config.php +++ b/application/config/config.php @@ -18,7 +18,7 @@ * * @package CodeIgniter * @author EllisLab Dev Team - * @copyright Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/) + * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/) * @license http://opensource.org/licenses/AFL-3.0 Academic Free License (AFL 3.0) * @link http://codeigniter.com * @since Version 1.0 @@ -62,11 +62,11 @@ | URI string. The default setting of 'AUTO' works for most servers. | If your links do not seem to work, try one of the other delicious flavors: | -| 'AUTO' Default - auto detects -| 'PATH_INFO' Uses the PATH_INFO -| 'QUERY_STRING' Uses the QUERY_STRING -| 'REQUEST_URI' Uses the REQUEST_URI -| 'ORIG_PATH_INFO' Uses the ORIG_PATH_INFO +| 'AUTO' Default - auto detects +| 'CLI' or 'argv' Uses $_SERVER['argv'] (for php-cli only) +| 'PATH_INFO' Uses $_SERVER['PATH_INFO'] +| 'REQUEST_URI' Uses $_SERVER['REQUEST_URI'] +| 'QUERY_STRING' Uses $_SERVER['QUERY_STRING'] | */ $config['uri_protocol'] = 'AUTO'; @@ -224,6 +224,20 @@ */ $config['log_path'] = ''; +/* +|-------------------------------------------------------------------------- +| Log File Extension +|-------------------------------------------------------------------------- +| +| The default filename extension for log files. The default 'php' allows for +| protecting the log files via basic scripting, when they are to be stored +| under a publicly accessible directory. +| +| Note: Leaving it blank will default to 'php'. +| +*/ +$config['log_file_extension'] = ''; + /* |-------------------------------------------------------------------------- | Date Format for Logs @@ -241,7 +255,7 @@ |-------------------------------------------------------------------------- | | Leave this BLANK unless you would like to set something other than the default -| system/cache/ folder. Use a full server path with trailing slash. +| application/cache/ folder. Use a full server path with trailing slash. | */ $config['cache_path'] = ''; @@ -265,6 +279,9 @@ | Session Variables |-------------------------------------------------------------------------- | +| 'sess_driver' = the driver to load: cookie (Classic), native (PHP sessions), +| or your custom driver name +| 'sess_valid_drivers' = additional valid drivers which may be loaded | 'sess_cookie_name' = the name you want for the cookie | 'sess_expiration' = the number of SECONDS you want the session to last. | by default sessions last 7200 seconds (two hours). Set to zero for no expiration. @@ -278,6 +295,8 @@ | 'sess_time_to_update' = how many seconds between CI refreshing Session Information | */ +$config['sess_driver'] = 'cookie'; +$config['sess_valid_drivers'] = array(); $config['sess_cookie_name'] = 'ci_session'; $config['sess_expiration'] = 7200; $config['sess_expire_on_close'] = FALSE; @@ -401,15 +420,19 @@ | Reverse Proxy IPs |-------------------------------------------------------------------------- | -| If your server is behind a reverse proxy, you must whitelist the proxy IP -| addresses from which CodeIgniter should trust the HTTP_X_FORWARDED_FOR -| header in order to properly identify the visitor's IP address. -| Comma-delimited, e.g. '10.0.1.200,10.0.1.201' +| If your server is behind a reverse proxy, you must whitelist the proxy +| IP addresses from which CodeIgniter should trust headers such as +| HTTP_X_FORWARDED_FOR and HTTP_CLIENT_IP in order to properly identify +| the visitor's IP address. | +| You can use both an array or a comma-separated list of proxy addresses, +| as well as specifying whole subnets. Here are a few examples: +| +| Comma-separated: '10.0.1.200,192.168.5.0/24' +| Array: array('10.0.1.200', '192.168.5.0/24') */ $config['proxy_ips'] = ''; - /* End of file config.php */ -/* Location: ./application/config/config.php */ +/* Location: ./application/config/config.php */ \ No newline at end of file diff --git a/application/config/constants.php b/application/config/constants.php index d22d2963e27..dc84712cde9 100644 --- a/application/config/constants.php +++ b/application/config/constants.php @@ -18,7 +18,7 @@ * * @package CodeIgniter * @author EllisLab Dev Team - * @copyright Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/) + * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/) * @license http://opensource.org/licenses/AFL-3.0 Academic Free License (AFL 3.0) * @link http://codeigniter.com * @since Version 1.0 @@ -52,14 +52,14 @@ | */ -define('FOPEN_READ', 'rb'); -define('FOPEN_READ_WRITE', 'r+b'); -define('FOPEN_WRITE_CREATE_DESTRUCTIVE', 'wb'); // truncates existing file data, use with care -define('FOPEN_READ_WRITE_CREATE_DESTRUCTIVE', 'w+b'); // truncates existing file data, use with care -define('FOPEN_WRITE_CREATE', 'ab'); -define('FOPEN_READ_WRITE_CREATE', 'a+b'); -define('FOPEN_WRITE_CREATE_STRICT', 'xb'); -define('FOPEN_READ_WRITE_CREATE_STRICT', 'x+b'); +define('FOPEN_READ', 'rb'); +define('FOPEN_READ_WRITE', 'r+b'); +define('FOPEN_WRITE_CREATE_DESTRUCTIVE', 'wb'); // truncates existing file data, use with care +define('FOPEN_READ_WRITE_CREATE_DESTRUCTIVE', 'w+b'); // truncates existing file data, use with care +define('FOPEN_WRITE_CREATE', 'ab'); +define('FOPEN_READ_WRITE_CREATE', 'a+b'); +define('FOPEN_WRITE_CREATE_STRICT', 'xb'); +define('FOPEN_READ_WRITE_CREATE_STRICT', 'x+b'); /* |-------------------------------------------------------------------------- @@ -73,6 +73,42 @@ */ define('SHOW_DEBUG_BACKTRACE', TRUE); +/* +|-------------------------------------------------------------------------- +| Exit Status Codes +|-------------------------------------------------------------------------- +| +| Used to indicate the conditions under which the script is exit()ing. +| While there is no universal standard for error codes, there are some +| broad conventions. Three such conventions are mentioned below, for +| those who wish to make use of them. The CodeIgniter defaults were +| chosen for the least overlap with these conventions, while still +| leaving room for others to be defined in future versions and user +| applications. +| +| The three main conventions used for determining exit status codes +| are as follows: +| +| Standard C/C++ Library (stdlibc): +| http://www.gnu.org/software/libc/manual/html_node/Exit-Status.html +| (This link also contains other GNU-specific conventions) +| BSD sysexits.h: +| http://www.gsp.com/cgi-bin/man.cgi?section=3&topic=sysexits +| Bash scripting: +| http://tldp.org/LDP/abs/html/exitcodes.html +| +*/ +define('EXIT_SUCCESS', 0); // no errors +define('EXIT_ERROR', 1); // generic error +define('EXIT_CONFIG', 3); // configuration error +define('EXIT_UNKNOWN_FILE', 4); // file not found +define('EXIT_UNKNOWN_CLASS', 5); // unknown class +define('EXIT_UNKNOWN_METHOD', 6); // unknown class member +define('EXIT_USER_INPUT', 7); // invalid user input +define('EXIT_DATABASE', 8); // database error +define('EXIT__AUTO_MIN', 9); // lowest automatically-assigned error code +define('EXIT__AUTO_MAX', 125); // highest automatically-assigned error code + /* End of file constants.php */ /* Location: ./application/config/constants.php */ \ No newline at end of file diff --git a/application/config/database.php b/application/config/database.php index bb0d87be0e1..f0b83975794 100644 --- a/application/config/database.php +++ b/application/config/database.php @@ -18,7 +18,7 @@ * * @package CodeIgniter * @author EllisLab Dev Team - * @copyright Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/) + * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/) * @license http://opensource.org/licenses/AFL-3.0 Academic Free License (AFL 3.0) * @link http://codeigniter.com * @since Version 1.0 @@ -43,7 +43,7 @@ | ['password'] The password used to connect to the database | ['database'] The name of the database you want to connect to | ['dbdriver'] The database driver. e.g.: mysqli. - Currently supported: +| Currently supported: | cubrid, ibase, mssql, mysql, mysqli, oci8, | odbc, pdo, postgre, sqlite, sqlite3, sqlsrv | ['dbprefix'] You can add an optional prefix, which will be added @@ -63,6 +63,8 @@ | Sites using Latin-1 or UTF-8 database character set and collation are unaffected. | ['swap_pre'] A default table prefix that should be swapped with the dbprefix | ['autoinit'] Whether or not to automatically initialize the database. +| ['encrypt'] Whether or not to use an encrypted connection. +| ['compress'] Whether or not to use client compression (MySQL only) | ['stricton'] TRUE/FALSE - forces 'Strict Mode' connections | - good for ensuring strict SQL while developing | ['failover'] array - A array with 0 or more data for connections if the main should fail. @@ -71,7 +73,7 @@ | make active. By default there is only one group (the 'default' group). | | The $query_builder variables lets you determine whether or not to load -| the query builder class +| the query builder class. */ $active_group = 'default'; @@ -93,6 +95,8 @@ 'dbcollat' => 'utf8_general_ci', 'swap_pre' => '', 'autoinit' => TRUE, + 'encrypt' => FALSE, + 'compress' => FALSE, 'stricton' => FALSE, 'failover' => array() ); diff --git a/application/config/doctypes.php b/application/config/doctypes.php index c7f5b55c277..b1a8959c25c 100644 --- a/application/config/doctypes.php +++ b/application/config/doctypes.php @@ -18,7 +18,7 @@ * * @package CodeIgniter * @author EllisLab Dev Team - * @copyright Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/) + * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/) * @license http://opensource.org/licenses/AFL-3.0 Academic Free License (AFL 3.0) * @link http://codeigniter.com * @since Version 1.0 @@ -26,26 +26,26 @@ */ $_doctypes = array( - 'xhtml11' => '', - 'xhtml1-strict' => '', - 'xhtml1-trans' => '', - 'xhtml1-frame' => '', - 'xhtml-basic11' => '', - 'html5' => '', - 'html4-strict' => '', - 'html4-trans' => '', - 'html4-frame' => '', - 'mathml1' => '', - 'mathml2' => '', - 'svg10' => '', - 'svg11' => '', - 'svg11-basic' => '', - 'svg11-tiny' => '', - 'xhtml-math-svg-xh' => '', - 'xhtml-math-svg-sh' => '', - 'xhtml-rdfa-1' => '', - 'xhtml-rdfa-2' => '' - ); + 'xhtml11' => '', + 'xhtml1-strict' => '', + 'xhtml1-trans' => '', + 'xhtml1-frame' => '', + 'xhtml-basic11' => '', + 'html5' => '', + 'html4-strict' => '', + 'html4-trans' => '', + 'html4-frame' => '', + 'mathml1' => '', + 'mathml2' => '', + 'svg10' => '', + 'svg11' => '', + 'svg11-basic' => '', + 'svg11-tiny' => '', + 'xhtml-math-svg-xh' => '', + 'xhtml-math-svg-sh' => '', + 'xhtml-rdfa-1' => '', + 'xhtml-rdfa-2' => '' +); /* End of file doctypes.php */ /* Location: ./application/config/doctypes.php */ \ No newline at end of file diff --git a/application/config/foreign_chars.php b/application/config/foreign_chars.php index 41de123dad0..ab98224f748 100644 --- a/application/config/foreign_chars.php +++ b/application/config/foreign_chars.php @@ -18,7 +18,7 @@ * * @package CodeIgniter * @author EllisLab Dev Team - * @copyright Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/) + * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/) * @license http://opensource.org/licenses/AFL-3.0 Academic Free License (AFL 3.0) * @link http://codeigniter.com * @since Version 1.0 @@ -40,46 +40,58 @@ '/Ä/' => 'Ae', '/Ü/' => 'Ue', '/Ö/' => 'Oe', - '/À|Á|Â|Ã|Ä|Å|Ǻ|Ā|Ă|Ą|Ǎ|Α|Ά/' => 'A', - '/à|á|â|ã|å|ǻ|ā|ă|ą|ǎ|ª|α|ά/' => 'a', + '/À|Á|Â|Ã|Ä|Å|Ǻ|Ā|Ă|Ą|Ǎ|Α|Ά|Ả|Ạ|Ầ|Ẫ|Ẩ|Ậ|Ằ|Ắ|Ẵ|Ẳ|Ặ|А/' => 'A', + '/à|á|â|ã|å|ǻ|ā|ă|ą|ǎ|ª|α|ά|ả|ạ|ầ|ấ|ẫ|ẩ|ậ|ằ|ắ|ẵ|ẳ|ặ|а/' => 'a', + '/Б/' => 'B', + '/б/' => 'b', '/Ç|Ć|Ĉ|Ċ|Č/' => 'C', '/ç|ć|ĉ|ċ|č/' => 'c', + '/Д/' => 'D', + '/д/' => 'd', '/Ð|Ď|Đ|Δ/' => 'Dj', '/ð|ď|đ|δ/' => 'dj', - '/È|É|Ê|Ë|Ē|Ĕ|Ė|Ę|Ě|Ε|Έ/' => 'E', - '/è|é|ê|ë|ē|ĕ|ė|ę|ě|έ|ε/' => 'e', - '/Ĝ|Ğ|Ġ|Ģ|Γ/' => 'G', - '/ĝ|ğ|ġ|ģ|γ/' => 'g', + '/È|É|Ê|Ë|Ē|Ĕ|Ė|Ę|Ě|Ε|Έ|Ẽ|Ẻ|Ẹ|Ề|Ế|Ễ|Ể|Ệ|Е|Э/' => 'E', + '/è|é|ê|ë|ē|ĕ|ė|ę|ě|έ|ε|ẽ|ẻ|ẹ|ề|ế|ễ|ể|ệ|е|э/' => 'e', + '/Ф/' => 'F', + '/ф/' => 'f', + '/Ĝ|Ğ|Ġ|Ģ|Γ|Г|Ґ/' => 'G', + '/ĝ|ğ|ġ|ģ|γ|г|ґ/' => 'g', '/Ĥ|Ħ/' => 'H', '/ĥ|ħ/' => 'h', - '/Ì|Í|Î|Ï|Ĩ|Ī|Ĭ|Ǐ|Į|İ|Η|Ή|Ί|Ι|Ϊ/' => 'I', - '/ì|í|î|ï|ĩ|ī|ĭ|ǐ|į|ı|η|ή|ί|ι|ϊ/' => 'i', + '/Ì|Í|Î|Ï|Ĩ|Ī|Ĭ|Ǐ|Į|İ|Η|Ή|Ί|Ι|Ϊ|Ỉ|Ị|И|Ы/' => 'I', + '/ì|í|î|ï|ĩ|ī|ĭ|ǐ|į|ı|η|ή|ί|ι|ϊ|ỉ|ị|и|ы|ї/' => 'i', '/Ĵ/' => 'J', '/ĵ/' => 'j', - '/Ķ|Κ/' => 'K', - '/ķ|κ/' => 'k', - '/Ĺ|Ļ|Ľ|Ŀ|Ł|Λ/' => 'L', - '/ĺ|ļ|ľ|ŀ|ł|λ/' => 'l', - '/Ñ|Ń|Ņ|Ň|Ν/' => 'N', - '/ñ|ń|ņ|ň|ʼn|ν/' => 'n', - '/Ò|Ó|Ô|Õ|Ō|Ŏ|Ǒ|Ő|Ơ|Ø|Ǿ|Ο|Ό|Ω|Ώ/' => 'O', - '/ò|ó|ô|õ|ō|ŏ|ǒ|ő|ơ|ø|ǿ|º|ο|ό|ω|ώ/' => 'o', - '/Ŕ|Ŗ|Ř|Ρ/' => 'R', - '/ŕ|ŗ|ř|ρ/' => 'r', - '/Ś|Ŝ|Ş|Ș|Š|Σ/' => 'S', - '/ś|ŝ|ş|ș|š|ſ|σ|ς/' => 's', - '/Ț|Ţ|Ť|Ŧ|τ/' => 'T', - '/ț|ţ|ť|ŧ/' => 't', - '/Ù|Ú|Û|Ũ|Ū|Ŭ|Ů|Ű|Ų|Ư|Ǔ|Ǖ|Ǘ|Ǚ|Ǜ/' => 'U', - '/ù|ú|û|ũ|ū|ŭ|ů|ű|ų|ư|ǔ|ǖ|ǘ|ǚ|ǜ|υ|ύ|ϋ/' => 'u', - '/Ý|Ÿ|Ŷ|Υ|Ύ|Ϋ/' => 'Y', - '/ý|ÿ|ŷ/' => 'y', + '/Ķ|Κ|К/' => 'K', + '/ķ|κ|к/' => 'k', + '/Ĺ|Ļ|Ľ|Ŀ|Ł|Λ|Л/' => 'L', + '/ĺ|ļ|ľ|ŀ|ł|λ|л/' => 'l', + '/М/' => 'M', + '/м/' => 'm', + '/Ñ|Ń|Ņ|Ň|Ν|Н/' => 'N', + '/ñ|ń|ņ|ň|ʼn|ν|н/' => 'n', + '/Ò|Ó|Ô|Õ|Ō|Ŏ|Ǒ|Ő|Ơ|Ø|Ǿ|Ο|Ό|Ω|Ώ|Ỏ|Ọ|Ồ|Ố|Ỗ|Ổ|Ộ|Ờ|Ớ|Ỡ|Ở|Ợ|О/' => 'O', + '/ò|ó|ô|õ|ō|ŏ|ǒ|ő|ơ|ø|ǿ|º|ο|ό|ω|ώ|ỏ|ọ|ồ|ố|ỗ|ổ|ộ|ờ|ớ|ỡ|ở|ợ|о/' => 'o', + '/П/' => 'P', + '/п/' => 'p', + '/Ŕ|Ŗ|Ř|Ρ|Р/' => 'R', + '/ŕ|ŗ|ř|ρ|р/' => 'r', + '/Ś|Ŝ|Ş|Ș|Š|Σ|С/' => 'S', + '/ś|ŝ|ş|ș|š|ſ|σ|ς|с/' => 's', + '/Ț|Ţ|Ť|Ŧ|τ|Т/' => 'T', + '/ț|ţ|ť|ŧ|т/' => 't', + '/Ù|Ú|Û|Ũ|Ū|Ŭ|Ů|Ű|Ų|Ư|Ǔ|Ǖ|Ǘ|Ǚ|Ǜ|Ũ|Ủ|Ụ|Ừ|Ứ|Ữ|Ử|Ự|У/' => 'U', + '/ù|ú|û|ũ|ū|ŭ|ů|ű|ų|ư|ǔ|ǖ|ǘ|ǚ|ǜ|υ|ύ|ϋ|ủ|ụ|ừ|ứ|ữ|ử|ự|у/' => 'u', + '/Ý|Ÿ|Ŷ|Υ|Ύ|Ϋ|Ỳ|Ỹ|Ỷ|Ỵ|Й/' => 'Y', + '/ý|ÿ|ŷ|ỳ|ỹ|ỷ|ỵ|й/' => 'y', + '/В/' => 'V', + '/в/' => 'v', '/Ŵ/' => 'W', '/ŵ/' => 'w', - '/Ź|Ż|Ž|Ζ/' => 'Z', - '/ź|ż|ž|ζ/' => 'z', + '/Ź|Ż|Ž|Ζ|З/' => 'Z', + '/ź|ż|ž|ζ|з/' => 'z', '/Æ|Ǽ/' => 'AE', - '/ß/'=> 'ss', + '/ß/' => 'ss', '/IJ/' => 'IJ', '/ij/' => 'ij', '/Œ/' => 'OE', @@ -89,6 +101,28 @@ '/β/' => 'v', '/μ/' => 'm', '/ψ/' => 'ps', + '/Ё/' => 'Yo', + '/ё/' => 'yo', + '/Є/' => 'Ye', + '/є/' => 'ye', + '/Ї/' => 'Yi', + '/Ж/' => 'Zh', + '/ж/' => 'zh', + '/Х/' => 'Kh', + '/х/' => 'kh', + '/Ц/' => 'Ts', + '/ц/' => 'ts', + '/Ч/' => 'Ch', + '/ч/' => 'ch', + '/Ш/' => 'Sh', + '/ш/' => 'sh', + '/Щ/' => 'Shch', + '/щ/' => 'shch', + '/Ъ|ъ|Ь|ь/' => '', + '/Ю/' => 'Yu', + '/ю/' => 'yu', + '/Я/' => 'Ya', + '/я/' => 'ya' ); /* End of file foreign_chars.php */ diff --git a/application/config/hooks.php b/application/config/hooks.php index 35005037067..c7532f5387e 100644 --- a/application/config/hooks.php +++ b/application/config/hooks.php @@ -18,7 +18,7 @@ * * @package CodeIgniter * @author EllisLab Dev Team - * @copyright Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/) + * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/) * @license http://opensource.org/licenses/AFL-3.0 Academic Free License (AFL 3.0) * @link http://codeigniter.com * @since Version 1.0 diff --git a/application/config/memcached.php b/application/config/memcached.php index 7166b70ba3d..714e81f6b0b 100644 --- a/application/config/memcached.php +++ b/application/config/memcached.php @@ -18,7 +18,7 @@ * * @package CodeIgniter * @author EllisLab Dev Team - * @copyright Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/) + * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/) * @license http://opensource.org/licenses/AFL-3.0 Academic Free License (AFL 3.0) * @link http://codeigniter.com * @since Version 2.0 diff --git a/application/config/migration.php b/application/config/migration.php index 7645ade7c9c..b348fb317a2 100644 --- a/application/config/migration.php +++ b/application/config/migration.php @@ -18,7 +18,7 @@ * * @package CodeIgniter * @author EllisLab Dev Team - * @copyright Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/) + * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/) * @license http://opensource.org/licenses/AFL-3.0 Academic Free License (AFL 3.0) * @link http://codeigniter.com * @since Version 1.0 @@ -37,6 +37,24 @@ */ $config['migration_enabled'] = FALSE; +/* +|-------------------------------------------------------------------------- +| Migration Type +|-------------------------------------------------------------------------- +| +| Migration file names may be based on a sequential identifier or on +| a timestamp. Options are: +| +| 'sequential' = Default migration naming (001_add_blog.php) +| 'timestamp' = Timestamp migration naming (20121031104401_add_blog.php) +| Use timestamp format YYYYMMDDHHIISS. +| +| If this configuration value is missing the Migration library defaults +| to 'sequential' for backward compatibility. +| +*/ +$config['migration_type'] = 'timestamp'; + /* |-------------------------------------------------------------------------- | Migrations table diff --git a/application/config/mimes.php b/application/config/mimes.php index 1917b11dea2..32d10f6c151 100644 --- a/application/config/mimes.php +++ b/application/config/mimes.php @@ -18,7 +18,7 @@ * * @package CodeIgniter * @author EllisLab Dev Team - * @copyright Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/) + * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/) * @license http://opensource.org/licenses/AFL-3.0 Academic Free License (AFL 3.0) * @link http://codeigniter.com * @since Version 1.0 @@ -44,13 +44,13 @@ 'lzh' => 'application/octet-stream', 'exe' => array('application/octet-stream', 'application/x-msdownload'), 'class' => 'application/octet-stream', - 'psd' => 'application/x-photoshop', + 'psd' => array('application/x-photoshop', 'image/vnd.adobe.photoshop'), 'so' => 'application/octet-stream', 'sea' => 'application/octet-stream', 'dll' => 'application/octet-stream', 'oda' => 'application/oda', - 'pdf' => array('application/pdf', 'application/x-download'), - 'ai' => 'application/postscript', + 'pdf' => array('application/pdf', 'application/force-download', 'application/x-download', 'binary/octet-stream'), + 'ai' => array('application/pdf', 'application/postscript'), 'eps' => 'application/postscript', 'ps' => 'application/postscript', 'smi' => 'application/smil', @@ -58,7 +58,7 @@ 'mif' => 'application/vnd.mif', 'xls' => array('application/vnd.ms-excel', 'application/msexcel', 'application/x-msexcel', 'application/x-ms-excel', 'application/x-excel', 'application/x-dos_ms_excel', 'application/xls', 'application/x-xls', 'application/excel', 'application/download', 'application/vnd.ms-office', 'application/msword'), 'ppt' => array('application/powerpoint', 'application/vnd.ms-powerpoint'), - 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'pptx' => array('application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/x-zip', 'application/zip'), 'wbxml' => 'application/wbxml', 'wmlc' => 'application/wmlc', 'dcr' => 'application/x-director', @@ -73,7 +73,7 @@ 'php3' => 'application/x-httpd-php', 'phtml' => 'application/x-httpd-php', 'phps' => 'application/x-httpd-php-source', - 'js' => 'application/x-javascript', + 'js' => array('application/x-javascript', 'text/plain'), 'swf' => 'application/x-shockwave-flash', 'sit' => 'application/x-stuffit', 'tar' => 'application/x-tar', @@ -104,16 +104,16 @@ 'png' => array('image/png', 'image/x-png'), 'tiff' => 'image/tiff', 'tif' => 'image/tiff', - 'css' => 'text/css', - 'html' => 'text/html', - 'htm' => 'text/html', - 'shtml' => 'text/html', + 'css' => array('text/css', 'text/plain'), + 'html' => array('text/html', 'text/plain'), + 'htm' => array('text/html', 'text/plain'), + 'shtml' => array('text/html', 'text/plain'), 'txt' => 'text/plain', 'text' => 'text/plain', 'log' => array('text/plain', 'text/x-log'), 'rtx' => 'text/richtext', 'rtf' => 'text/rtf', - 'xml' => array('application/xml', 'text/xml'), + 'xml' => array('application/xml', 'text/xml', 'text/plain'), 'xsl' => array('application/xml', 'text/xsl', 'text/xml'), 'mpeg' => 'video/mpeg', 'mpg' => 'video/mpeg', @@ -123,8 +123,10 @@ 'avi' => array('video/x-msvideo', 'video/msvideo', 'video/avi', 'application/x-troff-msvideo'), 'movie' => 'video/x-sgi-movie', 'doc' => array('application/msword', 'application/vnd.ms-office'), - 'docx' => array('application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/zip'), - 'xlsx' => array('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/zip', 'application/vnd.ms-excel', 'application/msword'), + 'docx' => array('application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/zip', 'application/msword', 'application/x-zip'), + 'dot' => array('application/msword', 'application/vnd.ms-office'), + 'dotx' => array('application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/zip', 'application/msword'), + 'xlsx' => array('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/zip', 'application/vnd.ms-excel', 'application/msword', 'application/x-zip'), 'word' => array('application/msword', 'application/octet-stream'), 'xl' => 'application/excel', 'eml' => 'message/rfc822', @@ -152,19 +154,25 @@ 'mp4' => 'video/mp4', 'm4a' => 'audio/x-m4a', 'f4v' => 'video/mp4', + 'webm' => 'video/webm', 'aac' => 'audio/x-acc', 'm4u' => 'application/vnd.mpegurl', 'm3u' => 'text/plain', 'xspf' => 'application/xspf+xml', 'vlc' => 'application/videolan', - 'wmv' => 'video/x-ms-wmv', + 'wmv' => array('video/x-ms-wmv', 'video/x-ms-asf'), 'au' => 'audio/x-au', 'ac3' => 'audio/ac3', 'flac' => 'audio/x-flac', 'ogg' => 'audio/ogg', 'kmz' => array('application/vnd.google-earth.kmz', 'application/zip', 'application/x-zip'), 'kml' => array('application/vnd.google-earth.kml+xml', 'application/xml', 'text/xml'), - 'ics' => 'text/calendar' + 'ics' => 'text/calendar', + 'zsh' => 'text/x-scriptzsh', + '7zip' => array('application/x-compressed', 'application/x-zip-compressed', 'application/zip', 'multipart/x-zip'), + 'cdr' => array('application/cdr', 'application/coreldraw', 'application/x-cdr', 'application/x-coreldraw', 'image/cdr', 'image/x-cdr', 'zz-application/zz-winassoc-cdr'), + 'wma' => array('audio/x-ms-wma', 'video/x-ms-asf'), + 'jar' => array('application/java-archive', 'application/x-java-application', 'application/x-jar', 'application/x-compressed') ); /* End of file mimes.php */ diff --git a/application/config/profiler.php b/application/config/profiler.php index c161a4d5950..fb9d8eed48f 100644 --- a/application/config/profiler.php +++ b/application/config/profiler.php @@ -18,7 +18,7 @@ * * @package CodeIgniter * @author EllisLab Dev Team - * @copyright Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/) + * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/) * @license http://opensource.org/licenses/AFL-3.0 Academic Free License (AFL 3.0) * @link http://codeigniter.com * @since Version 1.0 diff --git a/application/config/routes.php b/application/config/routes.php index 0011986158e..3078c3c76f2 100644 --- a/application/config/routes.php +++ b/application/config/routes.php @@ -18,7 +18,7 @@ * * @package CodeIgniter * @author EllisLab Dev Team - * @copyright Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/) + * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/) * @license http://opensource.org/licenses/AFL-3.0 Academic Free License (AFL 3.0) * @link http://codeigniter.com * @since Version 1.0 @@ -49,7 +49,7 @@ | RESERVED ROUTES | ------------------------------------------------------------------------- | -| There are two reserved routes: +| There are three reserved routes: | | $route['default_controller'] = 'welcome'; | @@ -59,13 +59,23 @@ | | $route['404_override'] = 'errors/page_missing'; | -| This route will tell the Router what URI segments to use if those provided -| in the URL cannot be matched to a valid route. +| This route will tell the Router which controller/method to use if those +| provided in the URL cannot be matched to a valid route. | +| $route['translate_uri_dashes'] = FALSE; +| +| This is not exactly a route, but allows you to automatically route +| controller and method names that contain dashes. '-' isn't a valid +| class or method name character, so it requires translation. +| When you set this option to TRUE, it will replace ALL dashes in the +| controller and method URI segments. +| +| Examples: my-controller/index -> my_controller/index +| my-controller/my-method -> my_controller/my_method */ - $route['default_controller'] = 'welcome'; $route['404_override'] = ''; +$route['translate_uri_dashes'] = FALSE; /* End of file routes.php */ /* Location: ./application/config/routes.php */ \ No newline at end of file diff --git a/application/config/smileys.php b/application/config/smileys.php index baefe53802a..3c49c469eaa 100644 --- a/application/config/smileys.php +++ b/application/config/smileys.php @@ -18,7 +18,7 @@ * * @package CodeIgniter * @author EllisLab Dev Team - * @copyright Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/) + * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/) * @license http://opensource.org/licenses/AFL-3.0 Academic Free License (AFL 3.0) * @link http://codeigniter.com * @since Version 1.0 @@ -30,7 +30,7 @@ | SMILEYS | ------------------------------------------------------------------- | This file contains an array of smileys for use with the emoticon helper. -| Individual images can be used to replace multiple simileys. For example: +| Individual images can be used to replace multiple smileys. For example: | :-) and :) use the same image replacement. | | Please see user guide for more info: @@ -84,7 +84,7 @@ ':vampire:' => array('vampire.gif', '19', '19', 'vampire'), ':snake:' => array('snake.gif', '19', '19', 'snake'), ':exclaim:' => array('exclaim.gif', '19', '19', 'excaim'), - ':question:' => array('question.gif', '19', '19', 'question') // no comma after last item + ':question:' => array('question.gif', '19', '19', 'question') ); diff --git a/application/config/user_agents.php b/application/config/user_agents.php index 78e4c8c7d99..5887a15be0c 100644 --- a/application/config/user_agents.php +++ b/application/config/user_agents.php @@ -18,7 +18,7 @@ * * @package CodeIgniter * @author EllisLab Dev Team - * @copyright Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/) + * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/) * @license http://opensource.org/licenses/AFL-3.0 Academic Free License (AFL 3.0) * @link http://codeigniter.com * @since Version 1.0 @@ -50,6 +50,7 @@ 'win98' => 'Windows 98', 'windows 95' => 'Windows 95', 'win95' => 'Windows 95', + 'windows phone' => 'Windows Phone', 'windows' => 'Unknown Windows OS', 'android' => 'Android', 'blackberry' => 'BlackBerry', @@ -101,7 +102,8 @@ 'Links' => 'Links', 'hotjava' => 'HotJava', 'amaya' => 'Amaya', - 'IBrowse' => 'IBrowse' + 'IBrowse' => 'IBrowse', + 'Maxthon' => 'Maxthon' ); $mobiles = array( @@ -148,6 +150,7 @@ 'mot-' => 'Motorola', 'playstation portable' => 'PlayStation Portable', 'playstation 3' => 'PlayStation 3', + 'playstation vita' => 'PlayStation Vita', 'hiptop' => 'Danger Hiptop', 'nec-' => 'NEC', 'panasonic' => 'Panasonic', @@ -180,7 +183,7 @@ 'operamini' => 'Opera Mini', 'opera mini' => 'Opera Mini', 'opera mobi' => 'Opera Mobile', - 'fennec' => 'Firefox Mobile', + 'fennec' => 'Firefox Mobile', // Other 'digital paths' => 'Digital Paths', @@ -207,13 +210,15 @@ $robots = array( 'googlebot' => 'Googlebot', 'msnbot' => 'MSNBot', + 'baiduspider' => 'Baiduspider', 'bingbot' => 'Bing', 'slurp' => 'Inktomi Slurp', 'yahoo' => 'Yahoo', 'askjeeves' => 'AskJeeves', 'fastcrawler' => 'FastCrawler', 'infoseek' => 'InfoSeek Robot 1.0', - 'lycos' => 'Lycos' + 'lycos' => 'Lycos', + 'yandex' => 'YandexBot' ); /* End of file user_agents.php */ diff --git a/application/controllers/welcome.php b/application/controllers/Welcome.php similarity index 88% rename from application/controllers/welcome.php rename to application/controllers/Welcome.php index 1ed82d2a716..31ceea94863 100644 --- a/application/controllers/welcome.php +++ b/application/controllers/Welcome.php @@ -1,4 +1,4 @@ - @@ -31,9 +32,9 @@ 404 Page Not Found @@ -102,7 +104,7 @@ application/views/welcome_message.php

The corresponding controller for this page is found at:

- application/controllers/welcome.php + application/controllers/Welcome.php

If you are exploring CodeIgniter for the very first time, you should start by reading the User Guide.

diff --git a/composer.json b/composer.json index fa6dc02e44a..0f98fe7a08f 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,10 @@ { - "require": { - "mikey179/vfsStream": "*" - } + "description" : "Dependencies for CodeIgniter's testing environment", + "name" : "ellislab/codeigniter", + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "mikey179/vfsStream": "1.1.*" + } } \ No newline at end of file diff --git a/contributing.md b/contributing.md new file mode 100644 index 00000000000..b6d486d00fa --- /dev/null +++ b/contributing.md @@ -0,0 +1,92 @@ +# Contributing to CodeIgniter + + +CodeIgniter is a community driven project and accepts contributions of code and documentation from the community. These contributions are made in the form of Issues or [Pull Requests](http://help.github.com/send-pull-requests/) on the [EllisLab CodeIgniter repository](https://github.com/EllisLab/CodeIgniter>) on GitHub. + +Issues are a quick way to point out a bug. If you find a bug or documentation error in CodeIgniter then please check a few things first: + +1. There is not already an open Issue +2. The issue has already been fixed (check the develop branch, or look for closed Issues) +3. Is it something really obvious that you can fix yourself? + +Reporting issues is helpful but an even better approach is to send a Pull Request, which is done by "Forking" the main repository and committing to your own copy. This will require you to use the version control system called Git. + +## Guidelines + +Before we look into how, here are the guidelines. If your Pull Requests fail +to pass these guidelines it will be declined and you will need to re-submit +when you’ve made the changes. This might sound a bit tough, but it is required +for us to maintain quality of the code-base. + +### PHP Style + +All code must meet the [Style Guide](http://codeigniter.com/user_guide/general/styleguide.html), which is +essentially the [Allman indent style](http://en.wikipedia.org/wiki/Indent_style#Allman_style), underscores and readable operators. This makes certain that all code is the same format as the existing code and means it will be as readable as possible. + +### Documentation + +If you change anything that requires a change to documentation then you will need to add it. New classes, methods, parameters, changing default values, etc are all things that will require a change to documentation. The change-log must also be updated for every change. Also PHPDoc blocks must be maintained. + +### Compatibility + +CodeIgniter is compatible with PHP 5.2.4 so all code supplied must stick to +this requirement. If PHP 5.3 or 5.4 functions or features are used then there +must be a fallback for PHP 5.2.4. + +### Branching + +CodeIgniter uses the [Git-Flow](http://nvie.com/posts/a-successful-git-branching-model/) branching model which requires all pull requests to be sent to the "develop" branch. This is +where the next planned version will be developed. The "master" branch will always contain the latest stable version and is kept clean so a "hotfix" (e.g: an emergency security patch) can be applied to master to create a new version, without worrying about other features holding it up. For this reason all commits need to be made to "develop" and any sent to "master" will be closed automatically. If you have multiple changes to submit, please place all changes into their own branch on your fork. + +One thing at a time: A pull request should only contain one change. That does not mean only one commit, but one change - however many commits it took. The reason for this is that if you change X and Y but send a pull request for both at the same time, we might really want X but disagree with Y, meaning we cannot merge the request. Using the Git-Flow branching model you can create new branches for both of these features and send two requests. + +### Signing + +You must sign your work, certifying that you either wrote the work or otherwise have the right to pass it on to an open source project. git makes this trivial as you merely have to use `--signoff` on your commits to your CodeIgniter fork. + +`git commit --signoff` + +or simply + +`git commit -s` + +This will sign your commits with the information setup in your git config, e.g. + +`Signed-off-by: John Q Public ` + +If you are using [Tower](http://www.git-tower.com/) there is a "Sign-Off" checkbox in the commit window. You could even alias git commit to use the `-s` flag so you don’t have to think about it. + +By signing your work in this manner, you certify to a "Developer's Certificate of Origin". The current version of this certificate is in the `DCO.txt` file in the root of this repository. + + +## How-to Guide + +There are two ways to make changes, the easy way and the hard way. Either way you will need to [create a GitHub account](https://github.com/signup/free). + +Easy way GitHub allows in-line editing of files for making simple typo changes and quick-fixes. This is not the best way as you are unable to test the code works. If you do this you could be introducing syntax errors, etc, but for a Git-phobic user this is good for a quick-fix. + +Hard way The best way to contribute is to "clone" your fork of CodeIgniter to your development area. That sounds like some jargon, but "forking" on GitHub means "making a copy of that repo to your account" and "cloning" means "copying that code to your environment so you can work on it". + +1. Set up Git (Windows, Mac & Linux) +2. Go to the CodeIgniter repo +3. Fork it +4. Clone your CodeIgniter repo: git@github.com:/CodeIgniter.git +5. Checkout the "develop" branch At this point you are ready to start making changes. +6. Fix existing bugs on the Issue tracker after taking a look to see nobody else is working on them. +7. Commit the files +8. Push your develop branch to your fork +9. Send a pull request [http://help.github.com/send-pull-requests/](http://help.github.com/send-pull-requests/) + +The Reactor Engineers will now be alerted about the change and at least one of the team will respond. If your change fails to meet the guidelines it will be bounced, or feedback will be provided to help you improve it. + +Once the Reactor Engineer handling your pull request is happy with it they will post it to the internal EllisLab discussion area to be double checked by the other Engineers and EllisLab developers. If nobody has a problem with the change then it will be merged into develop and will be part of the next release. Keeping your fork up-to-date + +Unlike systems like Subversion, Git can have multiple remotes. A remote is the name for a URL of a Git repository. By default your fork will have a remote named "origin" which points to your fork, but you can add another remote named "codeigniter" which points to `git://github.com/EllisLab/CodeIgniter.git`. This is a read-only remote but you can pull from this develop branch to update your own. + +If you are using command-line you can do the following: + +1. `git remote add codeigniter git://github.com/EllisLab/CodeIgniter.git` +2. `git pull codeigniter develop` +3. `git push origin develop` + +Now your fork is up to date. This should be done regularly, or before you send a pull request at least. \ No newline at end of file diff --git a/index.php b/index.php old mode 100644 new mode 100755 index 107a2095b7c..3040ef0800d --- a/index.php +++ b/index.php @@ -18,7 +18,7 @@ * * @package CodeIgniter * @author EllisLab Dev Team - * @copyright Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/) + * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/) * @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) * @link http://codeigniter.com * @since Version 1.0 @@ -61,13 +61,14 @@ case 'testing': case 'production': - error_reporting(E_ALL ^ E_NOTICE ^ E_DEPRECATED ^ E_STRICT); + error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT); ini_set('display_errors', 0); break; default: header('HTTP/1.1 503 Service Unavailable.', TRUE, 503); - exit('The application environment is not set correctly.'); + echo 'The application environment is not set correctly.'; + exit(1); // EXIT_* constants not yet defined; 1 is EXIT_ERROR, a generic error. } /* @@ -76,7 +77,7 @@ *--------------------------------------------------------------- * * This variable must contain the name of your "system" folder. - * Include the path if the folder is not in the same directory + * Include the path if the folder is not in the same directory * as this file. */ $system_path = 'system'; @@ -87,7 +88,7 @@ *--------------------------------------------------------------- * * If you want this front controller to use a different "application" - * folder then the default one you can set its name here. The folder + * folder than the default one you can set its name here. The folder * can also be renamed or relocated anywhere on your server. If * you do, use a full server path. For more info please see the user guide: * http://codeigniter.com/user_guide/general/managing_apps.html @@ -190,7 +191,8 @@ if ( ! is_dir($system_path)) { header('HTTP/1.1 503 Service Unavailable.', TRUE, 503); - exit('Your system folder path does not appear to be set correctly. Please open the following file and correct this: '.pathinfo(__FILE__, PATHINFO_BASENAME)); + echo 'Your system folder path does not appear to be set correctly. Please open the following file and correct this: '.pathinfo(__FILE__, PATHINFO_BASENAME); + exit(3); // EXIT_* constants not yet defined; 3 is EXIT_CONFIG. } /* @@ -225,7 +227,8 @@ if ( ! is_dir(BASEPATH.$application_folder.'/')) { header('HTTP/1.1 503 Service Unavailable.', TRUE, 503); - exit('Your application folder path does not appear to be set correctly. Please open the following file and correct this: '.SELF); + echo 'Your application folder path does not appear to be set correctly. Please open the following file and correct this: '.SELF; + exit(3); // EXIT_* constants not yet defined; 3 is EXIT_CONFIG. } define('APPPATH', BASEPATH.$application_folder.'/'); @@ -241,7 +244,8 @@ elseif ( ! is_dir(APPPATH.'views/')) { header('HTTP/1.1 503 Service Unavailable.', TRUE, 503); - exit('Your view folder path does not appear to be set correctly. Please open the following file and correct this: '.SELF); + echo 'Your view folder path does not appear to be set correctly. Please open the following file and correct this: '.SELF; + exit(3); // EXIT_* constants not yet defined; 3 is EXIT_CONFIG. } else { @@ -251,7 +255,7 @@ if (($_temp = realpath($view_folder)) !== FALSE) { - $view_folder = realpath($view_folder).'/'; + $view_folder = $_temp.'/'; } else { @@ -270,4 +274,4 @@ require_once BASEPATH.'core/CodeIgniter.php'; /* End of file index.php */ -/* Location: ./index.php */ +/* Location: ./index.php */ \ No newline at end of file diff --git a/readme.rst b/readme.rst index b211ad7cdb2..d768162374c 100644 --- a/readme.rst +++ b/readme.rst @@ -38,166 +38,6 @@ Installation Please see the `installation section `_ of the CodeIgniter User Guide. -************ -Contributing -************ - -CodeIgniter is a community driven project and accepts contributions of code -and documentation from the community. These contributions are made in the form -of Issues or `Pull Requests `_ on -the `EllisLab CodeIgniter repository -`_ on GitHub. - -Issues are a quick way to point out a bug. If you find a bug or documentation -error in CodeIgniter then please check a few things first: - -- There is not already an open Issue -- The issue has already been fixed (check the develop branch, or look for - closed Issues) -- Is it something really obvious that you fix it yourself? - -Reporting issues is helpful but an even better approach is to send a Pull -Request, which is done by "Forking" the main repository and committing to your -own copy. This will require you to use the version control system called Git. - -********** -Guidelines -********** - -Before we look into how, here are the guidelines. If your Pull Requests fail -to pass these guidelines it will be declined and you will need to re-submit -when you’ve made the changes. This might sound a bit tough, but it is required -for us to maintain quality of the code-base. - -PHP Style -========= - -All code must meet the `Style Guide -`_, which is -essentially the `Allman indent style -`_, underscores and -readable operators. This makes certain that all code is the same format as the -existing code and means it will be as readable as possible. - -Documentation -============= - -If you change anything that requires a change to documentation then you will -need to add it. New classes, methods, parameters, changing default values, etc -are all things that will require a change to documentation. The change-log -must also be updated for every change. Also PHPDoc blocks must be maintained. - -Compatibility -============= - -CodeIgniter is compatible with PHP 5.2.4 so all code supplied must stick to -this requirement. If PHP 5.3 or 5.4 functions or features are used then there -must be a fallback for PHP 5.2.4. - -Branching -========= - -CodeIgniter uses the `Git-Flow -`_ branching model -which requires all pull requests to be sent to the "develop" branch. This is -where the next planned version will be developed. The "master" branch will -always contain the latest stable version and is kept clean so a "hotfix" (e.g: -an emergency security patch) can be applied to master to create a new version, -without worrying about other features holding it up. For this reason all -commits need to be made to "develop" and any sent to "master" will be closed -automatically. If you have multiple changes to submit, please place all -changes into their own branch on your fork. - -One thing at a time: A pull request should only contain one change. That does -not mean only one commit, but one change - however many commits it took. The -reason for this is that if you change X and Y but send a pull request for both -at the same time, we might really want X but disagree with Y, meaning we -cannot merge the request. Using the Git-Flow branching model you can create -new branches for both of these features and send two requests. - -Signing -======= -You must sign your work, certifying that you either wrote the work or -otherwise have the right to pass it on to an open source project. git makes -this trivial as you merely have to use `--signoff` on your commits to your -CodeIgniter fork. - -:: - - git commit --signoff - -or simply:: - - git commit -s - -This will sign your commits with the information setup in your git config, e.g. - - Signed-off-by: John Q Public - -If you are using Tower there is a "Sign-Off" checkbox in the commit window. You -could even alias git commit to use the -s flag so you don’t have to think about -it. - -By signing your work in this manner, you certify to a "Developer's Certificate -or Origin". The current version of this certificate is in the `DCO.txt` file -in the root of this repository. - - -************ -How-to Guide -************ - -There are two ways to make changes, the easy way and the hard way. Either way -you will need to `create a GitHub account `_. - -Easy way GitHub allows in-line editing of files for making simple typo changes -and quick-fixes. This is not the best way as you are unable to test the code -works. If you do this you could be introducing syntax errors, etc, but for a -Git-phobic user this is good for a quick-fix. - -Hard way The best way to contribute is to "clone" your fork of CodeIgniter to -your development area. That sounds like some jargon, but "forking" on GitHub -means "making a copy of that repo to your account" and "cloning" means -"copying that code to your environment so you can work on it". - -#. Set up Git (Windows, Mac & Linux) -#. Go to the CodeIgniter repo -#. Fork it -#. Clone your CodeIgniter repo: git@github.com:/CodeIgniter.git -#. Checkout the "develop" branch At this point you are ready to start making - changes. -#. Fix existing bugs on the Issue tracker after taking a look to see nobody - else is working on them. -#. Commit the files -#. Push your develop branch to your fork -#. Send a pull request http://help.github.com/send-pull-requests/ - -The Reactor Engineers will now be alerted about the change and at least one of -the team will respond. If your change fails to meet the guidelines it will be -bounced, or feedback will be provided to help you improve it. - -Once the Reactor Engineer handling your pull request is happy with it they -will post it to the internal EllisLab discussion area to be double checked by -the other Engineers and EllisLab developers. If nobody has a problem with the -change then it will be merged into develop and will be part of the next -release. Keeping your fork up-to-date - -Unlike systems like Subversion, Git can have multiple remotes. A remote is the -name for a URL of a Git repository. By default your fork will have a remote -named "origin" which points to your fork, but you can add another remote named -"codeigniter" which points to git://github.com/EllisLab/CodeIgniter.git. This -is a read-only remote but you can pull from this develop branch to update your -own. - -If you are using command-line you can do the following: - -#. git remote add codeigniter git://github.com/EllisLab/CodeIgniter.git -#. git pull codeigniter develop -#. git push origin develop - -Now your fork is up to date. This should be done regularly, or before you send -a pull request at least. - ******* License ******* @@ -212,7 +52,7 @@ Resources - `User Guide `_ - `Community Forums `_ - `Community Wiki `_ -- `Community IRC `_ +- `Community IRC `_ *************** Acknowledgement diff --git a/system/.htaccess b/system/.htaccess index 6c63ed4c4d2..97c65d2df24 100644 --- a/system/.htaccess +++ b/system/.htaccess @@ -1,6 +1,6 @@ - Require all denied + Require all denied - Deny from all + Deny from all \ No newline at end of file diff --git a/system/core/Benchmark.php b/system/core/Benchmark.php index 2fabdf46e98..f9be18a42be 100644 --- a/system/core/Benchmark.php +++ b/system/core/Benchmark.php @@ -1,4 +1,4 @@ -_assign_to_config($assign_to_config); + foreach ($assign_to_config as $key => $value) + { + $CFG->set_item($key, $value); + } } /* @@ -161,7 +165,6 @@ * ------------------------------------------------------ */ $RTR =& load_class('Router', 'core'); - $RTR->_set_routing(); // Set any routing overrides that may exist in the main index file if (isset($routing)) @@ -229,7 +232,6 @@ function &get_instance() return CI_Controller::get_instance(); } - if (file_exists(APPPATH.'core/'.$CFG->config['subclass_prefix'].'Controller.php')) { require APPPATH.'core/'.$CFG->config['subclass_prefix'].'Controller.php'; @@ -238,12 +240,13 @@ function &get_instance() // Load the local application controller // Note: The Router class automatically validates the controller path using the router->_validate_request(). // If this include fails it means that the default controller in the Routes.php file is not resolving to something valid. - if ( ! file_exists(APPPATH.'controllers/'.$RTR->fetch_directory().$RTR->fetch_class().'.php')) + $class = ucfirst($RTR->class); + if ( ! file_exists(APPPATH.'controllers/'.$RTR->directory.$class.'.php')) { show_error('Unable to load your default controller. Please make sure the controller specified in your Routes.php file is valid.'); } - include(APPPATH.'controllers/'.$RTR->fetch_directory().$RTR->fetch_class().'.php'); + include(APPPATH.'controllers/'.$RTR->directory.$class.'.php'); // Set a mark point for benchmarking $BM->mark('loading_time:_base_classes_end'); @@ -253,24 +256,24 @@ function &get_instance() * Security check * ------------------------------------------------------ * - * None of the functions in the app controller or the + * None of the methods in the app controller or the * loader class can be called via the URI, nor can - * controller functions that begin with an underscore + * controller methods that begin with an underscore. */ - $class = $RTR->fetch_class(); - $method = $RTR->fetch_method(); + $method = $RTR->method; - if ( ! class_exists($class) - OR strpos($method, '_') === 0 - OR in_array(strtolower($method), array_map('strtolower', get_class_methods('CI_Controller'))) - ) + if ( ! class_exists($class, FALSE) OR $method[0] === '_' OR method_exists('CI_Controller', $method)) { if ( ! empty($RTR->routes['404_override'])) { - $x = explode('/', $RTR->routes['404_override'], 2); - $class = $x[0]; - $method = isset($x[1]) ? $x[1] : 'index'; - if ( ! class_exists($class)) + if (sscanf($RTR->routes['404_override'], '%[^/]/%s', $class, $method) !== 2) + { + $method = 'index'; + } + + $class = ucfirst($class); + + if ( ! class_exists($class, FALSE)) { if ( ! file_exists(APPPATH.'controllers/'.$class.'.php')) { @@ -286,6 +289,44 @@ function &get_instance() } } + if (method_exists($class, '_remap')) + { + $params = array($method, array_slice($URI->rsegments, 2)); + $method = '_remap'; + } + else + { + // WARNING: It appears that there are issues with is_callable() even in PHP 5.2! + // Furthermore, there are bug reports and feature/change requests related to it + // that make it unreliable to use in this context. Please, DO NOT change this + // work-around until a better alternative is available. + if ( ! in_array(strtolower($method), array_map('strtolower', get_class_methods($class)), TRUE)) + { + if (empty($RTR->routes['404_override'])) + { + show_404($class.'/'.$method); + } + elseif (sscanf($RTR->routes['404_override'], '%[^/]/%s', $class, $method) !== 2) + { + $method = 'index'; + } + + $class = ucfirst($class); + + if ( ! class_exists($class, FALSE)) + { + if ( ! file_exists(APPPATH.'controllers/'.$class.'.php')) + { + show_404($class.'/'.$method); + } + + include_once(APPPATH.'controllers/'.$class.'.php'); + } + } + + $params = array_slice($URI->rsegments, 2); + } + /* * ------------------------------------------------------ * Is there a "pre_controller" hook? @@ -315,45 +356,7 @@ function &get_instance() * Call the requested method * ------------------------------------------------------ */ - // Is there a "remap" function? If so, we call it instead - if (method_exists($CI, '_remap')) - { - $CI->_remap($method, array_slice($URI->rsegments, 2)); - } - else - { - // is_callable() returns TRUE on some versions of PHP 5 for private and protected - // methods, so we'll use this workaround for consistent behavior - if ( ! in_array(strtolower($method), array_map('strtolower', get_class_methods($CI)))) - { - // Check and see if we are using a 404 override and use it. - if ( ! empty($RTR->routes['404_override'])) - { - $x = explode('/', $RTR->routes['404_override'], 2); - $class = $x[0]; - $method = isset($x[1]) ? $x[1] : 'index'; - if ( ! class_exists($class)) - { - if ( ! file_exists(APPPATH.'controllers/'.$class.'.php')) - { - show_404($class.'/'.$method); - } - - include_once(APPPATH.'controllers/'.$class.'.php'); - unset($CI); - $CI = new $class(); - } - } - else - { - show_404($class.'/'.$method); - } - } - - // Call the requested method. - // Any URI segments present (besides the class/function) will be passed to the method for convenience - call_user_func_array(array(&$CI, $method), array_slice($URI->rsegments, 2)); - } + call_user_func_array(array(&$CI, $method), $params); // Mark a benchmark end point $BM->mark('controller_execution_time_( '.$class.' / '.$method.' )_end'); diff --git a/system/core/Common.php b/system/core/Common.php index 57374b07dde..7f296b133c3 100644 --- a/system/core/Common.php +++ b/system/core/Common.php @@ -1,4 +1,4 @@ - 0) + // Are any values being dynamically added or replaced? + foreach ($replace as $key => $val) { - foreach ($replace as $key => $val) - { - if (isset($config[$key])) - { - $config[$key] = $val; - } - } + $_config[0][$key] = $val; } - return $_config[0] =& $config; + return $_config[0]; } } @@ -285,20 +284,15 @@ function &get_config($replace = array()) */ function config_item($item) { - static $_config_item = array(); + static $_config; - if ( ! isset($_config_item[$item])) + if (empty($_config)) { - $config =& get_config(); - - if ( ! isset($config[$item])) - { - return FALSE; - } - $_config_item[$item] = $config[$item]; + // references cannot be directly assigned to static variables, so we use an array + $_config[0] =& get_config(); } - return $_config_item[$item]; + return isset($_config[0][$item]) ? $_config[0][$item] : FALSE; } } @@ -315,11 +309,11 @@ function &get_mimes() { static $_mimes = array(); - if (defined('ENVIRONMENT') && is_file(APPPATH.'config/'.ENVIRONMENT.'/mimes.php')) + if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/mimes.php')) { $_mimes = include(APPPATH.'config/'.ENVIRONMENT.'/mimes.php'); } - elseif (is_file(APPPATH.'config/mimes.php')) + elseif (file_exists(APPPATH.'config/mimes.php')) { $_mimes = include(APPPATH.'config/mimes.php'); } @@ -330,6 +324,37 @@ function &get_mimes() // ------------------------------------------------------------------------ +if ( ! function_exists('is_https')) +{ + /** + * Is HTTPS? + * + * Determines if the application is accessed via an encrypted + * (HTTPS) connection. + * + * @return bool + */ + function is_https() + { + if ( ! empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off') + { + return TRUE; + } + elseif (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') + { + return TRUE; + } + elseif ( ! empty($_SERVER['HTTP_FRONT_END_HTTPS']) && strtolower($_SERVER['HTTP_FRONT_END_HTTPS']) !== 'off') + { + return TRUE; + } + + return FALSE; + } +} + +// ------------------------------------------------------------------------ + if ( ! function_exists('show_error')) { /** @@ -337,7 +362,7 @@ function &get_mimes() * * This function lets us invoke the exception class and * display errors using the standard error template located - * in application/errors/errors.php + * in application/views/errors/error_general.php * This function will send the error page directly to the * browser and exit. * @@ -348,9 +373,24 @@ function &get_mimes() */ function show_error($message, $status_code = 500, $heading = 'An Error Was Encountered') { + $status_code = abs($status_code); + if ($status_code < 100) + { + $exit_status = $status_code + EXIT__AUTO_MIN; + if ($exit_status > EXIT__AUTO_MAX) + { + $exit_status = EXIT_ERROR; + } + $status_code = 500; + } + else + { + $exit_status = EXIT_ERROR; + } + $_error =& load_class('Exceptions', 'core'); echo $_error->show_error($heading, $message, 'error_general', $status_code); - exit; + exit($exit_status); } } @@ -373,7 +413,7 @@ function show_404($page = '', $log_error = TRUE) { $_error =& load_class('Exceptions', 'core'); $_error->show_404($page, $log_error); - exit; + exit(EXIT_UNKNOWN_FILE); } } @@ -387,22 +427,22 @@ function show_404($page = '', $log_error = TRUE) * We use this as a simple mechanism to access the logging * class and send messages to be logged. * - * @param string - * @param string - * @param bool + * @param string the error level: 'error', 'debug' or 'info' + * @param string the error message + * @param bool whether the error is a native PHP error * @return void */ - function log_message($level = 'error', $message, $php_error = FALSE) + function log_message($level, $message, $php_error = FALSE) { static $_log; - if (config_item('log_threshold') === 0) + if ($_log === NULL) { - return; + // references cannot be directly assigned to static variables, so we use an array + $_log[0] =& load_class('Log', 'core'); } - $_log =& load_class('Log'); - $_log->write_log($level, $message, $php_error); + $_log[0]->write_log($level, $message, $php_error); } } @@ -484,17 +524,13 @@ function set_status_header($code = 200, $text = '') $server_protocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : FALSE; - if (strpos(php_sapi_name(), 'cgi') === 0) + if (strpos(PHP_SAPI, 'cgi') === 0) { header('Status: '.$code.' '.$text, TRUE); } - elseif ($server_protocol === 'HTTP/1.0') - { - header('HTTP/1.0 '.$code.' '.$text, TRUE, $code); - } else { - header('HTTP/1.1 '.$code.' '.$text, TRUE, $code); + header(($server_protocol ? $server_protocol : 'HTTP/1.1').' '.$code.' '.$text, TRUE, $code); } } } @@ -506,8 +542,8 @@ function set_status_header($code = 200, $text = '') /** * Exception Handler * - * This is the custom exception handler that is declaired at the top - * of Codeigniter.php. The main reason we use this is to permit + * This is the custom exception handler that is declared at the top + * of CodeIgniter.php. The main reason we use this is to permit * PHP errors to be logged in our own log files since the user may * not have access to server logs. Since this function * effectively intercepts PHP errors, however, we also need @@ -524,18 +560,17 @@ function _exception_handler($severity, $message, $filepath, $line) { $_error =& load_class('Exceptions', 'core'); - // Should we display the error? We'll get the current error_reporting + // Should we ignore the error? We'll get the current error_reporting // level and add its bits with the severity bits to find out. - // And respect display_errors - if (($severity & error_reporting()) === $severity && (bool) ini_get('display_errors') === TRUE) + if (($severity & error_reporting()) !== $severity) { - $_error->show_php_error($severity, $message, $filepath, $line); + return; } - // Should we log the error? No? We're done... - if (config_item('log_threshold') === 0) + // Should we display the error? + if ((bool) ini_get('display_errors') === TRUE) { - return; + $_error->show_php_error($severity, $message, $filepath, $line); } $_error->log_exception($severity, $message, $filepath, $line); @@ -637,5 +672,57 @@ function _stringify_attributes($attributes, $js = FALSE) } } +// ------------------------------------------------------------------------ + +if ( ! function_exists('function_usable')) +{ + /** + * Function usable + * + * Executes a function_exists() check, and if the Suhosin PHP + * extension is loaded - checks whether the function that is + * checked might be disabled in there as well. + * + * This is useful as function_exists() will return FALSE for + * functions disabled via the *disable_functions* php.ini + * setting, but not for *suhosin.executor.func.blacklist* and + * *suhosin.executor.disable_eval*. These settings will just + * terminate script execution if a disabled function is executed. + * + * @link http://www.hardened-php.net/suhosin/ + * @param string $function_name Function to check for + * @return bool TRUE if the function exists and is safe to call, + * FALSE otherwise. + */ + function function_usable($function_name) + { + static $_suhosin_func_blacklist; + + if (function_exists($function_name)) + { + if ( ! isset($_suhosin_func_blacklist)) + { + if (extension_loaded('suhosin')) + { + $_suhosin_func_blacklist = explode(',', trim(@ini_get('suhosin.executor.func.blacklist'))); + + if ( ! in_array('eval', $_suhosin_func_blacklist, TRUE) && @ini_get('suhosin.executor.disable_eval')) + { + $_suhosin_func_blacklist[] = 'eval'; + } + } + else + { + $_suhosin_func_blacklist = array(); + } + } + + return ! in_array($function_name, $_suhosin_func_blacklist, TRUE); + } + + return FALSE; + } +} + /* End of file Common.php */ /* Location: ./system/core/Common.php */ \ No newline at end of file diff --git a/system/core/Config.php b/system/core/Config.php index 2f6a9e0856e..109ee6424ee 100644 --- a/system/core/Config.php +++ b/system/core/Config.php @@ -1,4 +1,4 @@ -_config_paths as $path) { - foreach ($check_locations as $location) + foreach (array(ENVIRONMENT.'/'.$file, $file) as $location) { $file_path = $path.'config/'.$location.'.php'; @@ -183,33 +182,33 @@ public function load($file = '', $use_sections = FALSE, $fail_gracefully = FALSE /** * Fetch a config file item * - * @param string the config item name - * @param string the index name - * @return string + * @param string $item Config item name + * @param string $index Index name + * @return string|null The configuration item or NULL if the item doesn't exist */ public function item($item, $index = '') { if ($index == '') { - return isset($this->config[$item]) ? $this->config[$item] : FALSE; + return isset($this->config[$item]) ? $this->config[$item] : NULL; } - return isset($this->config[$index], $this->config[$index][$item]) ? $this->config[$index][$item] : FALSE; + return isset($this->config[$index], $this->config[$index][$item]) ? $this->config[$index][$item] : NULL; } // -------------------------------------------------------------------- /** - * Fetch a config file item - adds slash after item (if item is not empty) + * Fetch a config file item with slash appended (if not empty) * - * @param string the config item name - * @return string + * @param string $item Config item name + * @return string|null The configuration item or NULL if the item doesn't exist */ public function slash_item($item) { if ( ! isset($this->config[$item])) { - return FALSE; + return NULL; } elseif (trim($this->config[$item]) === '') { @@ -223,9 +222,12 @@ public function slash_item($item) /** * Site URL + * * Returns base_url . index_page [. uri_string] * - * @param mixed the URI string or an array of segments + * @uses CI_Config::_uri_string() + * + * @param string|string[] $uri URI string or an array of segments * @return string */ public function site_url(http://github.com/$uri%20=%20'') @@ -239,15 +241,18 @@ public function site_url(http://github.com/$uri%20=%20'') if ($this->item('enable_query_strings') === FALSE) { - $suffix = ($this->item('url_suffix') === FALSE) ? '' : $this->item('url_suffix'); + $suffix = isset($this->config['url_suffix']) ? $this->config['url_suffix'] : ''; - if ($suffix !== '' && ($offset = strpos($uri, '?')) !== FALSE) - { - $uri = substr($uri, 0, $offset).$suffix.substr($uri, $offset); - } - else + if ($suffix !== '') { - $uri .= $suffix; + if (($offset = strpos($uri, '?')) !== FALSE) + { + $uri = substr($uri, 0, $offset).$suffix.substr($uri, $offset); + } + else + { + $uri .= $suffix; + } } return $this->slash_item('base_url').$this->slash_item('index_page').$uri; @@ -264,9 +269,12 @@ public function site_url(http://github.com/$uri%20=%20'') /** * Base URL + * * Returns base_url [. uri_string] * - * @param string $uri + * @uses CI_Config::_uri_string() + * + * @param string|string[] $uri URI string or an array of segments * @return string */ public function base_url(http://github.com/$uri%20=%20'') @@ -277,9 +285,12 @@ public function base_url(http://github.com/$uri%20=%20'') // ------------------------------------------------------------- /** - * Build URI string for use in Config::site_url() and Config::base_url() + * Build URI string * - * @param mixed $uri + * @used-by CI_Config::site_url() + * @used-by CI_Config::base_url() + * + * @param string|string[] $uri URI string or an array of segments * @return string */ protected function _uri_string($uri) @@ -318,8 +329,8 @@ public function system_url() /** * Set a config file item * - * @param string the config item key - * @param string the config item value + * @param string $item Config item key + * @param string $value Config item value * @return void */ public function set_item($item, $value) @@ -327,29 +338,6 @@ public function set_item($item, $value) $this->config[$item] = $value; } - // -------------------------------------------------------------------- - - /** - * Assign to Config - * - * This function is called by the front controller (CodeIgniter.php) - * after the Config class is instantiated. It permits config items - * to be assigned or overriden by variables contained in the index.php file - * - * @param array - * @return void - */ - public function _assign_to_config($items = array()) - { - if (is_array($items)) - { - foreach ($items as $key => $val) - { - $this->set_item($key, $val); - } - } - } - } /* End of file Config.php */ diff --git a/system/core/Controller.php b/system/core/Controller.php index 49141480784..3fcadcadfae 100644 --- a/system/core/Controller.php +++ b/system/core/Controller.php @@ -1,4 +1,4 @@ -show_error($heading, $message, 'error_404', 404); - exit; + exit(EXIT_UNKNOWN_FILE); } // -------------------------------------------------------------------- @@ -122,15 +125,15 @@ public function show_404($page = '', $log_error = TRUE) /** * General Error Page * - * This function takes an error message as input - * (either as a string or an array) and displays - * it using the specified template. + * Takes an error message as input (either as a string or an array) + * and displays it using the specified template. + * + * @param string $heading Page heading + * @param string|string[] $message Error message + * @param string $template Template name + * @param int $status_code (default: 500) * - * @param string the heading - * @param string the message - * @param string the template name - * @param int the status code - * @return string + * @return string Error page output */ public function show_error($heading, $message, $template = 'error_general', $status_code = 500) { @@ -154,11 +157,11 @@ public function show_error($heading, $message, $template = 'error_general', $sta /** * Native PHP error handler * - * @param string the error severity - * @param string the error string - * @param string the error filepath - * @param string the error line number - * @return string + * @param int $severity Error level + * @param string $message Error message + * @param string $filepath File path + * @param int $line Line number + * @return string Error page output */ public function show_php_error($severity, $message, $filepath, $line) { diff --git a/system/core/Hooks.php b/system/core/Hooks.php index afbf4b45372..b3b11199165 100644 --- a/system/core/Hooks.php +++ b/system/core/Hooks.php @@ -1,4 +1,4 @@ -in_progress === TRUE) + if ($this->_in_progress === TRUE) { return; } @@ -173,52 +179,28 @@ protected function _run_hook($data) return FALSE; } - // ----------------------------------- - // Set class/function name - // ----------------------------------- - - $class = FALSE; - $function = FALSE; - $params = ''; - - if ( ! empty($data['class'])) - { - $class = $data['class']; - } - - if ( ! empty($data['function'])) - { - $function = $data['function']; - } - - if (isset($data['params'])) - { - $params = $data['params']; - } + // Determine and class and/or function names + $class = empty($data['class']) ? FALSE : $data['class']; + $function = empty($data['function']) ? FALSE : $data['function']; + $params = isset($data['params']) ? $data['params'] : ''; if ($class === FALSE && $function === FALSE) { return FALSE; } - // ----------------------------------- - // Set the in_progress flag - // ----------------------------------- - - $this->in_progress = TRUE; + // Set the _in_progress flag + $this->_in_progress = TRUE; - // ----------------------------------- // Call the requested class and/or function - // ----------------------------------- - if ($class !== FALSE) { - if ( ! class_exists($class)) + if ( ! class_exists($class, FALSE)) { require($filepath); } - $HOOK = new $class; + $HOOK = new $class(); $HOOK->$function($params); } else @@ -231,7 +213,7 @@ protected function _run_hook($data) $function($params); } - $this->in_progress = FALSE; + $this->_in_progress = FALSE; return TRUE; } diff --git a/system/core/Input.php b/system/core/Input.php index d7bfed3f8db..24e21ea08ae 100644 --- a/system/core/Input.php +++ b/system/core/Input.php @@ -1,4 +1,4 @@ - 1) // Does the index contain array notation + { + $value = $array; + for ($i = 0; $i < $count; $i++) + { + $key = trim($matches[0][$i], '[]'); + if ($key === '') // Empty notation will return the value as array + { + break; + } - if ($xss_clean === TRUE) + if (isset($value[$key])) + { + $value = $value[$key]; + } + else + { + return NULL; + } + } + } + else { - return $this->security->xss_clean($array[$index]); + return NULL; } - return $array[$index]; + return ($xss_clean === TRUE) + ? $this->security->xss_clean($value) + : $value; } // -------------------------------------------------------------------- @@ -151,15 +193,20 @@ protected function _fetch_from_array(&$array, $index = '', $xss_clean = FALSE) /** * Fetch an item from the GET array * - * @param string - * @param bool - * @return string + * @param string $index Index for item to be fetched from $_GET + * @param bool $xss_clean Whether to apply XSS filtering + * @return mixed */ public function get($index = NULL, $xss_clean = FALSE) { // Check if a field has been provided - if ($index === NULL && ! empty($_GET)) + if ($index === NULL) { + if (empty($_GET)) + { + return array(); + } + $get = array(); // loop through the full _GET array @@ -178,15 +225,20 @@ public function get($index = NULL, $xss_clean = FALSE) /** * Fetch an item from the POST array * - * @param string - * @param bool - * @return string + * @param string $index Index for item to be fetched from $_POST + * @param bool $xss_clean Whether to apply XSS filtering + * @return mixed */ public function post($index = NULL, $xss_clean = FALSE) { // Check if a field has been provided - if ($index === NULL && ! empty($_POST)) + if ($index === NULL) { + if (empty($_POST)) + { + return array(); + } + $post = array(); // Loop through the full _POST array and return it @@ -200,17 +252,16 @@ public function post($index = NULL, $xss_clean = FALSE) return $this->_fetch_from_array($_POST, $index, $xss_clean); } - // -------------------------------------------------------------------- /** - * Fetch an item from either the GET array or the POST + * Fetch an item from POST data with fallback to GET * - * @param string The index key - * @param bool XSS cleaning - * @return string + * @param string $index Index for item to be fetched from $_POST or $_GET + * @param bool $xss_clean Whether to apply XSS filtering + * @return mixed */ - public function get_post($index = '', $xss_clean = FALSE) + public function post_get($index = '', $xss_clean = FALSE) { return isset($_POST[$index]) ? $this->post($index, $xss_clean) @@ -219,34 +270,95 @@ public function get_post($index = '', $xss_clean = FALSE) // -------------------------------------------------------------------- + /** + * Fetch an item from GET data with fallback to POST + * + * @param string $index Index for item to be fetched from $_GET or $_POST + * @param bool $xss_clean Whether to apply XSS filtering + * @return mixed + */ + public function get_post($index = '', $xss_clean = FALSE) + { + return isset($_GET[$index]) + ? $this->get($index, $xss_clean) + : $this->post($index, $xss_clean); + } + + // -------------------------------------------------------------------- + /** * Fetch an item from the COOKIE array * - * @param string - * @param bool - * @return string + * @param string $index Index for item to be fetched from $_COOKIE + * @param bool $xss_clean Whether to apply XSS filtering + * @return mixed */ public function cookie($index = '', $xss_clean = FALSE) { return $this->_fetch_from_array($_COOKIE, $index, $xss_clean); } + // -------------------------------------------------------------------- + + /** + * Fetch an item from the SERVER array + * + * @param string $index Index for item to be fetched from $_SERVER + * @param bool $xss_clean Whether to apply XSS filtering + * @return mixed + */ + public function server($index = '', $xss_clean = FALSE) + { + return $this->_fetch_from_array($_SERVER, $index, $xss_clean); + } + + // ------------------------------------------------------------------------ + + /** + * Fetch an item from the php://input stream + * + * Useful when you need to access PUT, DELETE or PATCH request data. + * + * @param string $index Index for item to be fetched + * @param bool $xss_clean Whether to apply XSS filtering + * @return mixed + */ + public function input_stream($index = '', $xss_clean = FALSE) + { + // The input stream can only be read once, so we'll need to check + // if we have already done that first. + if (is_array($this->_input_stream)) + { + return $this->_fetch_from_array($this->_input_stream, $index, $xss_clean); + } + + // Parse the input stream in our cache var + parse_str(file_get_contents('php://input'), $this->_input_stream); + if ( ! is_array($this->_input_stream)) + { + $this->_input_stream = array(); + return NULL; + } + + return $this->_fetch_from_array($this->_input_stream, $index, $xss_clean); + } + // ------------------------------------------------------------------------ /** * Set cookie * - * Accepts seven parameters, or you can submit an associative + * Accepts an arbitrary number of parameters (up to 7) or an associative * array in the first parameter containing all the values. * - * @param mixed - * @param string the value of the cookie - * @param string the number of seconds until expiration - * @param string the cookie domain. Usually: .yourdomain.com - * @param string the cookie path - * @param string the cookie prefix - * @param bool true makes the cookie secure - * @param bool true makes the cookie accessible via http(s) only (no javascript) + * @param string|mixed[] $name Cookie name or an array containing parameters + * @param string $value Cookie value + * @param int $expire Cookie expiration time in seconds + * @param string $domain Cookie domain (e.g.: '.yourdomain.com') + * @param string $path Cookie path (default: '/') + * @param string $prefix Cookie name prefix + * @param bool $secure Whether to only transfer cookies via SSL + * @param bool $httponly Whether to only makes the cookie accessible via HTTP (no javascript) * @return void */ public function set_cookie($name = '', $value = '', $expire = '', $domain = '', $path = '/', $prefix = '', $secure = FALSE, $httponly = FALSE) @@ -302,24 +414,12 @@ public function set_cookie($name = '', $value = '', $expire = '', $domain = '', // -------------------------------------------------------------------- - /** - * Fetch an item from the SERVER array - * - * @param string - * @param bool - * @return string - */ - public function server($index = '', $xss_clean = FALSE) - { - return $this->_fetch_from_array($_SERVER, $index, $xss_clean); - } - - // -------------------------------------------------------------------- - /** * Fetch the IP Address * - * @return string + * Determines and validates the visitor's IP address. + * + * @return string IP address */ public function ip_address() { @@ -328,62 +428,117 @@ public function ip_address() return $this->ip_address; } - if (config_item('proxy_ips') != '' && $this->server('HTTP_X_FORWARDED_FOR') && $this->server('REMOTE_ADDR')) + $proxy_ips = config_item('proxy_ips'); + if ( ! empty($proxy_ips) && ! is_array($proxy_ips)) { - $has_ranges = strpos($proxies, '/') !== false; - $proxies = preg_split('/[\s,]/', config_item('proxy_ips'), -1, PREG_SPLIT_NO_EMPTY); - $proxies = is_array($proxies) ? $proxies : array($proxies); - - if ($has_ranges) - { - $long_ip = ip2long($_SERVER['REMOTE_ADDR']); - $bit_32 = 1 << 32; + $proxy_ips = explode(',', str_replace(' ', '', $proxy_ips)); + } + + $this->ip_address = $this->server('REMOTE_ADDR'); - // Go through each of the IP Addresses to check for and - // test against range notation - foreach($proxies as $ip) + if ($proxy_ips) + { + foreach (array('HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_X_CLIENT_IP', 'HTTP_X_CLUSTER_CLIENT_IP') as $header) + { + if (($spoof = $this->server($header)) !== NULL) { - list($address, $mask_length) = explode('/', $ip); + // Some proxies typically list the whole chain of IP + // addresses through which the client has reached us. + // e.g. client_ip, proxy_ip1, proxy_ip2, etc. + sscanf($spoof, '%[^,]', $spoof); - // Generate the bitmask for a 32 bit IP Address - $bitmask = $bit_32 - (1 << (32 - (int)$mask_length)); - if (($long_ip & $bitmask) == $address) + if ( ! $this->valid_ip($spoof)) + { + $spoof = NULL; + } + else { - $this->ip_address = $_SERVER['HTTP_X_FORWARDED_FOR']; break; } } - - } else { - $this->ip_address = in_array($_SERVER['REMOTE_ADDR'], $proxies) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR']; } - } - elseif ( ! $this->server('HTTP_CLIENT_IP') && $this->server('REMOTE_ADDR')) - { - $this->ip_address = $_SERVER['REMOTE_ADDR']; - } - elseif ($this->server('REMOTE_ADDR') && $this->server('HTTP_CLIENT_IP')) - { - $this->ip_address = $_SERVER['HTTP_CLIENT_IP']; - } - elseif ($this->server('HTTP_CLIENT_IP')) - { - $this->ip_address = $_SERVER['HTTP_CLIENT_IP']; - } - elseif ($this->server('HTTP_X_FORWARDED_FOR')) - { - $this->ip_address = $_SERVER['HTTP_X_FORWARDED_FOR']; - } - if ($this->ip_address === FALSE) - { - return $this->ip_address = '0.0.0.0'; - } + if ($spoof) + { + for ($i = 0, $c = count($proxy_ips); $i < $c; $i++) + { + // Check if we have an IP address or a subnet + if (strpos($proxy_ips[$i], '/') === FALSE) + { + // An IP address (and not a subnet) is specified. + // We can compare right away. + if ($proxy_ips[$i] === $this->ip_address) + { + $this->ip_address = $spoof; + break; + } + + continue; + } - if (strpos($this->ip_address, ',') !== FALSE) - { - $x = explode(',', $this->ip_address); - $this->ip_address = trim(end($x)); + // We have a subnet ... now the heavy lifting begins + isset($separator) OR $separator = $this->valid_ip($this->ip_address, 'ipv6') ? ':' : '.'; + + // If the proxy entry doesn't match the IP protocol - skip it + if (strpos($proxy_ips[$i], $separator) === FALSE) + { + continue; + } + + // Convert the REMOTE_ADDR IP address to binary, if needed + if ( ! isset($ip, $sprintf)) + { + if ($separator === ':') + { + // Make sure we're have the "full" IPv6 format + $ip = explode(':', + str_replace('::', + str_repeat(':', 9 - substr_count($this->ip_address, ':')), + $this->ip_address + ) + ); + + for ($i = 0; $i < 8; $i++) + { + $ip[$i] = intval($ip[$i], 16); + } + + $sprintf = '%016b%016b%016b%016b%016b%016b%016b%016b'; + } + else + { + $ip = explode('.', $this->ip_address); + $sprintf = '%08b%08b%08b%08b'; + } + + $ip = vsprintf($sprintf, $ip); + } + + // Split the netmask length off the network address + sscanf($proxy_ips[$i], '%[^/]/%d', $netaddr, $masklen); + + // Again, an IPv6 address is most likely in a compressed form + if ($separator === ':') + { + $netaddr = explode(':', str_replace('::', str_repeat(':', 9 - substr_count($netaddr, ':')), $netaddr)); + for ($i = 0; $i < 8; $i++) + { + $netaddr[$i] = intval($netaddr[$i], 16); + } + } + else + { + $netaddr = explode('.', $netaddr); + } + + // Convert to binary and finally compare + if (strncmp($ip, vsprintf($sprintf, $netaddr), $masklen) === 0) + { + $this->ip_address = $spoof; + break; + } + } + } } if ( ! $this->valid_ip($this->ip_address)) @@ -399,8 +554,8 @@ public function ip_address() /** * Validate IP Address * - * @param string - * @param string 'ipv4' or 'ipv6' + * @param string $ip IP address + * @param string $which IP protocol: 'ipv4' or 'ipv6' * @return bool */ public function valid_ip($ip, $which = '') @@ -424,9 +579,9 @@ public function valid_ip($ip, $which = '') // -------------------------------------------------------------------- /** - * User Agent + * Fetch User Agent string * - * @return string + * @return string|null User Agent string or NULL if it doesn't exist */ public function user_agent() { @@ -435,7 +590,7 @@ public function user_agent() return $this->user_agent; } - return $this->user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : FALSE; + return $this->user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : NULL; } // -------------------------------------------------------------------- @@ -443,11 +598,12 @@ public function user_agent() /** * Sanitize Globals * - * This function does the following: + * Internal method serving for the following purposes: * - * - Unsets $_GET data (if query strings are not enabled) - * - Unsets all globals if register_globals is enabled - * - Standardizes newline characters to \n + * - Unsets $_GET data (if query strings are not enabled) + * - Unsets all globals if register_globals is enabled + * - Cleans POST, COOKIE and SERVER data + * - Standardizes newline characters to PHP_EOL * * @return void */ @@ -475,25 +631,29 @@ protected function _sanitize_globals() 'IN' ); - // Unset globals for securiy. + // Unset globals for security. // This is effectively the same as register_globals = off - foreach (array($_GET, $_POST, $_COOKIE) as $global) + // PHP 5.4 no longer has the register_globals functionality. + if ( ! is_php('5.4')) { - if (is_array($global)) + foreach (array($_GET, $_POST, $_COOKIE) as $global) { - foreach ($global as $key => $val) + if (is_array($global)) { - if ( ! in_array($key, $protected)) + foreach ($global as $key => $val) { - global $$key; - $$key = NULL; + if ( ! in_array($key, $protected)) + { + global $$key; + $$key = NULL; + } } } - } - elseif ( ! in_array($global, $protected)) - { - global $$global; - $$global = NULL; + elseif ( ! in_array($global, $protected)) + { + global $$global; + $$global = NULL; + } } } @@ -541,7 +701,7 @@ protected function _sanitize_globals() $_SERVER['PHP_SELF'] = strip_tags($_SERVER['PHP_SELF']); // CSRF Protection check - if ($this->_enable_csrf === TRUE) + if ($this->_enable_csrf === TRUE && ! $this->is_cli_request()) { $this->security->csrf_verify(); } @@ -554,10 +714,10 @@ protected function _sanitize_globals() /** * Clean Input Data * - * This is a helper function. It escapes data and - * standardizes newline characters to \n + * Internal method that aids in escaping data and + * standardizing newline characters to PHP_EOL. * - * @param string + * @param string|string[] $str Input string(s) * @return string */ protected function _clean_input_data($str) @@ -565,9 +725,9 @@ protected function _clean_input_data($str) if (is_array($str)) { $new_array = array(); - foreach ($str as $key => $val) + foreach (array_keys($str) as $key) { - $new_array[$this->_clean_input_keys($key)] = $this->_clean_input_data($val); + $new_array[$this->_clean_input_keys($key)] = $this->_clean_input_data($str[$key]); } return $new_array; } @@ -598,9 +758,9 @@ protected function _clean_input_data($str) } // Standardize newlines if needed - if ($this->_standardize_newlines === TRUE && strpos($str, "\r") !== FALSE) + if ($this->_standardize_newlines === TRUE) { - return str_replace(array("\r\n", "\r", "\r\n\n"), PHP_EOL, $str); + return preg_replace('/(?:\r\n|[\r\n])/', PHP_EOL, $str); } return $str; @@ -611,11 +771,11 @@ protected function _clean_input_data($str) /** * Clean Keys * - * This is a helper function. To prevent malicious users + * Internal method that helps to prevent malicious users * from trying to exploit keys we make sure that keys are * only named with alpha-numeric text and a few other items. * - * @param string + * @param string $str Input string * @return string */ protected function _clean_input_keys($str) @@ -623,7 +783,8 @@ protected function _clean_input_keys($str) if ( ! preg_match('/^[a-z0-9:_\/|-]+$/i', $str)) { set_status_header(503); - exit('Disallowed Key Characters.'); + echo 'Disallowed Key Characters.'; + exit(EXIT_USER_INPUT); } // Clean UTF-8 if supported @@ -640,39 +801,35 @@ protected function _clean_input_keys($str) /** * Request Headers * - * In Apache, you can simply call apache_request_headers(), however for - * people running other webservers the function is undefined. - * - * @param bool XSS cleaning + * @param bool $xss_clean Whether to apply XSS filtering * @return array */ public function request_headers($xss_clean = FALSE) { - // Look at Apache go! - if (function_exists('apache_request_headers')) + // If header is already defined, return it immediately + if ( ! empty($this->headers)) { - $headers = apache_request_headers(); + return $this->headers; } - else - { - $headers['Content-Type'] = isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : @getenv('CONTENT_TYPE'); - foreach ($_SERVER as $key => $val) - { - if (strpos($key, 'HTTP_') === 0) - { - $headers[substr($key, 5)] = $this->_fetch_from_array($_SERVER, $key, $xss_clean); - } - } + // In Apache, you can simply call apache_request_headers() + if (function_exists('apache_request_headers')) + { + return $this->headers = apache_request_headers(); } - // take SOME_HEADER and turn it into Some-Header - foreach ($headers as $key => $val) + $this->headers['Content-Type'] = isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : @getenv('CONTENT_TYPE'); + + foreach ($_SERVER as $key => $val) { - $key = str_replace('_', ' ', strtolower($key)); - $key = str_replace(' ', '-', ucwords($key)); + if (sscanf($key, 'HTTP_%s', $header) === 1) + { + // take SOME_HEADER and turn it into Some-Header + $header = str_replace('_', ' ', strtolower($header)); + $header = str_replace(' ', '-', ucwords($header)); - $this->headers[$key] = $val; + $this->headers[$header] = $this->_fetch_from_array($_SERVER, $key, $xss_clean); + } } return $this->headers; @@ -685,9 +842,9 @@ public function request_headers($xss_clean = FALSE) * * Returns the value of a single member of the headers class member * - * @param string array key for $this->headers - * @param bool XSS Clean or not - * @return mixed FALSE on failure, string on success + * @param string $index Header name + * @param bool $xss_clean Whether to apply XSS filtering + * @return string|bool The requested header on success or FALSE on failure */ public function get_request_header($index, $xss_clean = FALSE) { @@ -709,9 +866,9 @@ public function get_request_header($index, $xss_clean = FALSE) // -------------------------------------------------------------------- /** - * Is ajax Request? + * Is AJAX request? * - * Test to see if a request contains the HTTP_X_REQUESTED_WITH header + * Test to see if a request contains the HTTP_X_REQUESTED_WITH header. * * @return bool */ @@ -723,15 +880,15 @@ public function is_ajax_request() // -------------------------------------------------------------------- /** - * Is cli Request? + * Is CLI request? * - * Test to see if a request was made from the command line + * Test to see if a request was made from the command line. * * @return bool */ public function is_cli_request() { - return (php_sapi_name() === 'cli' OR defined('STDIN')); + return (PHP_SAPI === 'cli' OR defined('STDIN')); } // -------------------------------------------------------------------- @@ -739,10 +896,11 @@ public function is_cli_request() /** * Get Request Method * - * Return the Request Method + * Return the request method * - * @param bool uppercase or lowercase - * @return bool + * @param bool $upper Whether to return in upper or lower case + * (default: FALSE) + * @return string */ public function method($upper = FALSE) { diff --git a/system/core/Lang.php b/system/core/Lang.php index 3001f1b13d4..3236709f232 100644 --- a/system/core/Lang.php +++ b/system/core/Lang.php @@ -1,4 +1,4 @@ -is_loaded[$langfile]) && $this->is_loaded[$langfile] === $idiom) @@ -94,31 +96,41 @@ public function load($langfile, $idiom = '', $return = FALSE, $add_suffix = TRUE return; } - // Determine where the language file is and load it - if ($alt_path !== '' && file_exists($alt_path.'language/'.$idiom.'/'.$langfile)) + // Load the base file, so any others found can override it + $basepath = BASEPATH.'language/'.$idiom.'/'.$langfile; + if (($found = file_exists($basepath)) === TRUE) { - include($alt_path.'language/'.$idiom.'/'.$langfile); + include($basepath); + } + + // Do we have an alternative path to look in? + if ($alt_path !== '') + { + $alt_path .= 'language/'.$idiom.'/'.$langfile; + if (file_exists($alt_path)) + { + include($alt_path); + $found = TRUE; + } } else { - $found = FALSE; - foreach (get_instance()->load->get_package_paths(TRUE) as $package_path) { - if (file_exists($package_path.'language/'.$idiom.'/'.$langfile)) + $package_path .= 'language/'.$idiom.'/'.$langfile; + if ($basepath !== $package_path && file_exists($package_path)) { - include($package_path.'language/'.$idiom.'/'.$langfile); + include($package_path); $found = TRUE; break; } } - - if ($found !== TRUE) - { - show_error('Unable to load the requested language file: language/'.$idiom.'/'.$langfile); - } } + if ($found !== TRUE) + { + show_error('Unable to load the requested language file: language/'.$idiom.'/'.$langfile); + } if ( ! isset($lang) OR ! is_array($lang)) { @@ -146,17 +158,20 @@ public function load($langfile, $idiom = '', $return = FALSE, $add_suffix = TRUE // -------------------------------------------------------------------- /** - * Fetch a single line of text from the language array + * Language line + * + * Fetches a single line of text from the language array * - * @param string $line the language line - * @return string + * @param string $line Language line key + * @param bool $log_errors Whether to log an error message if the line is not found + * @return string Translation */ - public function line($line = '') + public function line($line = '', $log_errors = TRUE) { $value = ($line === '' OR ! isset($this->language[$line])) ? FALSE : $this->language[$line]; // Because killer robots like unicorns! - if ($value === FALSE) + if ($value === FALSE && $log_errors === TRUE) { log_message('error', 'Could not find the language line "'.$line.'"'); } diff --git a/system/core/Loader.php b/system/core/Loader.php index 0bc6e844abe..1709c2db1dc 100644 --- a/system/core/Loader.php +++ b/system/core/Loader.php @@ -1,4 +1,4 @@ - TRUE); /** * List of paths to load libraries from * - * @var array + * @var array */ - protected $_ci_library_paths = array(); + protected $_ci_library_paths = array(APPPATH, BASEPATH); /** * List of paths to load models from * - * @var array + * @var array */ - protected $_ci_model_paths = array(); + protected $_ci_model_paths = array(APPPATH); /** * List of paths to load helpers from * - * @var array + * @var array */ - protected $_ci_helper_paths = array(); - - /** - * List of loaded base classes - * - * @var array - */ - protected $_base_classes = array(); // Set by the controller class + protected $_ci_helper_paths = array(APPPATH, BASEPATH); /** * List of cached variables * - * @var array + * @var array */ protected $_ci_cached_vars = array(); /** * List of loaded classes * - * @var array + * @var array */ protected $_ci_classes = array(); - /** - * List of loaded files - * - * @var array - */ - protected $_ci_loaded_files = array(); - /** * List of loaded models * - * @var array + * @var array */ protected $_ci_models = array(); /** * List of loaded helpers * - * @var array + * @var array */ protected $_ci_helpers = array(); /** * List of class name mappings * - * @var array + * @var array */ protected $_ci_varmap = array( 'unit_test' => 'unit', 'user_agent' => 'agent' ); + // -------------------------------------------------------------------- + /** - * Constructor + * Class constructor * - * Sets the path to the view files and gets the initial output buffering level + * Sets component load paths, gets the initial output buffering level. * * @return void */ public function __construct() { - $this->_ci_ob_level = ob_get_level(); - $this->_ci_library_paths = array(APPPATH, BASEPATH); - $this->_ci_helper_paths = array(APPPATH, BASEPATH); - $this->_ci_model_paths = array(APPPATH); - $this->_ci_view_paths = array(VIEWPATH => TRUE); + $this->_ci_ob_level = ob_get_level(); + $this->_ci_classes =& is_loaded(); log_message('debug', 'Loader Class Initialized'); } @@ -147,21 +133,17 @@ public function __construct() // -------------------------------------------------------------------- /** - * Initialize the Loader + * Initializer * - * This method is called once in CI_Controller. - * - * @return object + * @todo Figure out a way to move this to the constructor + * without breaking *package_path*() methods. + * @uses CI_Loader::_ci_autoloader() + * @used-by CI_Controller::__construct() + * @return void */ public function initialize() { - $this->_ci_classes = array(); - $this->_ci_loaded_files = array(); - $this->_ci_models = array(); - $this->_base_classes =& is_loaded(); - $this->_ci_autoloader(); - return $this; } // -------------------------------------------------------------------- @@ -169,36 +151,38 @@ public function initialize() /** * Is Loaded * - * A utility function to test if a class is in the self::$_ci_classes array. - * This function returns the object name if the class tested for is loaded, - * and returns FALSE if it isn't. + * A utility method to test if a class is in the self::$_ci_classes array. * - * It is mainly used in the form_helper -> _get_validation_object() + * @used-by Mainly used by Form Helper function _get_validation_object(). * - * @param string class being checked for - * @return mixed class object name on the CI SuperObject or FALSE + * @param string $class Class name to check for + * @return string|bool Class object name if loaded or FALSE */ public function is_loaded($class) { - return isset($this->_ci_classes[$class]) ? $this->_ci_classes[$class] : FALSE; + return array_search(ucfirst($class), $this->_ci_classes, TRUE); } // -------------------------------------------------------------------- /** - * Class Loader + * Library Loader * - * This function lets users load and instantiate classes. - * It is designed to be called from a user's app controllers. + * Loads and instantiates libraries. + * Designed to be called from application controllers. * - * @param string the name of the class - * @param mixed the optional parameters - * @param string an optional object name + * @param string $library Library name + * @param array $params Optional parameters to pass to the library class constructor + * @param string $object_name An optional object name to assign to * @return void */ public function library($library = '', $params = NULL, $object_name = NULL) { - if (is_array($library)) + if (empty($library)) + { + return; + } + elseif (is_array($library)) { foreach ($library as $class) { @@ -208,12 +192,7 @@ public function library($library = '', $params = NULL, $object_name = NULL) return; } - if ($library === '' OR isset($this->_base_classes[$library])) - { - return FALSE; - } - - if ( ! is_null($params) && ! is_array($params)) + if ($params !== NULL && ! is_array($params)) { $params = NULL; } @@ -226,26 +205,25 @@ public function library($library = '', $params = NULL, $object_name = NULL) /** * Model Loader * - * This function lets users load and instantiate models. + * Loads and instantiates libraries. * - * @param string the name of the class - * @param string name for the model - * @param bool database connection + * @param string $model Model name + * @param string $name An optional object name to assign to + * @param bool $db_conn An optional database connection configuration to initialize * @return void */ public function model($model, $name = '', $db_conn = FALSE) { - if (is_array($model)) + if (empty($model)) { - foreach ($model as $class) - { - $this->model($class); - } return; } - - if ($model === '') + elseif (is_array($model)) { + foreach ($model as $key => $value) + { + $this->model(is_int($key) ? $value : $key, $value); + } return; } @@ -277,33 +255,32 @@ public function model($model, $name = '', $db_conn = FALSE) show_error('The model name you are loading is the name of a resource that is already being used: '.$name); } - $model = strtolower($model); - - foreach ($this->_ci_model_paths as $mod_path) + if ($db_conn !== FALSE && ! class_exists('CI_DB', FALSE)) { - if ( ! file_exists($mod_path.'models/'.$path.$model.'.php')) + if ($db_conn === TRUE) { - continue; + $db_conn = ''; } - if ($db_conn !== FALSE && ! class_exists('CI_DB')) - { - if ($db_conn === TRUE) - { - $db_conn = ''; - } + $CI->load->database($db_conn, FALSE, TRUE); + } - $CI->load->database($db_conn, FALSE, TRUE); - } + if ( ! class_exists('CI_Model', FALSE)) + { + load_class('Model', 'core'); + } - if ( ! class_exists('CI_Model')) + $model = ucfirst(strtolower($model)); + + foreach ($this->_ci_model_paths as $mod_path) + { + if ( ! file_exists($mod_path.'models/'.$path.$model.'.php')) { - load_class('Model', 'core'); + continue; } require_once($mod_path.'models/'.$path.$model.'.php'); - $model = ucfirst($model); $CI->$name = new $model(); $this->_ci_models[] = $name; return; @@ -318,10 +295,13 @@ public function model($model, $name = '', $db_conn = FALSE) /** * Database Loader * - * @param string the DB credentials - * @param bool whether to return the DB object - * @param bool whether to enable query builder (this allows us to override the config setting) - * @return object + * @param mixed $params Database configuration options + * @param bool $return Whether to return the database object + * @param bool $query_builder Whether to enable Query Builder + * (overrides the configuration setting) + * + * @return void|object|bool Database object if $return is set to TRUE, + * FALSE on failure, void in any other case */ public function database($params = '', $return = FALSE, $query_builder = NULL) { @@ -329,7 +309,7 @@ public function database($params = '', $return = FALSE, $query_builder = NULL) $CI =& get_instance(); // Do we even need to load the database class? - if (class_exists('CI_DB') && $return === FALSE && $query_builder === NULL && isset($CI->db) && is_object($CI->db)) + if ($return === FALSE && $query_builder === NULL && isset($CI->db) && is_object($CI->db) && ! empty($CI->db->conn_id)) { return FALSE; } @@ -352,28 +332,32 @@ public function database($params = '', $return = FALSE, $query_builder = NULL) // -------------------------------------------------------------------- /** - * Load the Utilities Class + * Load the Database Utilities Class * - * @return string + * @param object $db Database object + * @param bool $return Whether to return the DB Forge class object or not + * @return void|object */ - public function dbutil() + public function dbutil($db = NULL, $return = FALSE) { - if ( ! class_exists('CI_DB')) - { - $this->database(); - } - $CI =& get_instance(); - // for backwards compatibility, load dbforge so we can extend dbutils off it - // this use is deprecated and strongly discouraged - $CI->load->dbforge(); + if ( ! is_object($db) OR ! ($db instanceof CI_DB)) + { + class_exists('CI_DB', FALSE) OR $this->database(); + $db =& $CI->db; + } require_once(BASEPATH.'database/DB_utility.php'); - require_once(BASEPATH.'database/drivers/'.$CI->db->dbdriver.'/'.$CI->db->dbdriver.'_utility.php'); - $class = 'CI_DB_'.$CI->db->dbdriver.'_utility'; + require_once(BASEPATH.'database/drivers/'.$db->dbdriver.'/'.$db->dbdriver.'_utility.php'); + $class = 'CI_DB_'.$db->dbdriver.'_utility'; - $CI->dbutil = new $class(); + if ($return === TRUE) + { + return new $class($db); + } + + $CI->dbutil = new $class($db); } // -------------------------------------------------------------------- @@ -381,40 +365,56 @@ public function dbutil() /** * Load the Database Forge Class * - * @return string + * @param object $db Database object + * @param bool $return Whether to return the DB Forge class object or not + * @return void|object */ - public function dbforge() + public function dbforge($db = NULL, $return = FALSE) { - if ( ! class_exists('CI_DB')) + $CI =& get_instance(); + if ( ! is_object($db) OR ! ($db instanceof CI_DB)) { - $this->database(); + class_exists('CI_DB', FALSE) OR $this->database(); + $db =& $CI->db; } - $CI =& get_instance(); - require_once(BASEPATH.'database/DB_forge.php'); - require_once(BASEPATH.'database/drivers/'.$CI->db->dbdriver.'/'.$CI->db->dbdriver.'_forge.php'); - $class = 'CI_DB_'.$CI->db->dbdriver.'_forge'; + require_once(BASEPATH.'database/drivers/'.$db->dbdriver.'/'.$db->dbdriver.'_forge.php'); - $CI->dbforge = new $class(); + if ( ! empty($db->subdriver)) + { + $driver_path = BASEPATH.'database/drivers/'.$db->dbdriver.'/subdrivers/'.$db->dbdriver.'_'.$db->subdriver.'_forge.php'; + if (file_exists($driver_path)) + { + require_once($driver_path); + $class = 'CI_DB_'.$db->dbdriver.'_'.$db->subdriver.'_forge'; + } + } + else + { + $class = 'CI_DB_'.$db->dbdriver.'_forge'; + } + + if ($return === TRUE) + { + return new $class($db); + } + + $CI->dbforge = new $class($db); } // -------------------------------------------------------------------- /** - * Load View + * View Loader * - * This function is used to load a "view" file. It has three parameters: + * Loads "view" files. * - * 1. The name of the "view" file to be included. - * 2. An associative array of data to be extracted for use in the view. - * 3. TRUE/FALSE - whether to return the data or load it. In - * some cases it's advantageous to be able to return data so that - * a developer can process it in some way. - * - * @param string - * @param array - * @param bool + * @param string $view View name + * @param array $vars An associative array of data + * to be extracted for use in the view + * @param bool $return Whether to return the view output + * or leave it to the Output class * @return void */ public function view($view, $vars = array(), $return = FALSE) @@ -425,13 +425,11 @@ public function view($view, $vars = array(), $return = FALSE) // -------------------------------------------------------------------- /** - * Load File - * - * This is a generic file loader + * Generic File Loader * - * @param string - * @param bool - * @return string + * @param string $path File path + * @param bool $return Whether to return the file output + * @return void|string */ public function file($path, $return = FALSE) { @@ -446,13 +444,15 @@ public function file($path, $return = FALSE) * Once variables are set they become available within * the controller class and its "view" files. * - * @param array - * @param string + * @param array|object|string $vars + * An associative array or object containing values + * to be set, or a value's name if string + * @param string $val Value to set, only used if $vars is a string * @return void */ public function vars($vars = array(), $val = '') { - if ($val !== '' && is_string($vars)) + if (is_string($vars)) { $vars = array($vars => $val); } @@ -475,8 +475,8 @@ public function vars($vars = array(), $val = '') * * Check if a variable is set and retrieve it. * - * @param array - * @return void + * @param string $key Variable name + * @return mixed The variable or NULL if not found */ public function get_var($key) { @@ -488,7 +488,7 @@ public function get_var($key) /** * Get Variables * - * Retrieve all loaded variables + * Retrieves all loaded variables. * * @return array */ @@ -500,11 +500,9 @@ public function get_vars() // -------------------------------------------------------------------- /** - * Load Helper - * - * This function loads the specified helper file. + * Helper Loader * - * @param mixed + * @param string|string[] $helpers Helper name(s) * @return void */ public function helper($helpers = array()) @@ -516,27 +514,34 @@ public function helper($helpers = array()) continue; } - $ext_helper = APPPATH.'helpers/'.config_item('subclass_prefix').$helper.'.php'; - // Is this a helper extension request? - if (file_exists($ext_helper)) + $ext_helper = config_item('subclass_prefix').$helper; + $ext_loaded = FALSE; + foreach ($this->_ci_helper_paths as $path) { - $base_helper = BASEPATH.'helpers/'.$helper.'.php'; + if (file_exists($path.'helpers/'.$ext_helper.'.php')) + { + include_once($path.'helpers/'.$ext_helper.'.php'); + $ext_loaded = TRUE; + } + } + // If we have loaded extensions - check if the base one is here + if ($ext_loaded === TRUE) + { + $base_helper = BASEPATH.'helpers/'.$helper.'.php'; if ( ! file_exists($base_helper)) { show_error('Unable to load the requested file: helpers/'.$helper.'.php'); } - include_once($ext_helper); include_once($base_helper); - $this->_ci_helpers[$helper] = TRUE; log_message('debug', 'Helper loaded: '.$helper); continue; } - // Try to load the helper + // No extensions found ... try loading regular helpers and/or overrides foreach ($this->_ci_helper_paths as $path) { if (file_exists($path.'helpers/'.$helper.'.php')) @@ -562,10 +567,11 @@ public function helper($helpers = array()) /** * Load Helpers * - * This is simply an alias to the above function in case the - * user has written the plural form of this function. + * An alias for the helper() method in case the developer has + * written the plural form of it. * - * @param array + * @uses CI_Loader::helper() + * @param string|string[] $helpers Helper name(s) * @return void */ public function helpers($helpers = array()) @@ -576,22 +582,21 @@ public function helpers($helpers = array()) // -------------------------------------------------------------------- /** - * Loads a language file + * Language Loader + * + * Loads language files. * - * @param array - * @param string + * @param string|string[] $files List of language file names to load + * @param string Language name * @return void */ - public function language($file = array(), $lang = '') + public function language($files = array(), $lang = '') { $CI =& get_instance(); - if ( ! is_array($file)) - { - $file = array($file); - } + is_array($files) OR $files = array($files); - foreach ($file as $langfile) + foreach ($files as $langfile) { $CI->lang->load($langfile, $lang); } @@ -600,30 +605,35 @@ public function language($file = array(), $lang = '') // -------------------------------------------------------------------- /** - * Loads a config file + * Config Loader * - * @param string - * @param bool - * @param bool - * @return void + * Loads a config file (an alias for CI_Config::load()). + * + * @uses CI_Config::load() + * @param string $file Configuration file name + * @param bool $use_sections Whether configuration values should be loaded into their own section + * @param bool $fail_gracefully Whether to just return FALSE or display an error message + * @return bool TRUE if the file was loaded correctly or FALSE on failure */ public function config($file = '', $use_sections = FALSE, $fail_gracefully = FALSE) { $CI =& get_instance(); - $CI->config->load($file, $use_sections, $fail_gracefully); + return $CI->config->load($file, $use_sections, $fail_gracefully); } // -------------------------------------------------------------------- /** - * Driver + * Driver Loader * - * Loads a driver library + * Loads a driver library. * - * @param mixed the name of the class or array of classes - * @param mixed the optional parameters - * @param string an optional object name - * @return void + * @param string|string[] $library Driver name(s) + * @param array $params Optional parameters to pass to the driver + * @param string $object_name An optional object name to assign to + * + * @return void|object|bool Object or FALSE on failure if $library is a string + * and $object_name is set. void otherwise. */ public function driver($library = '', $params = NULL, $object_name = NULL) { @@ -633,18 +643,18 @@ public function driver($library = '', $params = NULL, $object_name = NULL) { $this->driver($driver); } - return FALSE; + return; } - if ( ! class_exists('CI_Driver_Library')) + if ($library === '') { - // we aren't instantiating an object here, that'll be done by the Library itself - require BASEPATH.'libraries/Driver.php'; + return FALSE; } - if ($library === '') + if ( ! class_exists('CI_Driver_Library', FALSE)) { - return FALSE; + // We aren't instantiating an object here, just making the base class available + require BASEPATH.'libraries/Driver.php'; } // We can save the loader some time since Drivers will *always* be in a subfolder, @@ -662,10 +672,16 @@ public function driver($library = '', $params = NULL, $object_name = NULL) /** * Add Package Path * - * Prepends a parent path to the library, model, helper, and config path arrays + * Prepends a parent path to the library, model, helper and config + * path arrays. * - * @param string - * @param bool + * @see CI_Loader::$_ci_library_paths + * @see CI_Loader::$_ci_model_paths + * @see CI_Loader::$_ci_helper_paths + * @see CI_Config::$_config_paths + * + * @param string $path Path to add + * @param bool $view_cascade (default: TRUE) * @return void */ public function add_package_path($path, $view_cascade = TRUE) @@ -680,7 +696,7 @@ public function add_package_path($path, $view_cascade = TRUE) // Add config file path $config =& $this->_ci_get_component('config'); - array_push($config->_config_paths, $path); + $config->_config_paths[] = $path; } // -------------------------------------------------------------------- @@ -688,14 +704,14 @@ public function add_package_path($path, $view_cascade = TRUE) /** * Get Package Paths * - * Return a list of all package paths, by default it will ignore BASEPATH. + * Return a list of all package paths. * - * @param string - * @return void + * @param bool $include_base Whether to include BASEPATH (default: FALSE) + * @return array */ public function get_package_paths($include_base = FALSE) { - return $include_base === TRUE ? $this->_ci_library_paths : $this->_ci_model_paths; + return ($include_base === TRUE) ? $this->_ci_library_paths : $this->_ci_model_paths; } // -------------------------------------------------------------------- @@ -703,14 +719,14 @@ public function get_package_paths($include_base = FALSE) /** * Remove Package Path * - * Remove a path from the library, model, and helper path arrays if it exists - * If no path is provided, the most recently added path is removed. + * Remove a path from the library, model, helper and/or config + * path arrays if it exists. If no path is provided, the most recently + * added path will be removed removed. * - * @param string - * @param bool + * @param string $path Path to remove * @return void */ - public function remove_package_path($path = '', $remove_config_path = TRUE) + public function remove_package_path($path = '') { $config =& $this->_ci_get_component('config'); @@ -755,13 +771,16 @@ public function remove_package_path($path = '', $remove_config_path = TRUE) // -------------------------------------------------------------------- /** - * Loader + * Internal CI Data Loader + * + * Used to load views and files. * - * This function is used to load views and files. * Variables are prefixed with _ci_ to avoid symbol collision with - * variables made available to view files + * variables made available to view files. * - * @param array + * @used-by CI_Loader::view() + * @used-by CI_Loader::file() + * @param array $_ci_data Data to load * @return void */ protected function _ci_load($_ci_data) @@ -785,11 +804,11 @@ protected function _ci_load($_ci_data) $_ci_ext = pathinfo($_ci_view, PATHINFO_EXTENSION); $_ci_file = ($_ci_ext === '') ? $_ci_view.'.php' : $_ci_view; - foreach ($this->_ci_view_paths as $view_file => $cascade) + foreach ($this->_ci_view_paths as $_ci_view_file => $cascade) { - if (file_exists($view_file.$_ci_file)) + if (file_exists($_ci_view_file.$_ci_file)) { - $_ci_path = $view_file.$_ci_file; + $_ci_path = $_ci_view_file.$_ci_file; $file_exists = TRUE; break; } @@ -837,17 +856,19 @@ protected function _ci_load($_ci_data) * We buffer the output for two reasons: * 1. Speed. You get a significant speed boost. * 2. So that the final rendered template can be post-processed by - * the output class. Why do we need post processing? For one thing, - * in order to show the elapsed page load time. Unless we can - * intercept the content right before it's sent to the browser and - * then stop the timer it won't be accurate. + * the output class. Why do we need post processing? For one thing, + * in order to show the elapsed page load time. Unless we can + * intercept the content right before it's sent to the browser and + * then stop the timer it won't be accurate. */ ob_start(); // If the PHP installation does not support short tags we'll // do a little string replacement, changing the short tags // to standard PHP echo statements. - if ( ! is_php('5.4') && (bool) @ini_get('short_open_tag') === FALSE && config_item('rewrite_short_tags') === TRUE) + if ( ! is_php('5.4') && (bool) @ini_get('short_open_tag') === FALSE + && config_item('rewrite_short_tags') === TRUE && function_usable('eval') + ) { echo eval('?>'.preg_replace('/;*\s*\?>/', '; ?>', str_replace('_ci_loaded_files)) + // Safety: Was the class already loaded by a previous call? + if (class_exists(config_item('subclass_prefix').$class, FALSE)) + { + // Before we deem this to be a duplicate request, let's see + // if a custom object name is being supplied. If so, we'll + // return a new instance of the object + if ($object_name !== NULL) { - // Before we deem this to be a duplicate request, let's see - // if a custom object name is being supplied. If so, we'll - // return a new instance of the object - if ( ! is_null($object_name)) + $CI =& get_instance(); + if ( ! isset($CI->$object_name)) { - $CI =& get_instance(); - if ( ! isset($CI->$object_name)) - { - return $this->_ci_init_class($class, config_item('subclass_prefix'), $params, $object_name); - } + return $this->_ci_init_class($class, config_item('subclass_prefix'), $params, $object_name); } - - $is_duplicate = TRUE; - log_message('debug', $class.' class already loaded. Second attempt ignored.'); - return; } - include_once($baseclass); - include_once($subclass); - $this->_ci_loaded_files[] = $subclass; - - return $this->_ci_init_class($class, config_item('subclass_prefix'), $params, $object_name); + log_message('debug', $class.' class already loaded. Second attempt ignored.'); + return; } - // Lets search for the requested library file and load it. - $is_duplicate = FALSE; - foreach ($this->_ci_library_paths as $path) - { - $filepath = $path.'libraries/'.$subdir.$class.'.php'; + include_once($baseclass); + include_once($subclass); - // Does the file exist? No? Bummer... - if ( ! file_exists($filepath)) - { - continue; - } + return $this->_ci_init_class($class, config_item('subclass_prefix'), $params, $object_name); + } - // Safety: Was the class already loaded by a previous call? - if (in_array($filepath, $this->_ci_loaded_files)) + // Let's search for the requested library file and load it. + foreach ($this->_ci_library_paths as $path) + { + $filepath = $path.'libraries/'.$subdir.$class.'.php'; + + // Safety: Was the class already loaded by a previous call? + if (class_exists($class, FALSE)) + { + // Before we deem this to be a duplicate request, let's see + // if a custom object name is being supplied. If so, we'll + // return a new instance of the object + if ($object_name !== NULL) { - // Before we deem this to be a duplicate request, let's see - // if a custom object name is being supplied. If so, we'll - // return a new instance of the object - if ( ! is_null($object_name)) + $CI =& get_instance(); + if ( ! isset($CI->$object_name)) { - $CI =& get_instance(); - if ( ! isset($CI->$object_name)) - { - return $this->_ci_init_class($class, '', $params, $object_name); - } + return $this->_ci_init_class($class, '', $params, $object_name); } - - $is_duplicate = TRUE; - log_message('debug', $class.' class already loaded. Second attempt ignored.'); - return; } - include_once($filepath); - $this->_ci_loaded_files[] = $filepath; - return $this->_ci_init_class($class, '', $params, $object_name); + log_message('debug', $class.' class already loaded. Second attempt ignored.'); + return; + } + // Does the file exist? No? Bummer... + elseif ( ! file_exists($filepath)) + { + continue; } - } // END FOREACH + include_once($filepath); + return $this->_ci_init_class($class, '', $params, $object_name); + } // One last attempt. Maybe the library is in a subdirectory, but it wasn't specified? if ($subdir === '') { - $path = strtolower($class).'/'.$class; - return $this->_ci_load_class($path, $params); + return $this->_ci_load_class($class.'/'.$class, $params, $object_name); } // If we got this far we were unable to find the requested class. - // We do not issue errors if the load call failed due to a duplicate request - if ($is_duplicate === FALSE) - { - log_message('error', 'Unable to load the requested class: '.$class); - show_error('Unable to load the requested class: '.$class); - } + log_message('error', 'Unable to load the requested class: '.$class); + show_error('Unable to load the requested class: '.$class); } // -------------------------------------------------------------------- /** - * Instantiates a class + * Internal CI Class Instantiator * - * @param string - * @param string - * @param bool - * @param string an optional object name + * @used-by CI_Loader::_ci_load_class() + * + * @param string $class Class name + * @param string $prefix Class name prefix + * @param array|null|bool $config Optional configuration to pass to the class constructor: + * FALSE to skip; + * NULL to search in config paths; + * array containing configuration data + * @param string $object_name Optional object name to assign to * @return void */ protected function _ci_init_class($class, $prefix = '', $config = FALSE, $object_name = NULL) @@ -1043,12 +1058,12 @@ protected function _ci_init_class($class, $prefix = '', $config = FALSE, $object // We test for both uppercase and lowercase, for servers that // are case-sensitive with regard to file names. Check for environment // first, global next - if (defined('ENVIRONMENT') && file_exists($path.'config/'.ENVIRONMENT.'/'.strtolower($class).'.php')) + if (file_exists($path.'config/'.ENVIRONMENT.'/'.strtolower($class).'.php')) { include($path.'config/'.ENVIRONMENT.'/'.strtolower($class).'.php'); break; } - elseif (defined('ENVIRONMENT') && file_exists($path.'config/'.ENVIRONMENT.'/'.ucfirst(strtolower($class)).'.php')) + elseif (file_exists($path.'config/'.ENVIRONMENT.'/'.ucfirst(strtolower($class)).'.php')) { include($path.'config/'.ENVIRONMENT.'/'.ucfirst(strtolower($class)).'.php'); break; @@ -1069,11 +1084,11 @@ protected function _ci_init_class($class, $prefix = '', $config = FALSE, $object if ($prefix === '') { - if (class_exists('CI_'.$class)) + if (class_exists('CI_'.$class, FALSE)) { $name = 'CI_'.$class; } - elseif (class_exists(config_item('subclass_prefix').$class)) + elseif (class_exists(config_item('subclass_prefix').$class, FALSE)) { $name = config_item('subclass_prefix').$class; } @@ -1088,7 +1103,7 @@ protected function _ci_init_class($class, $prefix = '', $config = FALSE, $object } // Is the class name valid? - if ( ! class_exists($name)) + if ( ! class_exists($name, FALSE)) { log_message('error', 'Non-existent class: '.$name); show_error('Non-existent class: '.$name); @@ -1096,45 +1111,50 @@ protected function _ci_init_class($class, $prefix = '', $config = FALSE, $object // Set the variable name we will assign the class to // Was a custom class name supplied? If so we'll use it - $class = strtolower($class); - - if (is_null($object_name)) + if (empty($object_name)) { - $classvar = isset($this->_ci_varmap[$class]) ? $this->_ci_varmap[$class] : $class; + $object_name = strtolower($class); + if (isset($this->_ci_varmap[$object_name])) + { + $object_name = $this->_ci_varmap[$object_name]; + } } - else + + // Don't overwrite existing properties + $CI =& get_instance(); + if (isset($CI->$object_name)) { - $classvar = $object_name; + if ($CI->$object_name instanceof $name) + { + log_message('debug', $class." has already been instantiated as '".$object_name."'. Second attempt aborted."); + return; + } + + show_error("Resource '".$object_name."' already exists and is not a ".$class." instance."); } // Save the class name and object name - $this->_ci_classes[$class] = $classvar; + $this->_ci_classes[$object_name] = $class; // Instantiate the class - $CI =& get_instance(); - if ($config !== NULL) - { - $CI->$classvar = new $name($config); - } - else - { - $CI->$classvar = new $name(); - } + $CI->$object_name = isset($config) + ? new $name($config) + : new $name(); } // -------------------------------------------------------------------- /** - * Autoloader + * CI Autoloader * - * The config/autoload.php file contains an array that permits sub-systems, - * libraries, and helpers to be loaded automatically. + * Loads component listed in the config/autoload.php file. * + * @used-by CI_Loader::initialize() * @return void */ protected function _ci_autoloader() { - if (defined('ENVIRONMENT') && file_exists(APPPATH.'config/'.ENVIRONMENT.'/autoload.php')) + if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/autoload.php')) { include(APPPATH.'config/'.ENVIRONMENT.'/autoload.php'); } @@ -1176,6 +1196,15 @@ protected function _ci_autoloader() } } + // Autoload drivers + if (isset($autoload['drivers'])) + { + foreach ($autoload['drivers'] as $item) + { + $this->driver($item); + } + } + // Load libraries if (isset($autoload['libraries']) && count($autoload['libraries']) > 0) { @@ -1203,11 +1232,12 @@ protected function _ci_autoloader() // -------------------------------------------------------------------- /** - * Object to Array + * CI Object to Array translator * - * Takes an object as input and converts the class variables to array key/vals + * Takes an object as input and converts the class variables to + * an associative array with key/value pairs. * - * @param object + * @param object $object Object data to translate * @return array */ protected function _ci_object_to_array($object) @@ -1218,9 +1248,11 @@ protected function _ci_object_to_array($object) // -------------------------------------------------------------------- /** - * Get a reference to a specific library or model + * CI Component getter + * + * Get a reference to a specific library or model. * - * @param string + * @param string $component Component name * @return bool */ protected function &_ci_get_component($component) @@ -1234,10 +1266,11 @@ protected function &_ci_get_component($component) /** * Prep filename * - * This function preps the name of various items to make loading them more reliable. + * This function prepares filenames of various items to + * make their loading more reliable. * - * @param mixed - * @param string + * @param string|string[] $filename Filename(s) + * @param string $extension Filename extension * @return array */ protected function _ci_prep_filename($filename, $extension) diff --git a/system/libraries/Log.php b/system/core/Log.php similarity index 76% rename from system/libraries/Log.php rename to system/core/Log.php index baac80121ad..e4d72b5440b 100644 --- a/system/libraries/Log.php +++ b/system/core/Log.php @@ -1,4 +1,4 @@ - 1, 'DEBUG' => 2, 'INFO' => 3, 'ALL' => 4); + protected $_levels = array('ERROR' => 1, 'DEBUG' => 2, 'INFO' => 3, 'ALL' => 4); + + // -------------------------------------------------------------------- /** - * Initialize Logging class + * Class constructor * * @return void */ @@ -95,6 +105,10 @@ public function __construct() $config =& get_config(); $this->_log_path = ($config['log_path'] !== '') ? $config['log_path'] : APPPATH.'logs/'; + $this->_file_ext = (isset($config['log_file_extension']) && $config['log_file_extension'] !== '') + ? ltrim($config['log_file_extension'], '.') : 'php'; + + file_exists($this->_log_path) OR mkdir($this->_log_path, DIR_WRITE_MODE, TRUE); if ( ! is_dir($this->_log_path) OR ! is_really_writable($this->_log_path)) { @@ -124,12 +138,12 @@ public function __construct() * * Generally this function will be called using the global log_message() function * - * @param string the error level + * @param string the error level: 'error', 'debug' or 'info' * @param string the error message * @param bool whether the error is a native PHP error * @return bool */ - public function write_log($level = 'error', $msg, $php_error = FALSE) + public function write_log($level, $msg, $php_error = FALSE) { if ($this->_enabled === FALSE) { @@ -144,14 +158,17 @@ public function write_log($level = 'error', $msg, $php_error = FALSE) return FALSE; } - - $filepath = $this->_log_path.'log-'.date('Y-m-d').'.php'; - $message = ''; + $filepath = $this->_log_path.'log-'.date('Y-m-d').'.'.$this->_file_ext; + $message = ''; if ( ! file_exists($filepath)) { $newfile = TRUE; - $message .= '<'."?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); ?".">\n\n"; + // Only add protection to php files + if ($this->_file_ext === 'php') + { + $message .= "\n\n"; + } } if ( ! $fp = @fopen($filepath, FOPEN_WRITE_CREATE)) @@ -177,4 +194,4 @@ public function write_log($level = 'error', $msg, $php_error = FALSE) } /* End of file Log.php */ -/* Location: ./system/libraries/Log.php */ \ No newline at end of file +/* Location: ./system/core/Log.php */ \ No newline at end of file diff --git a/system/core/Model.php b/system/core/Model.php index 9bc9f879f46..1eb6f909b0b 100644 --- a/system/core/Model.php +++ b/system/core/Model.php @@ -1,4 +1,4 @@ -final_output == '') + if (empty($this->final_output)) { $this->final_output = $output; } @@ -175,14 +180,14 @@ public function append_output($output) /** * Set Header * - * Lets you set a server header which will be outputted with the final display. + * Lets you set a server header which will be sent with the final output. * - * Note: If a file is cached, headers will not be sent. We need to figure out - * how to permit header data to be saved with the cache data... + * Note: If a file is cached, headers will not be sent. + * @todo We need to figure out how to permit headers to be cached. * - * @param string - * @param bool - * @return void + * @param string $header Header + * @param bool $replace Whether to replace the old header value, if already set + * @return CI_Output */ public function set_header($header, $replace = TRUE) { @@ -192,7 +197,7 @@ public function set_header($header, $replace = TRUE) // We'll just skip content-length in those cases. if ($this->_zlib_oc && strncasecmp($header, 'content-length', 14) === 0) { - return; + return $this; } $this->headers[] = array($header, $replace); @@ -202,10 +207,11 @@ public function set_header($header, $replace = TRUE) // -------------------------------------------------------------------- /** - * Set Content Type Header + * Set Content-Type Header * - * @param string extension of the file we're outputting - * @return void + * @param string $mime_type Extension of the file we're outputting + * @param string $charset Character set (default: NULL) + * @return CI_Output */ public function set_content_type($mime_type, $charset = NULL) { @@ -233,7 +239,7 @@ public function set_content_type($mime_type, $charset = NULL) } $header = 'Content-Type: '.$mime_type - .(empty($charset) ? NULL : '; charset='.strtolower($charset)); + .(empty($charset) ? NULL : '; charset='.$charset); $this->headers[] = array($header, TRUE); return $this; @@ -242,7 +248,7 @@ public function set_content_type($mime_type, $charset = NULL) // -------------------------------------------------------------------- /** - * Get Current Content Type Header + * Get Current Content-Type Header * * @return string 'text/html', if not already set */ @@ -250,9 +256,9 @@ public function get_content_type() { for ($i = 0, $c = count($this->headers); $i < $c; $i++) { - if (preg_match('/^Content-Type:\s(.+)$/', $this->headers[$i][0], $matches)) + if (sscanf($this->headers[$i][0], 'Content-Type: %[^;]', $content_type) === 1) { - return $matches[1]; + return $content_type; } } @@ -261,13 +267,48 @@ public function get_content_type() // -------------------------------------------------------------------- + /** + * Get Header + * + * @param string $header_name + * @return string + */ + public function get_header($header) + { + // Combine headers already sent with our batched headers + $headers = array_merge( + // We only need [x][0] from our multi-dimensional array + array_map('array_shift', $this->headers), + headers_list() + ); + + if (empty($headers) OR empty($header)) + { + return NULL; + } + + for ($i = 0, $c = count($headers); $i < $c; $i++) + { + if (strncasecmp($header, $headers[$i], $l = strlen($header)) === 0) + { + return trim(substr($headers[$i], $l+1)); + } + } + + return NULL; + } + + // -------------------------------------------------------------------- + /** * Set HTTP Status Header - * moved to Common procedural functions in 1.7.2 * - * @param int the status code - * @param string - * @return void + * As of version 1.7.2, this is an alias for common function + * set_status_header(). + * + * @param int $code Status code (default: 200) + * @param string $text Optional message + * @return CI_Output */ public function set_status_header($code = 200, $text = '') { @@ -280,8 +321,8 @@ public function set_status_header($code = 200, $text = '') /** * Enable/disable Profiler * - * @param bool - * @return void + * @param bool $val TRUE to enable or FALSE to disable + * @return CI_Output */ public function enable_profiler($val = TRUE) { @@ -294,10 +335,11 @@ public function enable_profiler($val = TRUE) /** * Set Profiler Sections * - * Allows override of default / config settings for Profiler section display + * Allows override of default/config settings for + * Profiler section display. * - * @param array - * @return void + * @param array $sections Profiler sections + * @return CI_Output */ public function set_profiler_sections($sections) { @@ -320,8 +362,8 @@ public function set_profiler_sections($sections) /** * Set Cache * - * @param int - * @return void + * @param int $time Cache expiration time in seconds + * @return CI_Output */ public function cache($time) { @@ -334,16 +376,16 @@ public function cache($time) /** * Display Output * - * All "view" data is automatically put into this variable by the controller class: + * Processes sends the sends finalized output data to the browser along + * with any server headers and profile data. It also stops benchmark + * timers so the page rendering speed and memory usage can be shown. * - * $this->final_output + * Note: All "view" data is automatically put into $this->final_output + * by controller class. * - * This function sends the finalized output data to the browser along - * with any server headers and profile data. It also stops the - * benchmark timer so the page rendering speed and memory usage can be shown. - * - * @param string - * @return mixed + * @uses CI_Output::$final_output + * @param string $output Output data override + * @return void */ public function _display($output = '') { @@ -353,7 +395,7 @@ public function _display($output = '') global $BM, $CFG; // Grab the super object if we can. - if (class_exists('CI_Controller')) + if (class_exists('CI_Controller', FALSE)) { $CI =& get_instance(); } @@ -374,10 +416,9 @@ public function _display($output = '') $output = $this->minify($output, $this->mime_type); } - // -------------------------------------------------------------------- - // Do we need to write a cache file? Only if the controller does not have its + // Do we need to write a cache file? Only if the controller does not have its // own _output() method and we are not dealing with a cache file, which we // can determine by the existence of the $CI object above if ($this->cache_expiration > 0 && isset($CI) && ! method_exists($CI, '_output')) @@ -430,7 +471,7 @@ public function _display($output = '') echo $output; log_message('debug', 'Final output sent to browser'); log_message('debug', 'Total execution time: '.$elapsed); - return TRUE; + return; } // -------------------------------------------------------------------- @@ -472,9 +513,9 @@ public function _display($output = '') // -------------------------------------------------------------------- /** - * Write a Cache File + * Write Cache * - * @param string + * @param string $output Output data to cache * @return void */ public function _write_cache($output) @@ -503,9 +544,15 @@ public function _write_cache($output) $expire = time() + ($this->cache_expiration * 60); + // Put together our serialized info. + $cache_info = serialize(array( + 'expire' => $expire, + 'headers' => $this->headers + )); + if (flock($fp, LOCK_EX)) { - fwrite($fp, $expire.'TS--->'.$output); + fwrite($fp, $cache_info.'ENDCI--->'.$output); flock($fp, LOCK_UN); } else @@ -525,11 +572,14 @@ public function _write_cache($output) // -------------------------------------------------------------------- /** - * Update/serve a cached file + * Update/serve cached output * - * @param object config class - * @param object uri class - * @return bool + * @uses CI_Config + * @uses CI_URI + * + * @param object &$CFG CI_Config class instance + * @param object &$URI CI_URI class instance + * @return bool TRUE on success or FALSE on failure */ public function _display_cache(&$CFG, &$URI) { @@ -551,14 +601,16 @@ public function _display_cache(&$CFG, &$URI) flock($fp, LOCK_UN); fclose($fp); - // Strip out the embedded timestamp - if ( ! preg_match('/\d+TS--->/', $cache, $match)) + // Look for embedded serialized file info. + if ( ! preg_match('/^(.*)ENDCI--->/', $cache, $match)) { return FALSE; } + $cache_info = unserialize($match[1]); + $expire = $cache_info['expire']; + $last_modified = filemtime($cache_path); - $expire = str_replace('TS--->', '', $match[0]); // Has the file expired? if ($_SERVER['REQUEST_TIME'] >= $expire && is_really_writable($cache_path)) @@ -574,6 +626,12 @@ public function _display_cache(&$CFG, &$URI) $this->set_cache_header($last_modified, $expire); } + // Add headers from cache file. + foreach ($cache_info['headers'] as $header) + { + $this->set_header($header[0], $header[1]); + } + // Display the cache $this->_display(substr($cache, strlen($match[0]))); log_message('debug', 'Cache file is current. Sending it to browser.'); @@ -583,11 +641,52 @@ public function _display_cache(&$CFG, &$URI) // -------------------------------------------------------------------- /** + * Delete cache + * + * @param string $uri URI string + * @return bool + */ + public function delete_cache($uri = '') + { + $CI =& get_instance(); + $cache_path = $CI->config->item('cache_path'); + if ($cache_path === '') + { + $cache_path = APPPATH.'cache/'; + } + + if ( ! is_dir($cache_path)) + { + log_message('error', 'Unable to find cache path: '.$cache_path); + return FALSE; + } + + if (empty($uri)) + { + $uri = $CI->uri->uri_string(); + } + + $cache_path .= md5($CI->config->item('base_url').$CI->config->item('index_page').$uri); + + if ( ! @unlink($cache_path)) + { + log_message('error', 'Unable to delete cache file for '.$uri); + return FALSE; + } + + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Set Cache Header + * * Set the HTTP headers to match the server-side file cache settings * in order to reduce bandwidth. * - * @param int timestamp of when the page was last modified - * @param int timestamp of when should the requested page expire from cache + * @param int $last_modified Timestamp of when the page was last modified + * @param int $expiration Timestamp of when should the requested page expire from cache * @return void */ public function set_cache_header($last_modified, $expiration) @@ -611,11 +710,13 @@ public function set_cache_header($last_modified, $expiration) // -------------------------------------------------------------------- /** - * Reduce excessive size of HTML content. + * Minify * - * @param string - * @param string - * @return string + * Reduce excessive size of HTML/CSS/JavaScript content. + * + * @param string $output Output to minify + * @param string $type Output content MIME type + * @return string Minified output */ public function minify($output, $type = 'text/html') { @@ -623,9 +724,7 @@ public function minify($output, $type = 'text/html') { case 'text/html': - $size_before = strlen($output); - - if ($size_before === 0) + if (($size_before = strlen($output)) === 0) { return ''; } @@ -641,23 +740,23 @@ public function minify($output, $type = 'text/html') preg_match_all('{}msU', $output, $style_clean); foreach ($style_clean[0] as $s) { - $output = str_replace($s, $this->minify($s, 'text/css'), $output); + $output = str_replace($s, $this->_minify_script_style($s, TRUE), $output); } // Minify the javascript in your site"; + + $this->assertEquals("Hello, i try to [removed]alert('Hack');[removed] your site", get_cookie('bar', TRUE)); + $this->assertEquals("Hello, i try to your site", get_cookie('bar', FALSE)); + } + + // ------------------------------------------------------------------------ + + function test_delete_cookie() + { + /*$input_cls = $this->ci_core_class('input'); + $this->ci_instance_var('input', new $input_cls); + + $this->assertTrue(delete_cookie( + 'my_cookie' + ));*/ + + $this->markTestSkipped('Need to find a way to overcome a headers already set exception'); + } + +} \ No newline at end of file diff --git a/tests/codeigniter/helpers/date_helper_test.php b/tests/codeigniter/helpers/date_helper_test.php index 1b79b94809c..0f16e6c4952 100644 --- a/tests/codeigniter/helpers/date_helper_test.php +++ b/tests/codeigniter/helpers/date_helper_test.php @@ -5,7 +5,6 @@ class Date_helper_test extends CI_TestCase { public function set_up() { $this->helper('date'); - $this->time = time(); } @@ -168,6 +167,8 @@ public function test_standard_date_w3c() public function test_timespan() { + $this->ci_vfs_clone('system/language/english/date_lang.php'); + $loader_cls = $this->ci_core_class('load'); $this->ci_instance_var('load', new $loader_cls); @@ -290,6 +291,29 @@ public function test_timezones() $this->assertEquals(0, timezones('non_existant')); } + // ------------------------------------------------------------------------ + + public function test_date_range() + { + $dates = array( + '29-01-2012', '30-01-2012', '31-01-2012', + '01-02-2012', '02-02-2012', '03-02-2012', + '04-02-2012', '05-02-2012', '06-02-2012', + '07-02-2012', '08-02-2012', '09-02-2012', + '10-02-2012', '11-02-2012', '12-02-2012', + '13-02-2012', '14-02-2012', '15-02-2012', + '16-02-2012', '17-02-2012', '18-02-2012', + '19-02-2012', '20-02-2012', '21-02-2012', + '22-02-2012', '23-02-2012', '24-02-2012', + '25-02-2012', '26-02-2012', '27-02-2012', + '28-02-2012', '29-02-2012', '01-03-2012' + ); + + $this->assertEquals($dates, date_range(mktime(12, 0, 0, 1, 29, 2012), mktime(12, 0, 0, 3, 1, 2012), TRUE, 'd-m-Y')); + array_pop($dates); + $this->assertEquals($dates, date_range(mktime(12, 0, 0, 1, 29, 2012), 31, FALSE, 'd-m-Y')); + } + } /* End of file date_helper_test.php */ \ No newline at end of file diff --git a/tests/codeigniter/helpers/directory_helper_test.php b/tests/codeigniter/helpers/directory_helper_test.php index 176ff1d789a..de72dee78a9 100644 --- a/tests/codeigniter/helpers/directory_helper_test.php +++ b/tests/codeigniter/helpers/directory_helper_test.php @@ -14,35 +14,46 @@ public function set_up() public function test_directory_map() { + $ds = DIRECTORY_SEPARATOR; + $structure = array( 'libraries' => array( 'benchmark.html' => '', 'database' => array('active_record.html' => '', 'binds.html' => ''), 'email.html' => '', + '0' => '', '.hiddenfile.txt' => '' ) ); vfsStream::create($structure, $this->_test_dir); + // is_dir(), opendir(), etc. seem to fail on Windows + vfsStream when there are trailing backslashes in directory names + if ( ! is_dir(vfsStream::url('http://github.com/testDir').DIRECTORY_SEPARATOR)) + { + $this->markTestSkipped(); + return; + } + // test default recursive behavior $expected = array( - 'libraries' => array( + 'libraries'.$ds => array( 'benchmark.html', - 'database' => array('active_record.html', 'binds.html'), - 'email.html' + 'database'.$ds => array('active_record.html', 'binds.html'), + 'email.html', + '0' ) ); $this->assertEquals($expected, directory_map(vfsStream::url('http://github.com/testDir'))); // test detection of hidden files - $expected['libraries'][] = '.hiddenfile.txt'; + $expected['libraries'.$ds][] = '.hiddenfile.txt'; - $this->assertEquals($expected, directory_map(vfsStream::url('http://github.com/testDir'), FALSE, TRUE)); + $this->assertEquals($expected, directory_map(vfsStream::url('http://github.com/testDir'), 0, TRUE)); // test recursion depth behavior - $this->assertEquals(array('libraries'), directory_map(vfsStream::url('http://github.com/testDir'), 1)); + $this->assertEquals(array('libraries'.$ds), directory_map(vfsStream::url('http://github.com/testDir'), 1)); } } diff --git a/tests/codeigniter/helpers/download_helper_test.php b/tests/codeigniter/helpers/download_helper_test.php new file mode 100644 index 00000000000..d2b42e46bb9 --- /dev/null +++ b/tests/codeigniter/helpers/download_helper_test.php @@ -0,0 +1,10 @@ +markTestSkipped('Cant easily test'); + } + +} \ No newline at end of file diff --git a/tests/codeigniter/helpers/file_helper_test.php b/tests/codeigniter/helpers/file_helper_test.php index 9b03da9d7cc..3a6c73a5c40 100644 --- a/tests/codeigniter/helpers/file_helper_test.php +++ b/tests/codeigniter/helpers/file_helper_test.php @@ -148,6 +148,4 @@ private function _test_get_file_info($vals) // // } - // -------------------------------------------------------------------- - } \ No newline at end of file diff --git a/tests/codeigniter/helpers/form_helper_test.php b/tests/codeigniter/helpers/form_helper_test.php index 1a30ed993fa..e234f9c8340 100644 --- a/tests/codeigniter/helpers/form_helper_test.php +++ b/tests/codeigniter/helpers/form_helper_test.php @@ -1,10 +1,14 @@ helper('form'); + } + + // ------------------------------------------------------------------------ + public function test_form_hidden() { $expected = <<assertEquals($expected, form_hidden('username', 'johndoe')); } + // ------------------------------------------------------------------------ + public function test_form_input() { $expected = <<assertEquals($expected, form_input($data)); } + // ------------------------------------------------------------------------ + public function test_form_password() { $expected = <<assertEquals($expected, form_password('password')); } + // ------------------------------------------------------------------------ + public function test_form_upload() { $expected = << + EOH; $this->assertEquals($expected, form_upload('attachment')); } + // ------------------------------------------------------------------------ + public function test_form_textarea() { $expected = <<assertEquals($expected, form_textarea('notes', 'Notes')); } + // ------------------------------------------------------------------------ + public function test_form_dropdown() { $expected = <<assertEquals($expected, form_dropdown('cars', $options, array('volvo', 'audi'))); } + // ------------------------------------------------------------------------ + public function test_form_multiselect() { $expected = <<assertEquals($expected, form_multiselect('shirts[]', $options, array('med', 'large'))); } + // ------------------------------------------------------------------------ + public function test_form_fieldset() { $expected = <<assertEquals($expected, form_fieldset('Address Information')); } + // ------------------------------------------------------------------------ + public function test_form_fieldset_close() { $expected = <<assertEquals($expected, form_fieldset_close('')); } + // ------------------------------------------------------------------------ + public function test_form_checkbox() { $expected = <<assertEquals($expected, form_checkbox('newsletter', 'accept', TRUE)); } + // ------------------------------------------------------------------------ + public function test_form_radio() { $expected = <<assertEquals($expected, form_radio('newsletter', 'accept', TRUE)); } + // ------------------------------------------------------------------------ + public function test_form_submit() { $expected = <<assertEquals($expected, form_submit('mysubmit', 'Submit Post!')); } + // ------------------------------------------------------------------------ + public function test_form_label() { $expected = <<assertEquals($expected, form_label('What is your Name', 'username')); } + // ------------------------------------------------------------------------ + public function test_form_reset() { $expected = <<assertEquals($expected, form_reset('myreset', 'Reset')); } + // ------------------------------------------------------------------------ + public function test_form_button() { $expected = <<assertEquals($expected, form_button('name', 'content')); } + // ------------------------------------------------------------------------ + public function test_form_close() { $expected = <<assertEquals($expected, form_close('')); } + // ------------------------------------------------------------------------ + public function test_form_prep() { - $expected = 'Here is a string containing "quoted" text.'; + $this->assertEquals( + 'Here is a string containing "quoted" text.', + form_prep('Here is a string containing "quoted" text.') + ); - $this->assertEquals($expected, form_prep('Here is a string containing "quoted" text.')); + $this->assertEquals( + 'Here is a string containing a <tag>.', + form_prep('Here is a string containing a .', TRUE) + ); } } diff --git a/tests/codeigniter/helpers/html_helper_test.php b/tests/codeigniter/helpers/html_helper_test.php index 4dd717ff7d7..d66ad895cb7 100644 --- a/tests/codeigniter/helpers/html_helper_test.php +++ b/tests/codeigniter/helpers/html_helper_test.php @@ -88,4 +88,5 @@ public function test_meta() $this->assertEquals($expect, meta(array('name' => 'foo'))); } + } \ No newline at end of file diff --git a/tests/codeigniter/helpers/language_helper_test.php b/tests/codeigniter/helpers/language_helper_test.php new file mode 100644 index 00000000000..176da689a04 --- /dev/null +++ b/tests/codeigniter/helpers/language_helper_test.php @@ -0,0 +1,16 @@ +helper('language'); + $lang = $this->getMock('CI_Lang', array('line')); + $lang->expects($this->any())->method('line')->will($this->returnValue(FALSE)); + $this->ci_instance_var('lang', $lang); + + $this->assertFalse(lang(1)); + $this->assertEquals('', lang(1, 'foo', array('class' => 'bar'))); + } + +} \ No newline at end of file diff --git a/tests/codeigniter/helpers/number_helper_test.php b/tests/codeigniter/helpers/number_helper_test.php index ef6aae138e2..817db2c7e91 100644 --- a/tests/codeigniter/helpers/number_helper_test.php +++ b/tests/codeigniter/helpers/number_helper_test.php @@ -11,31 +11,18 @@ public function set_up() // Mock away load, too much going on in there, // we'll just check for the expected parameter - $lang = $this->getMock($lang_cls, array('load')); $lang->expects($this->once()) ->method('load') ->with($this->equalTo('number')); // Assign the proper language array - - $lang->language = $this->_get_lang('number'); + $lang->language = $this->lang('number'); // We don't have a controller, so just create // a cheap class to act as our super object. // Make sure it has a lang attribute. - - $obj = new stdClass; - $obj->lang = $lang; - $this->ci_instance($obj); - } - - // Quick helper to actually grab the language - // file. Consider moving this to ci_testcase? - public function _get_lang($name) - { - require BASEPATH.'language/english/'.$name.'_lang.php'; - return $lang; + $this->ci_instance_var('lang', $lang); } public function test_byte_format() diff --git a/tests/codeigniter/helpers/path_helper_test.php b/tests/codeigniter/helpers/path_helper_test.php index 0faf6f3835b..d25c3ed9b05 100644 --- a/tests/codeigniter/helpers/path_helper_test.php +++ b/tests/codeigniter/helpers/path_helper_test.php @@ -26,6 +26,7 @@ public function test_set_realpath_error_trigger() set_realpath('/path/to/nowhere', TRUE); } + } /* End of file path_helper_test.php */ \ No newline at end of file diff --git a/tests/codeigniter/helpers/security_helper_test.php b/tests/codeigniter/helpers/security_helper_test.php new file mode 100644 index 00000000000..effd3ec0289 --- /dev/null +++ b/tests/codeigniter/helpers/security_helper_test.php @@ -0,0 +1,64 @@ +helper('security'); + $obj = new stdClass; + $obj->security = new Mock_Core_Security(); + $this->ci_instance($obj); + } + + function test_xss_clean() + { + $this->assertEquals('foo', xss_clean('foo')); + + $this->assertEquals("Hello, i try to [removed]alert('Hack');[removed] your site", xss_clean("Hello, i try to your site")); + } + + function test_sanitize_filename() + { + $this->assertEquals('hello.doc', sanitize_filename('hello.doc')); + + $filename = './'; + $this->assertEquals('foo', sanitize_filename($filename)); + } + + function test_do_hash() + { + $md5 = md5('foo'); + $sha1 = sha1('foo'); + + $algos = hash_algos(); + $algo_results = array(); + foreach ($algos as $k => $v) + { + $algo_results[$v] = hash($v, 'foo'); + } + + $this->assertEquals($sha1, do_hash('foo')); + $this->assertEquals($sha1, do_hash('foo', 'sha1')); + $this->assertEquals($md5, do_hash('foo', 'md5')); + $this->assertEquals($md5, do_hash('foo', 'foobar')); + + // Test each algorithm available to PHP + foreach ($algo_results as $algo => $result) + { + $this->assertEquals($result, do_hash('foo', $algo)); + } + } + + function test_strip_image_tags() + { + $this->assertEquals('http://example.com/spacer.gif', strip_image_tags('http://example.com/spacer.gif')); + + $this->assertEquals('http://example.com/spacer.gif', strip_image_tags('Who needs CSS when you have a spacer.gif?')); + } + + function test_encode_php_tags() + { + $this->assertEquals('<? echo $foo; ?>', encode_php_tags('')); + } + +} \ No newline at end of file diff --git a/tests/codeigniter/helpers/text_helper_test.php b/tests/codeigniter/helpers/text_helper_test.php index f131469cbc7..d75d2620890 100644 --- a/tests/codeigniter/helpers/text_helper_test.php +++ b/tests/codeigniter/helpers/text_helper_test.php @@ -64,6 +64,7 @@ public function test_entities_to_ascii() public function test_convert_accented_characters() { + $this->ci_vfs_clone('application/config/foreign_chars.php'); $this->assertEquals('AAAeEEEIIOOEUUUeY', convert_accented_characters('ÀÂÄÈÊËÎÏÔŒÙÛÜŸ')); $this->assertEquals('a e i o u n ue', convert_accented_characters('á é í ó ú ñ ü')); } diff --git a/tests/codeigniter/helpers/url_helper_test.php b/tests/codeigniter/helpers/url_helper_test.php index c81c5f1b8a9..24823a634e7 100644 --- a/tests/codeigniter/helpers/url_helper_test.php +++ b/tests/codeigniter/helpers/url_helper_test.php @@ -48,9 +48,12 @@ public function test_prep_url() public function test_auto_link_url() { $strings = array( - 'www.codeigniter.com test' => 'http://www.codeigniter.com test', + 'www.codeigniter.com test' => 'www.codeigniter.com test', 'This is my noreply@codeigniter.com test' => 'This is my noreply@codeigniter.com test', - '
www.google.com' => '
http://www.google.com', + '
www.google.com' => '
www.google.com', + 'Download CodeIgniter at www.codeigniter.com. Period test.' => 'Download CodeIgniter at www.codeigniter.com. Period test.', + 'Download CodeIgniter at www.codeigniter.com, comma test' => 'Download CodeIgniter at www.codeigniter.com, comma test', + 'This one: ://codeigniter.com must not break this one: http://codeigniter.com' => 'This one: ://codeigniter.com must not break this one: http://codeigniter.com' ); foreach ($strings as $in => $out) @@ -64,7 +67,7 @@ public function test_auto_link_url() public function test_pull_675() { $strings = array( - '
www.google.com' => '
http://www.google.com', + '
www.google.com' => '
www.google.com', ); foreach ($strings as $in => $out) diff --git a/tests/codeigniter/libraries/Calendar_test.php b/tests/codeigniter/libraries/Calendar_test.php new file mode 100644 index 00000000000..952e8a8d2ce --- /dev/null +++ b/tests/codeigniter/libraries/Calendar_test.php @@ -0,0 +1,201 @@ +getMock('CI_Lang', array('load', 'line')); + $lang->expects($this->any())->method('line')->will($this->returnValue(FALSE)); + $this->ci_instance_var('lang', $lang); + $this->calendar = new CI_Calendar(); + } + + function test_initialize() + { + $this->calendar->initialize(array( + 'month_type' => 'short', + 'start_day' => 'monday' + )); + $this->assertEquals('short', $this->calendar->month_type); + $this->assertEquals('monday', $this->calendar->start_day); + } + + function test_generate() + { + $no_events = ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
September 2011
SuMoTuWeThFrSa
    123
45678910
11121314151617
18192021222324
252627282930 
'; + + $this->assertEquals($no_events, $this->calendar->generate(2011, 9)); + + $data = array( + 3 => 'http://example.com/news/article/2006/03/', + 7 => 'http://example.com/news/article/2006/07/', + 13 => 'http://example.com/news/article/2006/13/', + 26 => 'http://example.com/news/article/2006/26/' + ); + + $events = ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
September 2011
SuMoTuWeThFrSa
    123
45678910
11121314151617
18192021222324
252627282930 
'; + + $this->assertEquals($events, $this->calendar->generate(2011, 9, $data)); + } + + function test_get_month_name() + { + $this->calendar->month_type = NULL; + $this->assertEquals('January', $this->calendar->get_month_name('01')); + + $this->calendar->month_type = 'short'; + $this->assertEquals('Jan', $this->calendar->get_month_name('01')); + } + + function test_get_day_names() + { + $this->assertEquals(array( + 'Sunday', + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday' + ), $this->calendar->get_day_names('long')); + + $this->assertEquals(array( + 'Sun', + 'Mon', + 'Tue', + 'Wed', + 'Thu', + 'Fri', + 'Sat' + ), $this->calendar->get_day_names('short')); + + $this->calendar->day_type = NULL; + + $this->assertEquals(array( + 'Su', + 'Mo', + 'Tu', + 'We', + 'Th', + 'Fr', + 'Sa' + ), $this->calendar->get_day_names()); + } + + function test_adjust_date() + { + $this->assertEquals(array('month' => 8, 'year' => 2012), $this->calendar->adjust_date(8, 2012)); + $this->assertEquals(array('month' => 1, 'year' => 2013), $this->calendar->adjust_date(13, 2012)); + } + + function test_get_total_days() + { + $this->assertEquals(0, $this->calendar->get_total_days(13, 2012)); + + $this->assertEquals(31, $this->calendar->get_total_days(1, 2012)); + $this->assertEquals(28, $this->calendar->get_total_days(2, 2011)); + $this->assertEquals(29, $this->calendar->get_total_days(2, 2012)); + $this->assertEquals(31, $this->calendar->get_total_days(3, 2012)); + $this->assertEquals(30, $this->calendar->get_total_days(4, 2012)); + $this->assertEquals(31, $this->calendar->get_total_days(5, 2012)); + $this->assertEquals(30, $this->calendar->get_total_days(6, 2012)); + $this->assertEquals(31, $this->calendar->get_total_days(7, 2012)); + $this->assertEquals(31, $this->calendar->get_total_days(8, 2012)); + $this->assertEquals(30, $this->calendar->get_total_days(9, 2012)); + $this->assertEquals(31, $this->calendar->get_total_days(10, 2012)); + $this->assertEquals(30, $this->calendar->get_total_days(11, 2012)); + $this->assertEquals(31, $this->calendar->get_total_days(12, 2012)); + } + + function test_default_template() + { + $array = array( + 'table_open' => '', + 'heading_row_start' => '', + 'heading_previous_cell' => '', + 'heading_title_cell' => '', + 'heading_next_cell' => '', + 'heading_row_end' => '', + 'week_row_start' => '', + 'week_day_cell' => '', + 'week_row_end' => '', + 'cal_row_start' => '', + 'cal_cell_start' => '', + 'cal_cell_end_today' => '', + 'cal_row_end' => '', + 'table_close' => '
<<{heading}>>
{week_day}
', + 'cal_cell_start_today' => '', + 'cal_cell_content' => '{day}', + 'cal_cell_content_today' => '{day}', + 'cal_cell_no_content' => '{day}', + 'cal_cell_no_content_today' => '{day}', + 'cal_cell_blank' => ' ', + 'cal_cell_end' => '
' + ); + + $this->assertEquals($array, $this->calendar->default_template()); + } + +} \ No newline at end of file diff --git a/tests/codeigniter/libraries/Driver_test.php b/tests/codeigniter/libraries/Driver_test.php new file mode 100644 index 00000000000..d98e8ab9852 --- /dev/null +++ b/tests/codeigniter/libraries/Driver_test.php @@ -0,0 +1,177 @@ +subclass = 'Mock_Libraries_'; + $this->ci_set_config('subclass_prefix', $this->subclass); + + // Mock Loader->get_package_paths + $paths = 'get_package_paths'; + $ldr = $this->getMock('CI_Loader', array($paths)); + $ldr->expects($this->any())->method($paths)->will($this->returnValue(array(APPPATH, BASEPATH))); + $this->ci_instance_var('load', $ldr); + + // Create mock driver library + $this->name = 'Driver'; + $this->lib = new Mock_Libraries_Driver(); + } + + /** + * Test driver child loading + */ + public function test_load_driver() + { + // Create driver file + $driver = 'basic'; + $file = $this->name.'_'.$driver; + $class = 'CI_'.$file; + $prop = 'called'; + $content = ''.$prop.' = TRUE; } }'; + $this->ci_vfs_create($file, $content, $this->ci_base_root, array('libraries', $this->name, 'drivers')); + + // Make driver valid + $this->lib->driver_list($driver); + + // Load driver + $this->assertNotNull($this->lib->load_driver($driver)); + + // Did lib name get set? + $this->assertEquals($this->name, $this->lib->get_name()); + + // Was driver loaded? + $this->assertObjectHasAttribute($driver, $this->lib); + $this->assertAttributeInstanceOf($class, $driver, $this->lib); + $this->assertAttributeInstanceOf('CI_Driver', $driver, $this->lib); + + // Was decorate called? + $this->assertObjectHasAttribute($prop, $this->lib->$driver); + $this->assertTrue($this->lib->$driver->$prop); + + // Do we get an error for an invalid driver? + $driver = 'unlisted'; + $this->setExpectedException('RuntimeException', 'CI Error: Invalid driver requested: '.$this->name.'_'.$driver); + $this->lib->load_driver($driver); + } + + /** + * Test loading lowercase from app path + */ + public function test_load_app_driver() + { + // Create driver file + $driver = 'lowpack'; + $file = $this->name.'_'.$driver; + $class = 'CI_'.$file; + $content = 'ci_vfs_create($file, $content, $this->ci_app_root, + array('libraries', $this->name, 'drivers')); + + // Make valid list + $nodriver = 'absent'; + $this->lib->driver_list(array($driver, $nodriver)); + + // Load driver + $this->assertNotNull($this->lib->load_driver($driver)); + + // Was driver loaded? + $this->assertObjectHasAttribute($driver, $this->lib); + $this->assertAttributeInstanceOf($class, $driver, $this->lib); + $this->assertAttributeInstanceOf('CI_Driver', $driver, $this->lib); + + // Do we get an error for a non-existent driver? + $this->setExpectedException('RuntimeException', 'CI Error: Unable to load the requested driver: CI_'. + $this->name.'_'.$nodriver); + $this->lib->load_driver($nodriver); + } + + /** + * Test loading driver extension + */ + public function test_load_driver_ext() + { + // Create base file + $driver = 'extend'; + $base = $this->name.'_'.$driver; + $baseclass = 'CI_'.$base; + $content = 'ci_vfs_create($base, $content, $this->ci_base_root, array('libraries', $this->name, 'drivers')); + + // Create driver file + $class = $this->subclass.$base; + $content = 'ci_vfs_create($class, $content, $this->ci_app_root, array('libraries', $this->name, 'drivers')); + + // Make valid list + $this->lib->driver_list($driver); + + // Load driver + $this->assertNotNull($this->lib->load_driver($driver)); + + // Was driver loaded? + $this->assertObjectHasAttribute($driver, $this->lib); + $this->assertAttributeInstanceOf($class, $driver, $this->lib); + $this->assertAttributeInstanceOf($baseclass, $driver, $this->lib); + $this->assertAttributeInstanceOf('CI_Driver', $driver, $this->lib); + + // Create driver extension without base + $driver = 'baseless'; + $base = $this->name.'_'.$driver; + $class = $this->subclass.$base; + $content = 'ci_vfs_create($class, $content, $this->ci_app_root, array('libraries', $this->name, 'drivers')); + $this->lib->driver_list($driver); + + // Do we get an error when base class isn't found? + $this->setExpectedException('RuntimeException', 'CI Error: Unable to load the requested class: CI_'.$base); + $this->lib->load_driver($driver); + } + + /** + * Test decorating driver with parent attributes + */ + public function test_decorate() + { + // Create parent with a method and property to access + $pclass = 'Test_Parent'; + $prop = 'parent_prop'; + $value = 'initial'; + $method = 'parent_func'; + $return = 'func return'; + $code = 'class '.$pclass.' { public $'.$prop.' = \''.$value.'\'; '. + 'public function '.$method.'() { return \''.$return.'\'; } }'; + eval($code); + $parent = new $pclass(); + + // Create child driver to decorate + $class = 'Test_Driver'; + eval('class '.$class.' extends CI_Driver { }'); + $child = new $class(); + + // Decorate child + $child->decorate($parent); + + // Do we get the initial parent property value? + $this->assertEquals($value, $child->$prop); + + // Can we change the parent property? + $newval = 'changed'; + $child->$prop = $newval; + $this->assertEquals($newval, $parent->$prop); + + // Do we get back the updated value? + $this->assertEquals($newval, $child->$prop); + + // Can we call the parent method? + $this->assertEquals($return, $child->$method()); + } + +} \ No newline at end of file diff --git a/tests/codeigniter/libraries/Encrypt_test.php b/tests/codeigniter/libraries/Encrypt_test.php index 153a25e1d67..a08db8ed073 100644 --- a/tests/codeigniter/libraries/Encrypt_test.php +++ b/tests/codeigniter/libraries/Encrypt_test.php @@ -4,14 +4,12 @@ class Encrypt_test extends CI_TestCase { public function set_up() { - $obj = new stdClass; - $obj->encrypt = new Mock_Libraries_Encrypt(); - - $this->ci_instance($obj); - $this->encrypt = $obj->encrypt; + $this->encrypt = new Mock_Libraries_Encrypt(); + $this->ci_instance_var('encrypt', $this->encrypt); $this->ci_set_config('encryption_key', "Encryptin'glike@boss!"); $this->msg = 'My secret message'; + $this->mcrypt = extension_loaded('mcrypt'); } // -------------------------------------------------------------------- @@ -42,14 +40,25 @@ public function test_optional_key() public function test_default_cipher() { + if ( ! $this->mcrypt) + { + $this->markTestSkipped('MCrypt not available'); + return; + } + $this->assertEquals('rijndael-256', $this->encrypt->get_cipher()); } // -------------------------------------------------------------------- - public function test_set_cipher() { + if ( ! $this->mcrypt) + { + $this->markTestSkipped('MCrypt not available'); + return; + } + $this->encrypt->set_cipher(MCRYPT_BLOWFISH); $this->assertEquals('blowfish', $this->encrypt->get_cipher()); } @@ -58,6 +67,12 @@ public function test_set_cipher() public function test_default_mode() { + if ( ! $this->mcrypt) + { + $this->markTestSkipped('MCrypt not available'); + return; + } + $this->assertEquals('cbc', $this->encrypt->get_mode()); } @@ -65,6 +80,12 @@ public function test_default_mode() public function test_set_mode() { + if ( ! $this->mcrypt) + { + $this->markTestSkipped('MCrypt not available'); + return; + } + $this->encrypt->set_mode(MCRYPT_MODE_CFB); $this->assertEquals('cfb', $this->encrypt->get_mode()); } diff --git a/tests/codeigniter/libraries/Parser_test.php b/tests/codeigniter/libraries/Parser_test.php index b68f44a3303..394c22692f5 100644 --- a/tests/codeigniter/libraries/Parser_test.php +++ b/tests/codeigniter/libraries/Parser_test.php @@ -4,12 +4,8 @@ class Parser_test extends CI_TestCase { public function set_up() { - $obj = new stdClass; - $obj->parser = new Mock_Libraries_Parser(); - - $this->ci_instance($obj); - - $this->parser = $obj->parser; + $this->parser = new Mock_Libraries_Parser(); + $this->ci_instance_var('parser', $this->parser); } // -------------------------------------------------------------------- diff --git a/tests/codeigniter/libraries/Session_test.php b/tests/codeigniter/libraries/Session_test.php new file mode 100644 index 00000000000..6edda99d704 --- /dev/null +++ b/tests/codeigniter/libraries/Session_test.php @@ -0,0 +1,426 @@ + 0, + 'use_only_cookies' => 0, + 'cache_limiter' => false + ); + protected $setting_vals = array(); + protected $cookie_vals; + protected $session; + + /** + * Set up test framework + */ + public function set_up() + { + // Override settings + foreach ($this->settings as $name => $value) { + $this->setting_vals[$name] = ini_get('session.'.$name); + ini_set('session.'.$name, $value); + } + + // Start with clean environment + $this->cookie_vals = $_COOKIE; + $_COOKIE = array(); + + // Set subclass prefix to match our mock + $this->ci_set_config('subclass_prefix', 'Mock_Libraries_'); + + // Establish necessary support classes + $ci = $this->ci_instance(); + $ldr = $this->ci_core_class('load'); + $ci->load = new $ldr(); + $ci->input = new Mock_Core_Input(NULL, NULL); + + // Make sure string helper is available + $this->ci_vfs_clone('system/helpers/string_helper.php'); + + // Attach session instance locally + $config = array( + 'sess_encrypt_cookie' => FALSE, + 'sess_use_database' => FALSE, + 'sess_table_name' => '', + 'sess_expiration' => 7200, + 'sess_expire_on_close' => FALSE, + 'sess_match_ip' => FALSE, + 'sess_match_useragent' => TRUE, + 'sess_cookie_name' => 'ci_session', + 'cookie_path' => '', + 'cookie_domain' => '', + 'cookie_secure' => FALSE, + 'cookie_httponly' => FALSE, + 'sess_time_to_update' => 300, + 'time_reference' => 'local', + 'cookie_prefix' => '', + 'encryption_key' => 'foobar' + ); + $this->session = new Mock_Libraries_Session($config); + } + + /** + * Tear down test framework + */ + public function tear_down() + { + // Restore environment + if (session_id()) session_destroy(); + $_SESSION = array(); + $_COOKIE = $this->cookie_vals; + + // Restore settings + foreach ($this->settings as $name => $value) { + ini_set('session.'.$name, $this->setting_vals[$name]); + } + } + + /** + * Test set_userdata() function + */ + public function test_set_userdata() + { + // Set userdata values for each driver + $key1 = 'test1'; + $ckey2 = 'test2'; + $nkey2 = 'test3'; + $cmsg1 = 'Some test data'; + $cmsg2 = 42; + $nmsg1 = 'Other test data'; + $nmsg2 = true; + $this->session->cookie->set_userdata($key1, $cmsg1); + $this->session->set_userdata($ckey2, $cmsg2); + $this->session->native->set_userdata($key1, $nmsg1); + $this->session->set_userdata($nkey2, $nmsg2); + + // Verify independent messages + $this->assertEquals($cmsg1, $this->session->cookie->userdata($key1)); + $this->assertEquals($nmsg1, $this->session->native->userdata($key1)); + + // Verify pre-selected driver sets + $this->assertEquals($cmsg2, $this->session->cookie->userdata($ckey2)); + $this->assertEquals($nmsg2, $this->session->native->userdata($nkey2)); + + // Verify no crossover + $this->assertNull($this->session->cookie->userdata($nkey2)); + $this->assertNull($this->session->native->userdata($ckey2)); + } + + /** + * Test the has_userdata() function + */ + public function test_has_userdata() + { + // Set a userdata value for each driver + $key = 'hastest'; + $cmsg = 'My test data'; + $this->session->cookie->set_userdata($key, $cmsg); + $nmsg = 'Your test data'; + $this->session->native->set_userdata($key, $nmsg); + + // Verify values exist + $this->assertTrue($this->session->cookie->has_userdata($key)); + $this->assertTrue($this->session->native->has_userdata($key)); + + // Verify non-existent values + $nokey = 'hasnot'; + $this->assertFalse($this->session->cookie->has_userdata($nokey)); + $this->assertFalse($this->session->native->has_userdata($nokey)); + } + + /** + * Test the all_userdata() function + */ + public function test_all_userdata() + { + // Set a specific series of data for each driver + $cdata = array( + 'one' => 'first', + 'two' => 'second', + 'three' => 'third', + 'foo' => 'bar', + 'bar' => 'baz' + ); + $ndata = array( + 'one' => 'gold', + 'two' => 'silver', + 'three' => 'bronze', + 'foo' => 'baz', + 'bar' => 'foo' + ); + $this->session->cookie->set_userdata($cdata); + $this->session->native->set_userdata($ndata); + + // Make sure all values are present + $call = $this->session->cookie->all_userdata(); + foreach ($cdata as $key => $value) { + $this->assertEquals($value, $call[$key]); + } + $nall = $this->session->native->all_userdata(); + foreach ($ndata as $key => $value) { + $this->assertEquals($value, $nall[$key]); + } + } + + /** + * Test the unset_userdata() function + */ + public function test_unset_userdata() + { + // Set a userdata message for each driver + $key = 'untest'; + $cmsg = 'Other test data'; + $this->session->cookie->set_userdata($key, $cmsg); + $nmsg = 'Sundry test data'; + $this->session->native->set_userdata($key, $nmsg); + + // Verify independent messages + $this->assertEquals($this->session->cookie->userdata($key), $cmsg); + $this->assertEquals($this->session->native->userdata($key), $nmsg); + + // Unset them and verify absence + $this->session->cookie->unset_userdata($key); + $this->session->native->unset_userdata($key); + $this->assertNull($this->session->cookie->userdata($key)); + $this->assertNull($this->session->native->userdata($key)); + } + + /** + * Test the flashdata() functions + */ + public function test_flashdata() + { + // Set flashdata message for each driver + $key = 'fltest'; + $cmsg = 'Some flash data'; + $this->session->cookie->set_flashdata($key, $cmsg); + $nmsg = 'Other flash data'; + $this->session->native->set_flashdata($key, $nmsg); + + // Simulate page reload + $this->session->cookie->reload(); + $this->session->native->reload(); + + // Verify independent messages + $this->assertEquals($cmsg, $this->session->cookie->flashdata($key)); + $this->assertEquals($nmsg, $this->session->native->flashdata($key)); + + // Simulate next page reload + $this->session->cookie->reload(); + $this->session->native->reload(); + + // Verify absence of messages + $this->assertNull($this->session->cookie->flashdata($key)); + $this->assertNull($this->session->native->flashdata($key)); + } + + /** + * Test the keep_flashdata() function + */ + public function test_keep_flashdata() + { + // Set flashdata message for each driver + $key = 'kfltest'; + $cmsg = 'My flash data'; + $this->session->cookie->set_flashdata($key, $cmsg); + $nmsg = 'Your flash data'; + $this->session->native->set_flashdata($key, $nmsg); + + // Simulate page reload and verify independent messages + $this->session->cookie->reload(); + $this->session->native->reload(); + $this->assertEquals($cmsg, $this->session->cookie->flashdata($key)); + $this->assertEquals($nmsg, $this->session->native->flashdata($key)); + + // Keep messages + $this->session->cookie->keep_flashdata($key); + $this->session->native->keep_flashdata($key); + + // Simulate next page reload and verify message persistence + $this->session->cookie->reload(); + $this->session->native->reload(); + $this->assertEquals($cmsg, $this->session->cookie->flashdata($key)); + $this->assertEquals($nmsg, $this->session->native->flashdata($key)); + + // Simulate next page reload and verify absence of messages + $this->session->cookie->reload(); + $this->session->native->reload(); + $this->assertNull($this->session->cookie->flashdata($key)); + $this->assertNull($this->session->native->flashdata($key)); + } + + public function test_keep_flashdata_with_array() + { + // Set flashdata array for each driver + $cdata = array( + 'one' => 'first', + 'two' => 'second', + 'three' => 'third', + 'foo' => 'bar', + 'bar' => 'baz' + ); + $ndata = array( + 'one' => 'gold', + 'two' => 'silver', + 'three' => 'bronze', + 'foo' => 'baz', + 'bar' => 'foo' + ); + $kdata = array( + 'one', + 'two', + 'three', + 'foo', + 'bar' + ); + $this->session->cookie->set_flashdata($cdata); + $this->session->native->set_flashdata($ndata); + + // Simulate page reload and verify independent messages + $this->session->cookie->reload(); + $this->session->native->reload(); + $this->assertEquals($cdata, $this->session->cookie->all_flashdata()); + $this->assertEquals($ndata, $this->session->native->all_flashdata()); + + // Keep messages + $this->session->cookie->keep_flashdata($kdata); + $this->session->native->keep_flashdata($kdata); + + // Simulate next page reload and verify message persistence + $this->session->cookie->reload(); + $this->session->native->reload(); + $this->assertEquals($cdata, $this->session->cookie->all_flashdata()); + $this->assertEquals($ndata, $this->session->native->all_flashdata()); + + // Simulate next page reload and verify absence of messages + $this->session->cookie->reload(); + $this->session->native->reload(); + $this->assertEmpty($this->session->cookie->all_flashdata()); + $this->assertEmpty($this->session->native->all_flashdata()); + } + + /** + * Test the all_flashdata() function + */ + public function test_all_flashdata() + { + // Set a specific series of data for each driver + $cdata = array( + 'one' => 'first', + 'two' => 'second', + 'three' => 'third', + 'foo' => 'bar', + 'bar' => 'baz' + ); + $ndata = array( + 'one' => 'gold', + 'two' => 'silver', + 'three' => 'bronze', + 'foo' => 'baz', + 'bar' => 'foo' + ); + $this->session->cookie->set_flashdata($cdata); + $this->session->native->set_flashdata($ndata); + + // Simulate page reload and make sure all values are present + $this->session->cookie->reload(); + $this->session->native->reload(); + $this->assertEquals($cdata, $this->session->cookie->all_flashdata()); + $this->assertEquals($ndata, $this->session->native->all_flashdata()); + } + + /** + * Test the tempdata() functions + */ + public function test_set_tempdata() + { + // Set tempdata message for each driver - 1 second timeout + $key = 'tmptest'; + $cmsg = 'Some temp data'; + $this->session->cookie->set_tempdata($key, $cmsg, 1); + $nmsg = 'Other temp data'; + $this->session->native->set_tempdata($key, $nmsg, 1); + + // Simulate page reload and verify independent messages + $this->session->cookie->reload(); + $this->session->native->reload(); + $this->assertEquals($cmsg, $this->session->cookie->tempdata($key)); + $this->assertEquals($nmsg, $this->session->native->tempdata($key)); + + // Wait 2 seconds, simulate page reload and verify message absence + sleep(2); + $this->session->cookie->reload(); + $this->session->native->reload(); + $this->assertNull($this->session->cookie->tempdata($key)); + $this->assertNull($this->session->native->tempdata($key)); + } + + /** + * Test the unset_tempdata() function + */ + public function test_unset_tempdata() + { + // Set tempdata message for each driver - 1 second timeout + $key = 'utmptest'; + $cmsg = 'My temp data'; + $this->session->cookie->set_tempdata($key, $cmsg, 1); + $nmsg = 'Your temp data'; + $this->session->native->set_tempdata($key, $nmsg, 1); + + // Verify independent messages + $this->assertEquals($cmsg, $this->session->cookie->tempdata($key)); + $this->assertEquals($nmsg, $this->session->native->tempdata($key)); + + // Unset data and verify message absence + $this->session->cookie->unset_tempdata($key); + $this->session->native->unset_tempdata($key); + $this->assertNull($this->session->cookie->tempdata($key)); + $this->assertNull($this->session->native->tempdata($key)); + } + + /** + * Test the sess_regenerate() function + */ + public function test_sess_regenerate() + { + // Get current session id, regenerate, and compare + // Cookie driver + $oldid = $this->session->cookie->userdata('session_id'); + $this->session->cookie->sess_regenerate(); + $newid = $this->session->cookie->userdata('session_id'); + $this->assertNotEquals($oldid, $newid); + + // Native driver - bug #55267 (https://bugs.php.net/bug.php?id=55267) prevents testing this + // $oldid = session_id(); + // $this->session->native->sess_regenerate(); + // $oldid = session_id(); + // $this->assertNotEquals($oldid, $newid); + } + + /** + * Test the sess_destroy() function + */ + public function test_sess_destroy() + { + // Set a userdata message, destroy session, and verify absence + $key = 'dsttest'; + $msg = 'More test data'; + + // Cookie driver + $this->session->cookie->set_userdata($key, $msg); + $this->assertEquals($msg, $this->session->cookie->userdata($key)); + $this->session->cookie->sess_destroy(); + $this->assertNull($this->session->cookie->userdata($key)); + + // Native driver + $this->session->native->set_userdata($key, $msg); + $this->assertEquals($msg, $this->session->native->userdata($key)); + $this->session->native->sess_destroy(); + $this->assertNull($this->session->native->userdata($key)); + } + +} \ No newline at end of file diff --git a/tests/codeigniter/libraries/Table_test.php b/tests/codeigniter/libraries/Table_test.php index edfc83dd00d..ce04b6a6d24 100644 --- a/tests/codeigniter/libraries/Table_test.php +++ b/tests/codeigniter/libraries/Table_test.php @@ -4,12 +4,8 @@ class Table_test extends CI_TestCase { public function set_up() { - $obj = new stdClass; - $obj->table = new Mock_Libraries_Table(); - - $this->ci_instance($obj); - - $this->table = $obj->table; + $this->table = new Mock_Libraries_Table(); + $this->ci_instance_var('table', $this->table); } // Setter Methods diff --git a/tests/codeigniter/libraries/Typography_test.php b/tests/codeigniter/libraries/Typography_test.php index eb6dacb7352..5dba062438e 100644 --- a/tests/codeigniter/libraries/Typography_test.php +++ b/tests/codeigniter/libraries/Typography_test.php @@ -4,12 +4,8 @@ class Typography_test extends CI_TestCase { public function set_up() { - $obj = new stdClass; - $obj->type = new Mock_Libraries_Typography(); - - $this->ci_instance($obj); - - $this->type = $obj->type; + $this->type = new Mock_Libraries_Typography(); + $this->ci_instance('type', $this->type); } // -------------------------------------------------------------------- diff --git a/tests/codeigniter/libraries/Upload_test.php b/tests/codeigniter/libraries/Upload_test.php new file mode 100644 index 00000000000..4d9e4a46e4e --- /dev/null +++ b/tests/codeigniter/libraries/Upload_test.php @@ -0,0 +1,268 @@ +ci_instance(); + $ci->upload = new Mock_Libraries_Upload(); + $ci->security = new Mock_Core_Security(); + $ci->lang = $this->getMock('CI_Lang', array('load', 'line')); + $ci->lang->expects($this->any())->method('line')->will($this->returnValue(FALSE)); + $this->upload = $ci->upload; + } + + function test_do_upload() + { + $this->markTestSkipped('We can\'t really test this at the moment because of the call to `is_uploaded_file` in do_upload which isn\'t supported by vfsStream'); + } + + function test_data() + { + $data = array( + 'file_name' => 'hello.txt', + 'file_type' => 'text/plain', + 'file_path' => '/tmp/', + 'full_path' => '/tmp/hello.txt', + 'raw_name' => 'hello', + 'orig_name' => 'hello.txt', + 'client_name' => '', + 'file_ext' => '.txt', + 'file_size' => 100, + 'is_image' => FALSE, + 'image_width' => '', + 'image_height' => '', + 'image_type' => '', + 'image_size_str' => '' + ); + + $this->upload->set_upload_path('/tmp/'); + + foreach ($data as $k => $v) + { + $this->upload->{$k} = $v; + } + + $this->assertEquals('hello.txt', $this->upload->data('file_name')); + $this->assertEquals($data, $this->upload->data()); + } + + function test_set_upload_path() + { + $this->upload->set_upload_path('/tmp/'); + $this->assertEquals('/tmp/', $this->upload->upload_path); + + $this->upload->set_upload_path('/tmp'); + $this->assertEquals('/tmp/', $this->upload->upload_path); + } + + function test_set_filename() + { + $dir = 'uploads'; + $isnew = 'helloworld.txt'; + $exists = 'hello-world.txt'; + $this->ci_vfs_create($exists, 'Hello world.', $this->ci_app_root, $dir); + $path = $this->ci_vfs_path($dir.'/', APPPATH); + $this->upload->file_ext = '.txt'; + + $this->assertEquals($isnew, $this->upload->set_filename($path, $isnew)); + $this->assertEquals('hello-world1.txt', $this->upload->set_filename($path, $exists)); + } + + function test_set_max_filesize() + { + $this->upload->set_max_filesize(100); + $this->assertEquals(100, $this->upload->max_size); + } + + function test_set_max_filename() + { + $this->upload->set_max_filename(100); + $this->assertEquals(100, $this->upload->max_filename); + } + + function test_set_max_width() + { + $this->upload->set_max_width(100); + $this->assertEquals(100, $this->upload->max_width); + } + + function test_set_max_height() + { + $this->upload->set_max_height(100); + $this->assertEquals(100, $this->upload->max_height); + } + + function test_set_allowed_types() + { + $this->upload->set_allowed_types('*'); + $this->assertEquals('*', $this->upload->allowed_types); + + $this->upload->set_allowed_types('foo|bar'); + $this->assertEquals(array('foo', 'bar'), $this->upload->allowed_types); + } + + function test_set_image_properties() + { + $this->upload->file_type = 'image/gif'; + $this->upload->file_temp = realpath(PROJECT_BASE.'tests/mocks/uploads/ci_logo.gif'); + + $props = array( + 'image_width' => 170, + 'image_height' => 73, + 'image_type' => 'gif', + 'image_size_str' => 'width="170" height="73"' + ); + + $this->upload->set_image_properties($this->upload->file_temp); + + $this->assertEquals($props['image_width'], $this->upload->image_width); + $this->assertEquals($props['image_height'], $this->upload->image_height); + $this->assertEquals($props['image_type'], $this->upload->image_type); + $this->assertEquals($props['image_size_str'], $this->upload->image_size_str); + } + + function test_set_xss_clean() + { + $this->upload->set_xss_clean(TRUE); + $this->assertTrue($this->upload->xss_clean); + + $this->upload->set_xss_clean(FALSE); + $this->assertFalse($this->upload->xss_clean); + } + + function test_is_image() + { + $this->upload->file_type = 'image/x-png'; + $this->assertTrue($this->upload->is_image()); + + $this->upload->file_type = 'text/plain'; + $this->assertFalse($this->upload->is_image()); + } + + function test_is_allowed_filetype() + { + $this->upload->allowed_types = array('html', 'gif'); + + $this->upload->file_ext = '.txt'; + $this->upload->file_type = 'text/plain'; + $this->assertFalse($this->upload->is_allowed_filetype(FALSE)); + $this->assertFalse($this->upload->is_allowed_filetype(TRUE)); + + $this->upload->file_ext = '.html'; + $this->upload->file_type = 'text/html'; + $this->assertTrue($this->upload->is_allowed_filetype(FALSE)); + $this->assertTrue($this->upload->is_allowed_filetype(TRUE)); + + $this->upload->file_temp = realpath(PROJECT_BASE.'tests/mocks/uploads/ci_logo.gif'); + $this->upload->file_ext = '.gif'; + $this->upload->file_type = 'image/gif'; + $this->assertTrue($this->upload->is_allowed_filetype()); + } + + function test_is_allowed_filesize() + { + $this->upload->max_size = 100; + $this->upload->file_size = 99; + + $this->assertTrue($this->upload->is_allowed_filesize()); + + $this->upload->file_size = 101; + $this->assertFalse($this->upload->is_allowed_filesize()); + } + + function test_is_allowed_dimensions() + { + $this->upload->file_type = 'text/plain'; + $this->assertTrue($this->upload->is_allowed_dimensions()); + + $this->upload->file_type = 'image/gif'; + $this->upload->file_temp = realpath(PROJECT_BASE.'tests/mocks/uploads/ci_logo.gif'); + + $this->upload->max_width = 10; + $this->assertFalse($this->upload->is_allowed_dimensions()); + + $this->upload->max_width = 170; + $this->upload->max_height = 10; + $this->assertFalse($this->upload->is_allowed_dimensions()); + + $this->upload->max_height = 73; + $this->assertTrue($this->upload->is_allowed_dimensions()); + } + + function test_validate_upload_path() + { + $this->upload->upload_path = ''; + $this->assertFalse($this->upload->validate_upload_path()); + + $dir = 'uploads'; + $this->ci_vfs_mkdir($dir); + $this->upload->upload_path = $this->ci_vfs_path($dir); + $this->assertTrue($this->upload->validate_upload_path()); + } + + function test_get_extension() + { + $this->assertEquals('.txt', $this->upload->get_extension('hello.txt')); + $this->assertEquals('.htaccess', $this->upload->get_extension('.htaccess')); + $this->assertEquals('', $this->upload->get_extension('hello')); + } + + function test_limit_filename_length() + { + $this->assertEquals('hello.txt', $this->upload->limit_filename_length('hello.txt', 10)); + $this->assertEquals('hello.txt', $this->upload->limit_filename_length('hello-world.txt', 9)); + } + + function test_do_xss_clean() + { + $dir = 'uploads'; + $file1 = 'file1.txt'; + $file2 = 'file2.txt'; + $file3 = 'file3.txt'; + $this->ci_vfs_create($file1, 'The billy goat was waiting for them.', $this->ci_vfs_root, $dir); + $this->ci_vfs_create($file2, '', $this->ci_vfs_root, $dir); + $this->ci_vfs_create($file3, '', $this->ci_vfs_root, $dir); + + $this->upload->file_temp = $this->ci_vfs_path($file1, $dir); + $this->assertTrue($this->upload->do_xss_clean()); + + $this->upload->file_temp = $this->ci_vfs_path($file2, $dir); + $this->assertFalse($this->upload->do_xss_clean()); + + $this->upload->file_temp = $this->ci_vfs_path($file3, $dir); + $this->assertFalse($this->upload->do_xss_clean()); + + $this->upload->file_temp = realpath(PROJECT_BASE.'tests/mocks/uploads/ci_logo.gif'); + $this->assertTrue($this->upload->do_xss_clean()); + } + + function test_set_error() + { + $errors = array( + 'An error!' + ); + + $another = 'Another error!'; + + $this->upload->set_error($errors); + $this->assertEquals($errors, $this->upload->error_msg); + + $errors[] = $another; + $this->upload->set_error($another); + $this->assertEquals($errors, $this->upload->error_msg); + } + + function test_display_errors() + { + $this->upload->error_msg[] = 'Error test'; + $this->assertEquals('

Error test

', $this->upload->display_errors()); + } + + function test_mimes_types() + { + $this->assertEquals('text/plain', $this->upload->mimes_types('txt')); + $this->assertFalse($this->upload->mimes_types('foobar')); + } + +} \ No newline at end of file diff --git a/tests/codeigniter/libraries/Useragent_test.php b/tests/codeigniter/libraries/Useragent_test.php index 89383f80726..e3726554eff 100644 --- a/tests/codeigniter/libraries/Useragent_test.php +++ b/tests/codeigniter/libraries/Useragent_test.php @@ -10,12 +10,11 @@ public function set_up() // set a baseline user agent $_SERVER['HTTP_USER_AGENT'] = $this->_user_agent; - $obj = new stdClass; - $obj->agent = new Mock_Libraries_UserAgent(); + $this->ci_vfs_clone('application/config/user_agents.php'); - $this->ci_instance($obj); + $this->agent = new Mock_Libraries_UserAgent(); - $this->agent = $obj->agent; + $this->ci_instance_var('agent', $this->agent); } // -------------------------------------------------------------------- diff --git a/tests/mocks/autoloader.php b/tests/mocks/autoloader.php index be1c2220c41..3d216da1fc2 100644 --- a/tests/mocks/autoloader.php +++ b/tests/mocks/autoloader.php @@ -16,7 +16,7 @@ function autoload($class) $ci_core = array( 'Benchmark', 'Config', 'Controller', 'Exceptions', 'Hooks', 'Input', - 'Lang', 'Loader', 'Model', + 'Lang', 'Loader', 'Log', 'Model', 'Output', 'Router', 'Security', 'URI', 'Utf8', ); @@ -25,42 +25,58 @@ function autoload($class) 'Calendar', 'Cart', 'Driver_Library', 'Email', 'Encrypt', 'Form_validation', 'Ftp', 'Image_lib', 'Javascript', - 'Log', 'Migration', 'Pagination', - 'Parser', 'Profiler', 'Session', - 'Table', 'Trackback', 'Typography', - 'Unit_test', 'Upload', 'User_agent', - 'Xmlrpc', 'Zip', + 'Migration', 'Pagination', 'Parser', + 'Profiler', 'Table', 'Trackback', + 'Typography', 'Unit_test', 'Upload', + 'User_agent', 'Xmlrpc', 'Zip' + ); + + $ci_drivers = array( + 'Session', ); if (strpos($class, 'Mock_') === 0) { - $class = str_replace(array('Mock_', '_'), array('', DIRECTORY_SEPARATOR), $class); - $class = strtolower($class); + $class = strtolower(str_replace(array('Mock_', '_'), array('', DIRECTORY_SEPARATOR), $class)); } elseif (strpos($class, 'CI_') === 0) { - $fragments = explode('_', $class, 2); - $subclass = next($fragments); + $subclass = substr($class, 3); if (in_array($subclass, $ci_core)) { - $dir = BASEPATH.'core'.DIRECTORY_SEPARATOR; + $dir = SYSTEM_PATH.'core'.DIRECTORY_SEPARATOR; $class = $subclass; } elseif (in_array($subclass, $ci_libraries)) { - $dir = BASEPATH.'libraries'.DIRECTORY_SEPARATOR; + $dir = SYSTEM_PATH.'libraries'.DIRECTORY_SEPARATOR; $class = ($subclass === 'Driver_Library') ? 'Driver' : $subclass; } + elseif (in_array($subclass, $ci_drivers)) + { + $dir = SYSTEM_PATH.'libraries'.DIRECTORY_SEPARATOR.$subclass.DIRECTORY_SEPARATOR; + $class = $subclass; + } + elseif (in_array(($parent = strtok($subclass, '_')), $ci_drivers)) { + $dir = SYSTEM_PATH.'libraries'.DIRECTORY_SEPARATOR.$parent.DIRECTORY_SEPARATOR.'drivers'.DIRECTORY_SEPARATOR; + $class = $subclass; + } + elseif (preg_match('/^CI_DB_(.+)_(.+)_(driver|forge|result|utility)$/', $class, $m) && count($m) === 4) + { + $driver_path = SYSTEM_PATH.'database'.DIRECTORY_SEPARATOR.'drivers'.DIRECTORY_SEPARATOR; + $dir = $driver_path.$m[1].DIRECTORY_SEPARATOR.'subdrivers'.DIRECTORY_SEPARATOR; + $file = $dir.$m[1].'_'.$m[2].'_'.$m[3].'.php'; + } elseif (preg_match('/^CI_DB_(.+)_(driver|forge|result|utility)$/', $class, $m) && count($m) === 3) { - $driver_path = BASEPATH.'database'.DIRECTORY_SEPARATOR.'drivers'.DIRECTORY_SEPARATOR; + $driver_path = SYSTEM_PATH.'database'.DIRECTORY_SEPARATOR.'drivers'.DIRECTORY_SEPARATOR; $dir = $driver_path.$m[1].DIRECTORY_SEPARATOR; $file = $dir.$m[1].'_'.$m[2].'.php'; } elseif (strpos($class, 'CI_DB') === 0) { - $dir = BASEPATH.'database'.DIRECTORY_SEPARATOR; + $dir = SYSTEM_PATH.'database'.DIRECTORY_SEPARATOR; $file = $dir.str_replace(array('CI_DB','active_record'), array('DB', 'active_rec'), $subclass).'.php'; } else @@ -69,7 +85,7 @@ function autoload($class) } } - $file = (isset($file)) ? $file : $dir.$class.'.php'; + $file = isset($file) ? $file : $dir.$class.'.php'; if ( ! file_exists($file)) { diff --git a/tests/mocks/ci_testcase.php b/tests/mocks/ci_testcase.php index eda9e1b603e..ad4fe5ac3d7 100644 --- a/tests/mocks/ci_testcase.php +++ b/tests/mocks/ci_testcase.php @@ -2,7 +2,9 @@ class CI_TestCase extends PHPUnit_Framework_TestCase { - protected $ci_config; + public $ci_vfs_root; + public $ci_app_root; + public $ci_base_root; protected $ci_instance; protected static $ci_test_instance; @@ -25,13 +27,19 @@ class CI_TestCase extends PHPUnit_Framework_TestCase { public function __construct() { parent::__construct(); - $this->ci_config = array(); + $this->ci_instance = new stdClass(); } // -------------------------------------------------------------------- public function setUp() { + // Setup VFS with base directories + $this->ci_vfs_root = vfsStream::setup(); + $this->ci_app_root = vfsStream::newDirectory('application')->at($this->ci_vfs_root); + $this->ci_base_root = vfsStream::newDirectory('system')->at($this->ci_vfs_root); + $this->ci_view_root = vfsStream::newDirectory('views')->at($this->ci_app_root); + if (method_exists($this, 'set_up')) { $this->set_up(); @@ -57,15 +65,27 @@ public static function instance() // -------------------------------------------------------------------- - public function ci_set_config($key, $val = '') + public function ci_set_config($key = '', $val = '') { + // Add test config + if ( ! isset($this->ci_instance->config)) + { + $this->ci_instance->config = new CI_TestConfig(); + } + + // Empty key means just do setup above + if ($key === '') + { + return; + } + if (is_array($key)) { - $this->ci_config = $key; + $this->ci_instance->config->config = $key; } else { - $this->ci_config[$key] = $val; + $this->ci_instance->config->config[$key] = $val; } } @@ -73,7 +93,7 @@ public function ci_set_config($key, $val = '') public function ci_get_config() { - return $this->ci_config; + return isset($this->ci_instance->config) ? $this->ci_instance->config->config : array(); } // -------------------------------------------------------------------- @@ -132,7 +152,7 @@ public function &ci_core_class($name) if ( ! class_exists('CI_'.$class_name)) { - require_once BASEPATH.'core/'.$class_name.'.php'; + require_once SYSTEM_PATH.'core/'.$class_name.'.php'; } $GLOBALS[strtoupper($global_name)] = 'CI_'.$class_name; @@ -148,6 +168,165 @@ public function ci_set_core_class($name, $obj) $orig = $obj; } + /** + * Create VFS directory + * + * @param string Directory name + * @param object Optional root to create in + * @return object New directory object + */ + public function ci_vfs_mkdir($name, $root = NULL) + { + // Check for root + if ( ! $root) + { + $root = $this->ci_vfs_root; + } + + // Return new directory object + return vfsStream::newDirectory($name)->at($root); + } + + // -------------------------------------------------------------------- + + /** + * Create VFS content + * + * @param string File name + * @param string File content + * @param object VFS directory object + * @param mixed Optional subdirectory path or array of subs + * @return void + */ + public function ci_vfs_create($file, $content = '', $root = NULL, $path = NULL) + { + // Check for array + if (is_array($file)) + { + foreach ($file as $name => $content) + { + $this->ci_vfs_create($name, $content, $root, $path); + } + return; + } + + // Assert .php extension if none given + if (pathinfo($file, PATHINFO_EXTENSION) == '') + { + $file .= '.php'; + } + + // Build content + $tree = array($file => $content); + + // Check for path + $subs = array(); + if ($path) + { + // Explode if not array + $subs = is_array($path) ? $path : explode('/', trim($path, '/')); + } + + // Check for root + if ( ! $root) + { + // Use base VFS root + $root = $this->ci_vfs_root; + } + + // Handle subdirectories + while (($dir = array_shift($subs))) + { + // See if subdir exists under current root + $dir_root = $root->getChild($dir); + if ($dir_root) + { + // Yes - recurse into subdir + $root = $dir_root; + } + else + { + // No - put subdirectory back and quit + array_unshift($subs, $dir); + break; + } + } + + // Create any remaining subdirectories + if ($subs) + { + foreach (array_reverse($subs) as $dir) + { + // Wrap content in subdirectory for creation + $tree = array($dir => $tree); + } + } + + // Create tree + vfsStream::create($tree, $root); + } + + // -------------------------------------------------------------------- + + /** + * Clone a real file into VFS + * + * @param string Path from base directory + * @return bool TRUE on success, otherwise FALSE + */ + public function ci_vfs_clone($path) + { + // Check for array + if (is_array($path)) + { + foreach ($path as $file) + { + $this->ci_vfs_clone($file); + } + return; + } + + // Get real file contents + $content = file_get_contents(PROJECT_BASE.$path); + if ($content === FALSE) + { + // Couldn't find file to clone + return FALSE; + } + + $this->ci_vfs_create(basename($path), $content, NULL, dirname($path)); + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Helper to get a VFS URL path + * + * @param string Path + * @param string Optional base path + * @return string Path URL + */ + public function ci_vfs_path($path, $base = '') + { + // Check for base path + if ($base) + { + // Prepend to path + $path = rtrim($base, '/').'/'.ltrim($path, '/'); + + // Is it already in URL form? + if (strpos($path, '://') !== FALSE) + { + // Done - return path + return $path; + } + } + + // Trim leading slash and return URL + return vfsStream::url(http://github.com/ltrim($path,%20'/')); + } + // -------------------------------------------------------------------- // Internals // -------------------------------------------------------------------- @@ -171,7 +350,15 @@ public function runBare() public function helper($name) { - require_once(BASEPATH.'helpers/'.$name.'_helper.php'); + require_once(SYSTEM_PATH.'helpers/'.$name.'_helper.php'); + } + + // -------------------------------------------------------------------- + + public function lang($name) + { + require(SYSTEM_PATH.'language/english/'.$name.'_lang.php'); + return $lang; } // -------------------------------------------------------------------- diff --git a/tests/mocks/ci_testconfig.php b/tests/mocks/ci_testconfig.php new file mode 100644 index 00000000000..f80adc5d4eb --- /dev/null +++ b/tests/mocks/ci_testconfig.php @@ -0,0 +1,20 @@ +config[$key]) ? $this->config[$key] : FALSE; + } + + public function load($file, $arg2 = FALSE, $arg3 = FALSE) + { + $this->loaded[] = $file; + return TRUE; + } + +} \ No newline at end of file diff --git a/tests/mocks/core/common.php b/tests/mocks/core/common.php index a655ee1dbef..0ccfe1ea4b9 100644 --- a/tests/mocks/core/common.php +++ b/tests/mocks/core/common.php @@ -39,6 +39,30 @@ function config_item($item) } } +if ( ! function_exists('get_mimes')) +{ + /** + * Returns the MIME types array from config/mimes.php + * + * @return array + */ + function &get_mimes() + { + static $_mimes = array(); + + if (empty($_mimes)) + { + $path = realpath(PROJECT_BASE.'application/config/mimes.php'); + if (is_file($path)) + { + $_mimes = include($path); + } + } + + return $_mimes; + } +} + // -------------------------------------------------------------------- if ( ! function_exists('load_class')) @@ -124,7 +148,6 @@ function _exception_handler($severity, $message, $filepath, $line) } } - // We assume a few things about our environment ... // -------------------------------------------------------------------- @@ -146,15 +169,16 @@ function is_really_writable($file) if ( ! function_exists('is_loaded')) { - function is_loaded() + function &is_loaded() { - throw new Exception('Bad Isolation: mock up environment'); + $loaded = array(); + return $loaded; } } if ( ! function_exists('log_message')) { - function log_message($level = 'error', $message, $php_error = FALSE) + function log_message($level, $message, $php_error = FALSE) { return TRUE; } diff --git a/tests/mocks/core/input.php b/tests/mocks/core/input.php index 2a4aa499794..0d187384962 100644 --- a/tests/mocks/core/input.php +++ b/tests/mocks/core/input.php @@ -28,4 +28,14 @@ public function fetch_from_array($array, $index = '', $xss_clean = FALSE) return parent::_fetch_from_array($array, $index, $xss_clean); } + /** + * Lie about being a CLI request + * + * We take advantage of this in libraries/Session_test + */ + public function is_cli_request() + { + return FALSE; + } + } \ No newline at end of file diff --git a/tests/mocks/core/loader.php b/tests/mocks/core/loader.php deleted file mode 100644 index 53d88d55bef..00000000000 --- a/tests/mocks/core/loader.php +++ /dev/null @@ -1,31 +0,0 @@ -models_dir = vfsStream::newDirectory('models')->at(vfsStreamWrapper::getRoot()); - $this->libs_dir = vfsStream::newDirectory('libraries')->at(vfsStreamWrapper::getRoot()); - $this->helpers_dir = vfsStream::newDirectory('helpers')->at(vfsStreamWrapper::getRoot()); - $this->views_dir = vfsStream::newDirectory('views')->at(vfsStreamWrapper::getRoot()); - - $this->_ci_ob_level = ob_get_level(); - $this->_ci_library_paths = array(vfsStream::url('http://github.com/application').'/', BASEPATH); - $this->_ci_helper_paths = array(vfsStream::url('http://github.com/application').'/', BASEPATH); - $this->_ci_model_paths = array(vfsStream::url('http://github.com/application').'/'); - $this->_ci_view_paths = array(vfsStream::url('http://github.com/application').'/views/' => TRUE); - } - -} \ No newline at end of file diff --git a/tests/mocks/database/ci_test.sqlite b/tests/mocks/database/ci_test.sqlite index 44dcef9ec5e..574d3ae53bc 100755 Binary files a/tests/mocks/database/ci_test.sqlite and b/tests/mocks/database/ci_test.sqlite differ diff --git a/tests/mocks/database/config/mysqli.php b/tests/mocks/database/config/mysqli.php new file mode 100644 index 00000000000..5dd08abb23d --- /dev/null +++ b/tests/mocks/database/config/mysqli.php @@ -0,0 +1,34 @@ + array( + 'dsn' => '', + 'hostname' => 'localhost', + 'username' => 'travis', + 'password' => '', + 'database' => 'ci_test', + 'dbdriver' => 'mysqli' + ), + + // Database configuration with failover + 'mysqli_failover' => array( + 'dsn' => '', + 'hostname' => 'localhost', + 'username' => 'not_travis', + 'password' => 'wrong password', + 'database' => 'not_ci_test', + 'dbdriver' => 'mysqli', + 'failover' => array( + array( + 'dsn' => '', + 'hostname' => 'localhost', + 'username' => 'travis', + 'password' => '', + 'database' => 'ci_test', + 'dbdriver' => 'mysqli', + ) + ) + ) +); \ No newline at end of file diff --git a/tests/mocks/database/db.php b/tests/mocks/database/db.php index 75658530b7e..968476dea7b 100644 --- a/tests/mocks/database/db.php +++ b/tests/mocks/database/db.php @@ -7,6 +7,16 @@ class Mock_Database_DB { */ private $config = array(); + /** + * @var string DB driver name + */ + private static $dbdriver = ''; + + /** + * @var string DB sub-driver name + */ + private static $subdriver = ''; + /** * Prepare database configuration skeleton * @@ -31,6 +41,12 @@ public function set_dsn($group = 'default') throw new InvalidArgumentException('Group '.$group.' not exists'); } + self::$dbdriver = $this->config[$group]['dbdriver']; + if (isset($this->config[$group]['subdriver'])) + { + self::$subdriver = $this->config[$group]['subdriver']; + } + $params = array( 'dbprefix' => '', 'pconnect' => FALSE, @@ -50,7 +66,7 @@ public function set_dsn($group = 'default') $failover = empty($config['failover']) ? FALSE : $config['failover']; $dsn = $config['dbdriver'].'://'.$config['username'].':'.$config['password'] - .'@'.$config['hostname'].'/'.$config['database']; + .'@'.$config['hostname'].'/'.$config['database']; // Build the parameter $other_params = array_slice($config, 6); @@ -83,7 +99,34 @@ public static function config($driver) */ public static function DB($group, $query_builder = FALSE) { - include_once(BASEPATH.'database/DB.php'); + // Create dummy driver and builder files to "load" - the mocks have + // already triggered autoloading of the real files + $case = CI_TestCase::instance(); + $driver = self::$dbdriver; + $subdriver = self::$subdriver; + $case->ci_vfs_create(array( + 'DB_driver.php' => '', + 'DB_result.php' => '', + 'DB_forge.php' => '', + 'DB_query_builder.php' => '' + ), '', $case->ci_base_root, 'database'); + if (file_exists(SYSTEM_PATH.'database/drivers/'.$driver.'/'.$driver.'_driver.php')) + { + $case->ci_vfs_create(array( + $driver.'_driver.php' => '', + $driver.'_result.php' => '', + $driver.'_forge.php' => '' + ), '', $case->ci_base_root, 'database/drivers/'.$driver); + } + if ($subdriver) + { + $case->ci_vfs_create(array( + $driver.'_'.$subdriver.'_driver.php' => '', + $driver.'_'.$subdriver.'_forge.php' => '' + ), '', $case->ci_base_root, 'database/drivers/'.$driver.'/subdrivers'); + } + + include_once(SYSTEM_PATH.'database/DB.php'); try { diff --git a/tests/mocks/database/db/driver.php b/tests/mocks/database/db/driver.php index 65ac2c4ccef..2cf54b97bc9 100644 --- a/tests/mocks/database/db/driver.php +++ b/tests/mocks/database/db/driver.php @@ -34,6 +34,7 @@ public function __call($method, $arguments) return call_user_func_array(array($this->ci_db_driver, $method), $arguments); } + } class CI_DB extends Mock_Database_DB_QueryBuilder {} \ No newline at end of file diff --git a/tests/mocks/database/drivers/mysqli.php b/tests/mocks/database/drivers/mysqli.php new file mode 100644 index 00000000000..73c35b6099e --- /dev/null +++ b/tests/mocks/database/drivers/mysqli.php @@ -0,0 +1,17 @@ +ci_instance_var('db', $db); - $loader = new Mock_Core_Loader(); + $loader = new CI_Loader(); $loader->dbforge(); $forge = CI_TestCase::instance()->ci_instance_var('dbforge'); @@ -69,7 +69,7 @@ public static function create_tables() ) )); static::$forge->add_key('id', TRUE); - static::$forge->create_table('user', (strpos(static::$driver, 'pgsql') === FALSE)); + static::$forge->create_table('user', TRUE); // Job Table static::$forge->add_field(array( @@ -86,7 +86,7 @@ public static function create_tables() ) )); static::$forge->add_key('id', TRUE); - static::$forge->create_table('job', (strpos(static::$driver, 'pgsql') === FALSE)); + static::$forge->create_table('job', TRUE); // Misc Table static::$forge->add_field(array( @@ -103,7 +103,7 @@ public static function create_tables() ) )); static::$forge->add_key('id', TRUE); - static::$forge->create_table('misc', (strpos(static::$driver, 'pgsql') === FALSE)); + static::$forge->create_table('misc', TRUE); } /** @@ -129,7 +129,8 @@ public static function create_data() ), 'misc' => array( array('id' => 1, 'key' => '\\xxxfoo456', 'value' => 'Entry with \\xxx'), - array('id' => 2, 'key' => '\\%foo456', 'value' => 'Entry with \\%') + array('id' => 2, 'key' => '\\%foo456', 'value' => 'Entry with \\%'), + array('id' => 3, 'key' => 'spaces and tabs', 'value' => ' One two three tab') ) ); diff --git a/tests/mocks/libraries/driver.php b/tests/mocks/libraries/driver.php new file mode 100644 index 00000000000..633194345ac --- /dev/null +++ b/tests/mocks/libraries/driver.php @@ -0,0 +1,27 @@ +valid_drivers; + } + + $this->valid_drivers = (array) $drivers; + } + + /** + * Get library name + */ + public function get_name() + { + return $this->lib_name; + } +} \ No newline at end of file diff --git a/tests/mocks/libraries/session.php b/tests/mocks/libraries/session.php new file mode 100644 index 00000000000..adbecb32970 --- /dev/null +++ b/tests/mocks/libraries/session.php @@ -0,0 +1,38 @@ +_flashdata_sweep(); + $this->_flashdata_mark(); + $this->_tempdata_sweep(); + } +} + +/** + * Mock cookie driver to overload cookie setting + */ +class Mock_Libraries_Session_cookie extends CI_Session_cookie { + /** + * Overload _setcookie to manage $_COOKIE values, since actual cookies can't be set in unit testing + */ + protected function _setcookie($name, $value = '', $expire = 0, $path = '', $domain = '', $secure = FALSE, $httponly = FALSE) + { + if (empty($value) OR $expire <= time()) + { + unset($_COOKIE[$name]); + } + else + { + $_COOKIE[$name] = $value; + } + } +} + +class Mock_Libraries_Session_native extends CI_Session_native {} \ No newline at end of file diff --git a/tests/mocks/libraries/upload.php b/tests/mocks/libraries/upload.php new file mode 100644 index 00000000000..988723e4574 --- /dev/null +++ b/tests/mocks/libraries/upload.php @@ -0,0 +1,3 @@ + -./codeigniter/libraries - + PEAR_INSTALL_DIR PHP_LIBDIR + ../vendor - + \ No newline at end of file diff --git a/tests/travis/mysql.phpunit.xml b/tests/travis/mysql.phpunit.xml index 38c8eba48db..06d4a011b9a 100644 --- a/tests/travis/mysql.phpunit.xml +++ b/tests/travis/mysql.phpunit.xml @@ -18,7 +18,7 @@ - + ../../system diff --git a/tests/travis/mysqli.phpunit.xml b/tests/travis/mysqli.phpunit.xml new file mode 100644 index 00000000000..1364f8bfa47 --- /dev/null +++ b/tests/travis/mysqli.phpunit.xml @@ -0,0 +1,25 @@ + + + + + + + + + ../codeigniter + + + + + ../../system + + + \ No newline at end of file diff --git a/tests/travis/pdo/mysql.phpunit.xml b/tests/travis/pdo/mysql.phpunit.xml index c3113a66f61..7121edc455c 100644 --- a/tests/travis/pdo/mysql.phpunit.xml +++ b/tests/travis/pdo/mysql.phpunit.xml @@ -18,7 +18,7 @@ - + ../../../system diff --git a/tests/travis/pdo/pgsql.phpunit.xml b/tests/travis/pdo/pgsql.phpunit.xml index 232025523cd..df3ff986e21 100644 --- a/tests/travis/pdo/pgsql.phpunit.xml +++ b/tests/travis/pdo/pgsql.phpunit.xml @@ -18,7 +18,7 @@ - + ../../../system diff --git a/tests/travis/pdo/sqlite.phpunit.xml b/tests/travis/pdo/sqlite.phpunit.xml index 3d1256721a4..7d867f6d15a 100644 --- a/tests/travis/pdo/sqlite.phpunit.xml +++ b/tests/travis/pdo/sqlite.phpunit.xml @@ -18,7 +18,7 @@ - + ../../../system diff --git a/tests/travis/pgsql.phpunit.xml b/tests/travis/pgsql.phpunit.xml index 51e433d7610..bfddbf6b58e 100644 --- a/tests/travis/pgsql.phpunit.xml +++ b/tests/travis/pgsql.phpunit.xml @@ -18,7 +18,7 @@ - + ../../system diff --git a/tests/travis/sqlite.phpunit.xml b/tests/travis/sqlite.phpunit.xml index 70116573429..75c946aeea9 100644 --- a/tests/travis/sqlite.phpunit.xml +++ b/tests/travis/sqlite.phpunit.xml @@ -18,7 +18,7 @@ - + ../../system diff --git a/user_guide_src/cilexer/cilexer/cilexer.py b/user_guide_src/cilexer/cilexer/cilexer.py index 1c41f2aa0df..1edc7e820c4 100644 --- a/user_guide_src/cilexer/cilexer/cilexer.py +++ b/user_guide_src/cilexer/cilexer/cilexer.py @@ -15,7 +15,7 @@ # through the world wide web, please send an email to # licensing@ellislab.com so we can send you a copy immediately. # -# Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/) +# Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/) # http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) diff --git a/user_guide_src/source/_themes/eldocs/layout.html b/user_guide_src/source/_themes/eldocs/layout.html index 01db07cac1d..7f2fe1a3448 100644 --- a/user_guide_src/source/_themes/eldocs/layout.html +++ b/user_guide_src/source/_themes/eldocs/layout.html @@ -81,7 +81,7 @@ {%- block content %}
- {{ toctree(collapse=true) }} + {{ toctree }}
@@ -91,13 +91,7 @@ -
+
+ {%- block body %} {{ body }} + {%- endblock %}
{%- endblock %} @@ -125,8 +121,8 @@ {%- block footer %} {%- endblock %} - + \ No newline at end of file diff --git a/user_guide_src/source/_themes/eldocs/searchbox.html b/user_guide_src/source/_themes/eldocs/searchbox.html new file mode 100644 index 00000000000..039590bd97c --- /dev/null +++ b/user_guide_src/source/_themes/eldocs/searchbox.html @@ -0,0 +1,21 @@ + + + + diff --git a/user_guide_src/source/_themes/eldocs/static/asset/css/common.css b/user_guide_src/source/_themes/eldocs/static/asset/css/common.css index 6cabda03748..648e3303230 100644 --- a/user_guide_src/source/_themes/eldocs/static/asset/css/common.css +++ b/user_guide_src/source/_themes/eldocs/static/asset/css/common.css @@ -16,7 +16,7 @@ If you did not receive a copy of the license and are unable to obtain it through the world wide web, please send an email to licensing@ellislab.com so we can send you a copy immediately. -Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/) +Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/) http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) */ @@ -148,6 +148,8 @@ fieldset{ border: 0; } .top{ float: right; } +.highlight{ overflow-x: auto; } + .admonition, .highlight-ee, .highlight-ci, @@ -166,6 +168,8 @@ fieldset{ border: 0; } padding: 10px 10px 8px; } +.highlight-ci{ background-color: #FEFEFE; border-color: #E5E5E5; } + .admonition p{ margin: 0; } .codeblock{ margin: 10px 0; } @@ -181,6 +185,8 @@ fieldset{ border: 0; } } .admonition-title:after{ content: ': '; } + +.highlighted{ background-color: #FFF09B; } #table-contents{ bottom: 0; diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst index 827b1f0909d..84989189db7 100644 --- a/user_guide_src/source/changelog.rst +++ b/user_guide_src/source/changelog.rst @@ -19,6 +19,7 @@ Release Date: Not Released - General Changes - PHP 5.1.6 is no longer supported. CodeIgniter now requires PHP 5.2.4. + - Changed filenaming convention (class file names now must be Ucfirst and everything else in lowercase). - ``$_SERVER['CI_ENV']`` can now be set to control the ``ENVIRONMENT`` constant. - Added an optional backtrace to php-error template. - Added Android to the list of user agents. @@ -26,205 +27,431 @@ Release Date: Not Released - Added Fennec (Firefox for mobile) to the list of mobile user agents. - Ability to log certain error types, not all under a threshold. - Added support for pem, p10, p12, p7a, p7c, p7m, p7r, p7s, crt, crl, der, kdb, rsa, cer, sst, csr Certs to mimes.php. - - Added support for pgp and gpg to mimes.php. + - Added support for pgp, gpg, zsh and cdr files to mimes.php. - Added support for 3gp, 3g2, mp4, wmv, f4v, vlc Video files to mimes.php. - - Added support for m4a, aac, m4u, xspf, au, ac3, flac, ogg Audio files to mimes.php. + - Added support for m4a, aac, m4u, xspf, au, ac3, flac, ogg, wma Audio files to mimes.php. - Added support for kmz and kml (Google Earth) files to mimes.php. - Added support for ics Calendar files to mimes.php. - - Added support for rar archives to mimes.php. + - Added support for rar, jar and 7zip archives to mimes.php. - Updated support for xml ('application/xml') and xsl ('application/xml', 'text/xsl') files in mimes.php. - Updated support for doc files in mimes.php. + - Updated support for docx files in mimes.php. - Updated support for php files in mimes.php. - Updated support for zip files in mimes.php. - Updated support for csv files in mimes.php. - - Added some more doctypes. - - Added Romanian and Greek characters in foreign_characters.php. + - Added Romanian, Greek, Vietnamese and Cyrilic characters in *application/config/foreign_characters.php*. - Changed logger to only chmod when file is first created. - Removed previously deprecated SHA1 Library. - - Removed previously deprecated use of ``$autoload['core']`` in application/config/autoload.php. + - Removed previously deprecated use of ``$autoload['core']`` in *application/config/autoload.php*. Only entries in ``$autoload['libraries']`` are auto-loaded now. - Removed previously deprecated EXT constant. - Updated all classes to be written in PHP 5 style, with visibility declarations and no ``var`` usage for properties. - - Moved error templates to "application/views/errors" + - Moved error templates to *application/views/errors/*. + - Moved the Log class to *application/core/* - Global config files are loaded first, then environment ones. Environment config keys overwrite base ones, allowing to only set the keys we want changed per environment. - Changed detection of ``$view_folder`` so that if it's not found in the current path, it will now also be searched for under the application folder. - Path constants BASEPATH, APPPATH and VIEWPATH are now (internally) defined as absolute paths. - Updated email validation methods to use ``filter_var()`` instead of PCRE. - - Changed environment defaults to report all errors in 'development' and only fatal ones in 'testing' and 'production' but only display them in 'development'. + - Changed environment defaults to report all errors in *development* and only fatal ones in *testing*, *production* but only display them in *development*. + - Updated *ip_address* database field lengths from 16 to 45 for supporting IPv6 address on :doc:`Trackback Library ` and :doc:`Captcha Helper `. + - Removed *cheatsheets* and *quick_reference* PDFs from the documentation. + - Added availability checks where usage of dangerous functions like ``eval()`` and ``exec()`` is required. + - Added support for changing the file extension of log files using ``$config['log_file_extension']``. - Helpers - :doc:`Date Helper ` changes include: - - ``now()`` now works with all timezone strings supported by PHP. - - Added an optional third parameter to ``timespan()`` that constrains the number of time units displayed. - - Added an optional parameter to ``timezone_menu()`` that allows more attributes to be added to the generated select tag. - - Deprecated ``standard_date()``, which now just uses the native ``date()`` with `DateTime constants `_. - - ``create_captcha()`` accepts additional colors parameter, allowing for color customization. + + - :php:func:`now()` now works with all timezone strings supported by PHP. + - Added an optional third parameter to :php:func:`timespan()` that constrains the number of time units displayed. + - Added an optional parameter to :php:func:`timezone_menu()` that allows more attributes to be added to the generated select tag. + - Deprecated ``standard_date()``, which now just uses the native ``date()`` with `DateTime constants `_. + - Added function :php:func:`date_range()` that generates a list of dates between a specified period. + - :doc:`URL Helper ` changes include: - - ``url_title()`` will now trim extra dashes from beginning and end. - - ``anchor_popup()`` will now fill the "href" attribute with the URL and its JS code will return false instead. - - Added JS window name support to ``anchor_popup()`` function. - - Added support (auto-detection) for HTTP/1.1 response code 303 in ``redirect()``. - - Added XHTML Basic 1.1 doctype to :doc:`HTML Helper `. - - Changed ``humanize()`` to include a second param for the separator. - - Refactored ``plural()`` and ``singular()`` to avoid double pluralization and support more words. - - Added an optional third parameter to ``force_download()`` that enables/disables sending the actual file MIME type in the Content-Type header (disabled by default). - - Added a work-around in ``force_download()`` for a bug Android <= 2.1, where the filename extension needs to be in uppercase. - - ``form_dropdown()`` will now also take an array for unity with other form helpers. - - ``do_hash()`` now uses PHP's native ``hash()`` function (supporting more algorithms) and is deprecated. - - Removed previously deprecated helper function ``js_insert_smiley()`` from smiley helper. + + - Deprecated *separator* options **dash** and **underscore** for function :php:func:`url_title()` (they are only aliases for '-' and '_' respectively). + - :php:func:`url_title()` will now trim extra dashes from beginning and end. + - :php:func:`anchor_popup()` will now fill the *href* attribute with the URL and its JS code will return FALSE instead. + - Added JS window name support to the :php:func:`anchor_popup()` function. + - Added support (auto-detection) for HTTP/1.1 response code 303 in :php:func:`redirect()`. + - Changed :php:func:`redirect()` to choose the **refresh** method only on IIS servers, instead of all servers on Windows (when **auto** is used). + - Changed :php:func:`anchor()`, :php:func:`anchor_popup()`, and :php:func:`redirect()` to support protocol-relative URLs (e.g. *//ellislab.com/codeigniter*). + - Added an optional second parameter to both :php:func:`base_url()` and :php:func:`site_url()` that allows enforcing of a protocol different than the one in the *base_url* configuration setting. + + - :doc:`HTML Helper ` changes include: + + - Added more doctypes. + - Changed application and environment config files to be loaded in a cascade-like manner. + - The doctypes array is now cached and loaded only once. + + - :doc:`Inflector Helper ` changes include: + + - Changed :php:func:`humanize()` to allow passing an input separator as its second parameter. + - Refactored :php:func:`plural()` and :php:func:`singular()` to avoid double pluralization and support more words. + + - :doc:`Download Helper ` changes include: + + - Added an optional third parameter to :php:func:`force_download()` that enables/disables sending the actual file MIME type in the Content-Type header (disabled by default). + - Added a work-around in :php:func:`force_download()` for a bug Android <= 2.1, where the filename extension needs to be in uppercase. + - Added support for reading from an existing file path by passing NULL as the second parameter to :php:func:`force_download()` (useful for large files and/or safely transmitting binary data). + + - :doc:`Form Helper ` changes include: + + - :php:func:`form_dropdown()` will now also take an array for unity with other form helpers. + - :php:func:`form_prep()`'s second argument now only accepts a boolean value, which determines whether the value is escaped for a