diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..39f48d9
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,15 @@
+# top-most EditorConfig file
+root = true
+
+# Unix-style newlines with a newline ending every file
+[*]
+end_of_line = lf
+insert_final_newline = true
+
+# Matches multiple files with brace expansion notation
+# Set default charset
+[*]
+charset = utf-8
+
+# Tab indentation (no size specified)
+indent_style = tab
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..269044e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,31 @@
+.DS_Store
+
+application/cache/*
+!application/cache/index.html
+
+application/logs/*
+!application/logs/index.html
+
+!application/*/.htaccess
+
+composer.lock
+
+user_guide_src/build/*
+user_guide_src/cilexer/build/*
+user_guide_src/cilexer/dist/*
+user_guide_src/cilexer/pycilexer.egg-info/*
+/vendor/
+
+# IDE Files
+#-------------------------
+/nbproject/
+.idea/*
+
+## Sublime Text cache files
+*.tmlanguage.cache
+*.tmPreferences.cache
+*.stTheme.cache
+*.sublime-workspace
+*.sublime-project
+/tests/tests/
+/tests/results/
diff --git a/application/.htaccess b/application/.htaccess
new file mode 100644
index 0000000..6c63ed4
--- /dev/null
+++ b/application/.htaccess
@@ -0,0 +1,6 @@
+
Directory access is forbidden.
+ + + diff --git a/application/config/autoload.php b/application/config/autoload.php new file mode 100644 index 0000000..b960eab --- /dev/null +++ b/application/config/autoload.php @@ -0,0 +1,135 @@ + 'ua'); +*/ +$autoload['libraries'] = array('database', 'email', 'session'); + +/* +| ------------------------------------------------------------------- +| Auto-load Drivers +| ------------------------------------------------------------------- +| These classes are located in system/libraries/ or in your +| application/libraries/ directory, but are also placed inside their +| own subdirectory and they extend the CI_Driver_Library class. They +| offer multiple interchangeable driver options. +| +| Prototype: +| +| $autoload['drivers'] = array('cache'); +| +| You can also supply an alternative property name to be assigned in +| the controller: +| +| $autoload['drivers'] = array('cache' => 'cch'); +| +*/ +$autoload['drivers'] = array(); + +/* +| ------------------------------------------------------------------- +| Auto-load Helper Files +| ------------------------------------------------------------------- +| Prototype: +| +| $autoload['helper'] = array('url', 'file'); +*/ +$autoload['helper'] = array('url', 'file'); + +/* +| ------------------------------------------------------------------- +| Auto-load Config files +| ------------------------------------------------------------------- +| Prototype: +| +| $autoload['config'] = array('config1', 'config2'); +| +| NOTE: This item is intended for use ONLY if you have created custom +| config files. Otherwise, leave it blank. +| +*/ +$autoload['config'] = array(); + +/* +| ------------------------------------------------------------------- +| Auto-load Language files +| ------------------------------------------------------------------- +| Prototype: +| +| $autoload['language'] = array('lang1', 'lang2'); +| +| NOTE: Do not include the "_lang" part of your file. For example +| "codeigniter_lang.php" would be referenced as array('codeigniter'); +| +*/ +$autoload['language'] = array(); + +/* +| ------------------------------------------------------------------- +| Auto-load Models +| ------------------------------------------------------------------- +| Prototype: +| +| $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 new file mode 100644 index 0000000..aa550d7 --- /dev/null +++ b/application/config/config.php @@ -0,0 +1,523 @@ +]+$/i +| +| DO NOT CHANGE THIS UNLESS YOU FULLY UNDERSTAND THE REPERCUSSIONS!! +| +*/ +$config['permitted_uri_chars'] = 'a-z 0-9~%.:_\-'; + +/* +|-------------------------------------------------------------------------- +| Enable Query Strings +|-------------------------------------------------------------------------- +| +| By default CodeIgniter uses search-engine friendly segment based URLs: +| example.com/who/what/where/ +| +| You can optionally enable standard query string based URLs: +| example.com?who=me&what=something&where=here +| +| Options are: TRUE or FALSE (boolean) +| +| The other items let you set the query string 'words' that will +| invoke your controllers and its functions: +| example.com/index.php?c=controller&m=function +| +| Please note that some of the helpers won't work as expected when +| this feature is enabled, since CodeIgniter is designed primarily to +| use segment based URLs. +| +*/ +$config['enable_query_strings'] = FALSE; +$config['controller_trigger'] = 'c'; +$config['function_trigger'] = 'm'; +$config['directory_trigger'] = 'd'; + +/* +|-------------------------------------------------------------------------- +| Allow $_GET array +|-------------------------------------------------------------------------- +| +| By default CodeIgniter enables access to the $_GET array. If for some +| reason you would like to disable it, set 'allow_get_array' to FALSE. +| +| WARNING: This feature is DEPRECATED and currently available only +| for backwards compatibility purposes! +| +*/ +$config['allow_get_array'] = TRUE; + +/* +|-------------------------------------------------------------------------- +| Error Logging Threshold +|-------------------------------------------------------------------------- +| +| You can enable error logging by setting a threshold over zero. The +| threshold determines what gets logged. Threshold options are: +| +| 0 = Disables logging, Error logging TURNED OFF +| 1 = Error Messages (including PHP errors) +| 2 = Debug Messages +| 3 = Informational Messages +| 4 = All Messages +| +| You can also pass an array with threshold levels to show individual error types +| +| array(2) = Debug Messages, without Error Messages +| +| For a live site you'll usually only enable Errors (1) to be logged otherwise +| your log files will fill up very fast. +| +*/ +$config['log_threshold'] = 0; + +/* +|-------------------------------------------------------------------------- +| Error Logging Directory Path +|-------------------------------------------------------------------------- +| +| Leave this BLANK unless you would like to set something other than the default +| application/logs/ directory. Use a full server path with trailing slash. +| +*/ +$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'] = ''; + +/* +|-------------------------------------------------------------------------- +| Log File Permissions +|-------------------------------------------------------------------------- +| +| The file system permissions to be applied on newly created log files. +| +| IMPORTANT: This MUST be an integer (no quotes) and you MUST use octal +| integer notation (i.e. 0700, 0644, etc.) +*/ +$config['log_file_permissions'] = 0644; + +/* +|-------------------------------------------------------------------------- +| Date Format for Logs +|-------------------------------------------------------------------------- +| +| Each item that is logged has an associated date. You can use PHP date +| codes to set your own date formatting +| +*/ +$config['log_date_format'] = 'Y-m-d H:i:s'; + +/* +|-------------------------------------------------------------------------- +| Error Views Directory Path +|-------------------------------------------------------------------------- +| +| Leave this BLANK unless you would like to set something other than the default +| application/views/errors/ directory. Use a full server path with trailing slash. +| +*/ +$config['error_views_path'] = ''; + +/* +|-------------------------------------------------------------------------- +| Cache Directory Path +|-------------------------------------------------------------------------- +| +| Leave this BLANK unless you would like to set something other than the default +| application/cache/ directory. Use a full server path with trailing slash. +| +*/ +$config['cache_path'] = ''; + +/* +|-------------------------------------------------------------------------- +| Cache Include Query String +|-------------------------------------------------------------------------- +| +| Whether to take the URL query string into consideration when generating +| output cache files. Valid options are: +| +| FALSE = Disabled +| TRUE = Enabled, take all query parameters into account. +| Please be aware that this may result in numerous cache +| files generated for the same page over and over again. +| array('q') = Enabled, but only take into account the specified list +| of query parameters. +| +*/ +$config['cache_query_string'] = FALSE; + +/* +|-------------------------------------------------------------------------- +| Encryption Key +|-------------------------------------------------------------------------- +| +| If you use the Encryption class, you must set an encryption key. +| See the user guide for more info. +| +| https://codeigniter.com/user_guide/libraries/encryption.html +| +*/ +$config['encryption_key'] = ''; + +/* +|-------------------------------------------------------------------------- +| Session Variables +|-------------------------------------------------------------------------- +| +| 'sess_driver' +| +| The storage driver to use: files, database, redis, memcached +| +| 'sess_cookie_name' +| +| The session cookie name, must contain only [0-9a-z_-] characters +| +| 'sess_expiration' +| +| The number of SECONDS you want the session to last. +| Setting to 0 (zero) means expire when the browser is closed. +| +| 'sess_save_path' +| +| The location to save sessions to, driver dependent. +| +| For the 'files' driver, it's a path to a writable directory. +| WARNING: Only absolute paths are supported! +| +| For the 'database' driver, it's a table name. +| Please read up the manual for the format with other session drivers. +| +| IMPORTANT: You are REQUIRED to set a valid save path! +| +| 'sess_match_ip' +| +| Whether to match the user's IP address when reading the session data. +| +| WARNING: If you're using the database driver, don't forget to update +| your session table's PRIMARY KEY when changing this setting. +| +| 'sess_time_to_update' +| +| How many seconds between CI regenerating the session ID. +| +| 'sess_regenerate_destroy' +| +| Whether to destroy session data associated with the old session ID +| when auto-regenerating the session ID. When set to FALSE, the data +| will be later deleted by the garbage collector. +| +| Other session cookie settings are shared with the rest of the application, +| except for 'cookie_prefix' and 'cookie_httponly', which are ignored here. +| +*/ +$config['sess_driver'] = 'files'; +$config['sess_cookie_name'] = 'ci_session'; +$config['sess_expiration'] = 7200; +$config['sess_save_path'] = NULL; +$config['sess_match_ip'] = FALSE; +$config['sess_time_to_update'] = 300; +$config['sess_regenerate_destroy'] = FALSE; + +/* +|-------------------------------------------------------------------------- +| Cookie Related Variables +|-------------------------------------------------------------------------- +| +| 'cookie_prefix' = Set a cookie name prefix if you need to avoid collisions +| 'cookie_domain' = Set to .your-domain.com for site-wide cookies +| 'cookie_path' = Typically will be a forward slash +| 'cookie_secure' = Cookie will only be set if a secure HTTPS connection exists. +| 'cookie_httponly' = Cookie will only be accessible via HTTP(S) (no javascript) +| +| Note: These settings (with the exception of 'cookie_prefix' and +| 'cookie_httponly') will also affect sessions. +| +*/ +$config['cookie_prefix'] = ''; +$config['cookie_domain'] = ''; +$config['cookie_path'] = '/'; +$config['cookie_secure'] = FALSE; +$config['cookie_httponly'] = FALSE; + +/* +|-------------------------------------------------------------------------- +| Standardize newlines +|-------------------------------------------------------------------------- +| +| Determines whether to standardize newline characters in input data, +| meaning to replace \r\n, \r, \n occurrences with the PHP_EOL value. +| +| WARNING: This feature is DEPRECATED and currently available only +| for backwards compatibility purposes! +| +*/ +$config['standardize_newlines'] = FALSE; + +/* +|-------------------------------------------------------------------------- +| Global XSS Filtering +|-------------------------------------------------------------------------- +| +| Determines whether the XSS filter is always active when GET, POST or +| COOKIE data is encountered +| +| WARNING: This feature is DEPRECATED and currently available only +| for backwards compatibility purposes! +| +*/ +$config['global_xss_filtering'] = FALSE; + +/* +|-------------------------------------------------------------------------- +| Cross Site Request Forgery +|-------------------------------------------------------------------------- +| Enables a CSRF cookie token to be set. When set to TRUE, token will be +| checked on a submitted form. If you are accepting user data, it is strongly +| recommended CSRF protection be enabled. +| +| 'csrf_token_name' = The token name +| 'csrf_cookie_name' = The cookie name +| 'csrf_expire' = The number in seconds the token should expire. +| 'csrf_regenerate' = Regenerate token on every submission +| 'csrf_exclude_uris' = Array of URIs which ignore CSRF checks +*/ +$config['csrf_protection'] = FALSE; +$config['csrf_token_name'] = 'csrf_test_name'; +$config['csrf_cookie_name'] = 'csrf_cookie_name'; +$config['csrf_expire'] = 7200; +$config['csrf_regenerate'] = TRUE; +$config['csrf_exclude_uris'] = array(); + +/* +|-------------------------------------------------------------------------- +| Output Compression +|-------------------------------------------------------------------------- +| +| Enables Gzip output compression for faster page loads. When enabled, +| the output class will test whether your server supports Gzip. +| Even if it does, however, not all browsers support compression +| so enable only if you are reasonably sure your visitors can handle it. +| +| Only used if zlib.output_compression is turned off in your php.ini. +| Please do not use it together with httpd-level output compression. +| +| VERY IMPORTANT: If you are getting a blank page when compression is enabled it +| means you are prematurely outputting something to your browser. It could +| even be a line of whitespace at the end of one of your scripts. For +| compression to work, nothing can be sent before the output buffer is called +| by the output class. Do not 'echo' any values with compression enabled. +| +*/ +$config['compress_output'] = FALSE; + +/* +|-------------------------------------------------------------------------- +| Master Time Reference +|-------------------------------------------------------------------------- +| +| Options are 'local' or any PHP supported timezone. This preference tells +| the system whether to use your server's local time as the master 'now' +| reference, or convert it to the configured one timezone. See the 'date +| helper' page of the user guide for information regarding date handling. +| +*/ +$config['time_reference'] = 'local'; + +/* +|-------------------------------------------------------------------------- +| Rewrite PHP Short Tags +|-------------------------------------------------------------------------- +| +| If your PHP installation does not have short tag support enabled CI +| can rewrite the tags on-the-fly, enabling you to utilize that syntax +| in your view files. Options are TRUE or FALSE (boolean) +| +| Note: You need to have eval() enabled for this to work. +| +*/ +$config['rewrite_short_tags'] = FALSE; + +/* +|-------------------------------------------------------------------------- +| Reverse Proxy IPs +|-------------------------------------------------------------------------- +| +| 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'] = ''; diff --git a/application/config/constants.php b/application/config/constants.php new file mode 100644 index 0000000..18d3b4b --- /dev/null +++ b/application/config/constants.php @@ -0,0 +1,85 @@ +db->last_query() and profiling of DB queries. +| When you run a query, with this setting set to TRUE (default), +| CodeIgniter will store the SQL statement for debugging purposes. +| However, this may cause high memory usage, especially if you run +| a lot of SQL queries ... disable this to avoid that problem. +| +| The $active_group variable lets you choose which connection group to +| 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. +*/ +$active_group = 'default'; +$query_builder = TRUE; + +$db['default'] = array( + 'dsn' => '', + 'hostname' => 'localhost', + 'username' => 'root', + 'password' => '', + 'database' => 'restuser', + 'dbdriver' => 'mysqli', + 'dbprefix' => '', + 'pconnect' => FALSE, + 'db_debug' => (ENVIRONMENT !== 'production'), + 'cache_on' => FALSE, + 'cachedir' => '', + 'char_set' => 'utf8', + 'dbcollat' => 'utf8_general_ci', + 'swap_pre' => '', + 'encrypt' => FALSE, + 'compress' => FALSE, + 'stricton' => FALSE, + 'failover' => array(), + 'save_queries' => TRUE +); diff --git a/application/config/doctypes.php b/application/config/doctypes.php new file mode 100644 index 0000000..59a7991 --- /dev/null +++ b/application/config/doctypes.php @@ -0,0 +1,24 @@ + '', + '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' => '' +); diff --git a/application/config/foreign_chars.php b/application/config/foreign_chars.php new file mode 100644 index 0000000..995f483 --- /dev/null +++ b/application/config/foreign_chars.php @@ -0,0 +1,103 @@ + 'ae', + '/ö|œ/' => 'oe', + '/ü/' => 'ue', + '/Ä/' => 'Ae', + '/Ü/' => 'Ue', + '/Ö/' => 'Oe', + '/À|Á|Â|Ã|Ä|Å|Ǻ|Ā|Ă|Ą|Ǎ|Α|Ά|Ả|Ạ|Ầ|Ẫ|Ẩ|Ậ|Ằ|Ắ|Ẵ|Ẳ|Ặ|А/' => 'A', + '/à|á|â|ã|å|ǻ|ā|ă|ą|ǎ|ª|α|ά|ả|ạ|ầ|ấ|ẫ|ẩ|ậ|ằ|ắ|ẵ|ẳ|ặ|а/' => 'a', + '/Б/' => 'B', + '/б/' => 'b', + '/Ç|Ć|Ĉ|Ċ|Č/' => 'C', + '/ç|ć|ĉ|ċ|č/' => 'c', + '/Д/' => 'D', + '/д/' => 'd', + '/Ð|Ď|Đ|Δ/' => 'Dj', + '/ð|ď|đ|δ/' => 'dj', + '/È|É|Ê|Ë|Ē|Ĕ|Ė|Ę|Ě|Ε|Έ|Ẽ|Ẻ|Ẹ|Ề|Ế|Ễ|Ể|Ệ|Е|Э/' => 'E', + '/è|é|ê|ë|ē|ĕ|ė|ę|ě|έ|ε|ẽ|ẻ|ẹ|ề|ế|ễ|ể|ệ|е|э/' => 'e', + '/Ф/' => 'F', + '/ф/' => 'f', + '/Ĝ|Ğ|Ġ|Ģ|Γ|Г|Ґ/' => 'G', + '/ĝ|ğ|ġ|ģ|γ|г|ґ/' => 'g', + '/Ĥ|Ħ/' => 'H', + '/ĥ|ħ/' => 'h', + '/Ì|Í|Î|Ï|Ĩ|Ī|Ĭ|Ǐ|Į|İ|Η|Ή|Ί|Ι|Ϊ|Ỉ|Ị|И|Ы/' => 'I', + '/ì|í|î|ï|ĩ|ī|ĭ|ǐ|į|ı|η|ή|ί|ι|ϊ|ỉ|ị|и|ы|ї/' => 'i', + '/Ĵ/' => 'J', + '/ĵ/' => 'j', + '/Ķ|Κ|К/' => 'K', + '/ķ|κ|к/' => 'k', + '/Ĺ|Ļ|Ľ|Ŀ|Ł|Λ|Л/' => 'L', + '/ĺ|ļ|ľ|ŀ|ł|λ|л/' => 'l', + '/М/' => 'M', + '/м/' => 'm', + '/Ñ|Ń|Ņ|Ň|Ν|Н/' => 'N', + '/ñ|ń|ņ|ň|ʼn|ν|н/' => 'n', + '/Ò|Ó|Ô|Õ|Ō|Ŏ|Ǒ|Ő|Ơ|Ø|Ǿ|Ο|Ό|Ω|Ώ|Ỏ|Ọ|Ồ|Ố|Ỗ|Ổ|Ộ|Ờ|Ớ|Ỡ|Ở|Ợ|О/' => 'O', + '/ò|ó|ô|õ|ō|ŏ|ǒ|ő|ơ|ø|ǿ|º|ο|ό|ω|ώ|ỏ|ọ|ồ|ố|ỗ|ổ|ộ|ờ|ớ|ỡ|ở|ợ|о/' => 'o', + '/П/' => 'P', + '/п/' => 'p', + '/Ŕ|Ŗ|Ř|Ρ|Р/' => 'R', + '/ŕ|ŗ|ř|ρ|р/' => 'r', + '/Ś|Ŝ|Ş|Ș|Š|Σ|С/' => 'S', + '/ś|ŝ|ş|ș|š|ſ|σ|ς|с/' => 's', + '/Ț|Ţ|Ť|Ŧ|τ|Т/' => 'T', + '/ț|ţ|ť|ŧ|т/' => 't', + '/Þ|þ/' => 'th', + '/Ù|Ú|Û|Ũ|Ū|Ŭ|Ů|Ű|Ų|Ư|Ǔ|Ǖ|Ǘ|Ǚ|Ǜ|Ũ|Ủ|Ụ|Ừ|Ứ|Ữ|Ử|Ự|У/' => 'U', + '/ù|ú|û|ũ|ū|ŭ|ů|ű|ų|ư|ǔ|ǖ|ǘ|ǚ|ǜ|υ|ύ|ϋ|ủ|ụ|ừ|ứ|ữ|ử|ự|у/' => 'u', + '/Ƴ|Ɏ|Ỵ|Ẏ|Ӳ|Ӯ|Ў|Ý|Ÿ|Ŷ|Υ|Ύ|Ϋ|Ỳ|Ỹ|Ỷ|Ỵ|Й/' => 'Y', + '/ẙ|ʏ|ƴ|ɏ|ỵ|ẏ|ӳ|ӯ|ў|ý|ÿ|ŷ|ỳ|ỹ|ỷ|ỵ|й/' => 'y', + '/В/' => 'V', + '/в/' => 'v', + '/Ŵ/' => 'W', + '/ŵ/' => 'w', + '/Ź|Ż|Ž|Ζ|З/' => 'Z', + '/ź|ż|ž|ζ|з/' => 'z', + '/Æ|Ǽ/' => 'AE', + '/ß/' => 'ss', + '/IJ/' => 'IJ', + '/ij/' => 'ij', + '/Œ/' => 'OE', + '/ƒ/' => 'f', + '/ξ/' => 'ks', + '/π/' => 'p', + '/β/' => 'v', + '/μ/' => 'm', + '/ψ/' => 'ps', + '/Ё/' => 'Yo', + '/ё/' => 'yo', + '/Є/' => 'Ye', + '/є/' => 'ye', + '/Ї/' => 'Yi', + '/Ж/' => 'Zh', + '/ж/' => 'zh', + '/Х/' => 'Kh', + '/х/' => 'kh', + '/Ц/' => 'Ts', + '/ц/' => 'ts', + '/Ч/' => 'Ch', + '/ч/' => 'ch', + '/Ш/' => 'Sh', + '/ш/' => 'sh', + '/Щ/' => 'Shch', + '/щ/' => 'shch', + '/Ъ|ъ|Ь|ь/' => '', + '/Ю/' => 'Yu', + '/ю/' => 'yu', + '/Я/' => 'Ya', + '/я/' => 'ya' +); diff --git a/application/config/hooks.php b/application/config/hooks.php new file mode 100644 index 0000000..a8f38a5 --- /dev/null +++ b/application/config/hooks.php @@ -0,0 +1,13 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/config/memcached.php b/application/config/memcached.php new file mode 100644 index 0000000..5c23b39 --- /dev/null +++ b/application/config/memcached.php @@ -0,0 +1,19 @@ + array( + 'hostname' => '127.0.0.1', + 'port' => '11211', + 'weight' => '1', + ), +); diff --git a/application/config/migration.php b/application/config/migration.php new file mode 100644 index 0000000..4b585a6 --- /dev/null +++ b/application/config/migration.php @@ -0,0 +1,84 @@ +migration->current() this is the version that schema will +| be upgraded / downgraded to. +| +*/ +$config['migration_version'] = 0; + +/* +|-------------------------------------------------------------------------- +| Migrations Path +|-------------------------------------------------------------------------- +| +| Path to your migrations folder. +| Typically, it will be within your application path. +| Also, writing permission is required within the migrations path. +| +*/ +$config['migration_path'] = APPPATH.'migrations/'; diff --git a/application/config/mimes.php b/application/config/mimes.php new file mode 100644 index 0000000..0ec9db0 --- /dev/null +++ b/application/config/mimes.php @@ -0,0 +1,184 @@ + array('application/mac-binhex40', 'application/mac-binhex', 'application/x-binhex40', 'application/x-mac-binhex40'), + 'cpt' => 'application/mac-compactpro', + 'csv' => array('text/x-comma-separated-values', 'text/comma-separated-values', 'application/octet-stream', 'application/vnd.ms-excel', 'application/x-csv', 'text/x-csv', 'text/csv', 'application/csv', 'application/excel', 'application/vnd.msexcel', 'text/plain'), + 'bin' => array('application/macbinary', 'application/mac-binary', 'application/octet-stream', 'application/x-binary', 'application/x-macbinary'), + 'dms' => 'application/octet-stream', + 'lha' => 'application/octet-stream', + 'lzh' => 'application/octet-stream', + 'exe' => array('application/octet-stream', 'application/x-msdownload'), + 'class' => 'application/octet-stream', + '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/force-download', 'application/x-download', 'binary/octet-stream'), + 'ai' => array('application/pdf', 'application/postscript'), + 'eps' => 'application/postscript', + 'ps' => 'application/postscript', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + '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', 'application/vnd.ms-office', 'application/msword'), + 'pptx' => array('application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/x-zip', 'application/zip'), + 'wbxml' => 'application/wbxml', + 'wmlc' => 'application/wmlc', + 'dcr' => 'application/x-director', + 'dir' => 'application/x-director', + 'dxr' => 'application/x-director', + 'dvi' => 'application/x-dvi', + 'gtar' => 'application/x-gtar', + 'gz' => 'application/x-gzip', + 'gzip' => 'application/x-gzip', + 'php' => array('application/x-httpd-php', 'application/php', 'application/x-php', 'text/php', 'text/x-php', 'application/x-httpd-php-source'), + 'php4' => 'application/x-httpd-php', + 'php3' => 'application/x-httpd-php', + 'phtml' => 'application/x-httpd-php', + 'phps' => 'application/x-httpd-php-source', + 'js' => array('application/x-javascript', 'text/plain'), + 'swf' => 'application/x-shockwave-flash', + 'sit' => 'application/x-stuffit', + 'tar' => 'application/x-tar', + 'tgz' => array('application/x-tar', 'application/x-gzip-compressed'), + 'z' => 'application/x-compress', + 'xhtml' => 'application/xhtml+xml', + 'xht' => 'application/xhtml+xml', + 'zip' => array('application/x-zip', 'application/zip', 'application/x-zip-compressed', 'application/s-compressed', 'multipart/x-zip'), + 'rar' => array('application/x-rar', 'application/rar', 'application/x-rar-compressed'), + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mpga' => 'audio/mpeg', + 'mp2' => 'audio/mpeg', + 'mp3' => array('audio/mpeg', 'audio/mpg', 'audio/mpeg3', 'audio/mp3'), + 'aif' => array('audio/x-aiff', 'audio/aiff'), + 'aiff' => array('audio/x-aiff', 'audio/aiff'), + 'aifc' => 'audio/x-aiff', + 'ram' => 'audio/x-pn-realaudio', + 'rm' => 'audio/x-pn-realaudio', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'ra' => 'audio/x-realaudio', + 'rv' => 'video/vnd.rn-realvideo', + 'wav' => array('audio/x-wav', 'audio/wave', 'audio/wav'), + 'bmp' => array('image/bmp', 'image/x-bmp', 'image/x-bitmap', 'image/x-xbitmap', 'image/x-win-bitmap', 'image/x-windows-bmp', 'image/ms-bmp', 'image/x-ms-bmp', 'application/bmp', 'application/x-bmp', 'application/x-win-bitmap'), + 'gif' => 'image/gif', + 'jpeg' => array('image/jpeg', 'image/pjpeg'), + 'jpg' => array('image/jpeg', 'image/pjpeg'), + 'jpe' => array('image/jpeg', 'image/pjpeg'), + 'jp2' => array('image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'), + 'j2k' => array('image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'), + 'jpf' => array('image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'), + 'jpg2' => array('image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'), + 'jpx' => array('image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'), + 'jpm' => array('image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'), + 'mj2' => array('image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'), + 'mjp2' => array('image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'), + 'png' => array('image/png', 'image/x-png'), + 'tiff' => 'image/tiff', + 'tif' => 'image/tiff', + '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', 'text/plain'), + 'xsl' => array('application/xml', 'text/xsl', 'text/xml'), + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpe' => 'video/mpeg', + 'qt' => 'video/quicktime', + 'mov' => 'video/quicktime', + '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', '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', + 'json' => array('application/json', 'text/json'), + 'pem' => array('application/x-x509-user-cert', 'application/x-pem-file', 'application/octet-stream'), + 'p10' => array('application/x-pkcs10', 'application/pkcs10'), + 'p12' => 'application/x-pkcs12', + 'p7a' => 'application/x-pkcs7-signature', + 'p7c' => array('application/pkcs7-mime', 'application/x-pkcs7-mime'), + 'p7m' => array('application/pkcs7-mime', 'application/x-pkcs7-mime'), + 'p7r' => 'application/x-pkcs7-certreqresp', + 'p7s' => 'application/pkcs7-signature', + 'crt' => array('application/x-x509-ca-cert', 'application/x-x509-user-cert', 'application/pkix-cert'), + 'crl' => array('application/pkix-crl', 'application/pkcs-crl'), + 'der' => 'application/x-x509-ca-cert', + 'kdb' => 'application/octet-stream', + 'pgp' => 'application/pgp', + 'gpg' => 'application/gpg-keys', + 'sst' => 'application/octet-stream', + 'csr' => 'application/octet-stream', + 'rsa' => 'application/x-pkcs7', + 'cer' => array('application/pkix-cert', 'application/x-x509-ca-cert'), + '3g2' => 'video/3gpp2', + '3gp' => array('video/3gp', 'video/3gpp'), + 'mp4' => 'video/mp4', + 'm4a' => 'audio/x-m4a', + 'f4v' => array('video/mp4', 'video/x-f4v'), + 'flv' => 'video/x-flv', + 'webm' => 'video/webm', + 'aac' => 'audio/x-acc', + 'm4u' => 'application/vnd.mpegurl', + 'm3u' => 'text/plain', + 'xspf' => 'application/xspf+xml', + 'vlc' => 'application/videolan', + 'wmv' => array('video/x-ms-wmv', 'video/x-ms-asf'), + 'au' => 'audio/x-au', + 'ac3' => 'audio/ac3', + 'flac' => 'audio/x-flac', + 'ogg' => array('audio/ogg', 'video/ogg', 'application/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', + 'ical' => 'text/calendar', + 'zsh' => 'text/x-scriptzsh', + '7z' => array('application/x-7z-compressed', 'application/x-compressed', 'application/x-zip-compressed', 'application/zip', 'multipart/x-zip'), + '7zip' => array('application/x-7z-compressed', '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'), + 'svg' => array('image/svg+xml', 'application/xml', 'text/xml'), + 'vcf' => 'text/x-vcard', + 'srt' => array('text/srt', 'text/plain'), + 'vtt' => array('text/vtt', 'text/plain'), + 'ico' => array('image/x-icon', 'image/x-ico', 'image/vnd.microsoft.icon'), + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'otc' => 'application/vnd.oasis.opendocument.chart-template', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'otf' => 'application/vnd.oasis.opendocument.formula-template', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'oti' => 'application/vnd.oasis.opendocument.image-template', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'odm' => 'application/vnd.oasis.opendocument.text-master', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + 'oth' => 'application/vnd.oasis.opendocument.text-web' +); diff --git a/application/config/profiler.php b/application/config/profiler.php new file mode 100644 index 0000000..3db22e3 --- /dev/null +++ b/application/config/profiler.php @@ -0,0 +1,14 @@ + my_controller/index +| my-controller/my-method -> my_controller/my_method +*/ +$route['default_controller'] = 'welcome'; +$route['404_override'] = ''; +$route['translate_uri_dashes'] = FALSE; diff --git a/application/config/smileys.php b/application/config/smileys.php new file mode 100644 index 0000000..abf9a89 --- /dev/null +++ b/application/config/smileys.php @@ -0,0 +1,64 @@ + array('grin.gif', '19', '19', 'grin'), + ':lol:' => array('lol.gif', '19', '19', 'LOL'), + ':cheese:' => array('cheese.gif', '19', '19', 'cheese'), + ':)' => array('smile.gif', '19', '19', 'smile'), + ';-)' => array('wink.gif', '19', '19', 'wink'), + ';)' => array('wink.gif', '19', '19', 'wink'), + ':smirk:' => array('smirk.gif', '19', '19', 'smirk'), + ':roll:' => array('rolleyes.gif', '19', '19', 'rolleyes'), + ':-S' => array('confused.gif', '19', '19', 'confused'), + ':wow:' => array('surprise.gif', '19', '19', 'surprised'), + ':bug:' => array('bigsurprise.gif', '19', '19', 'big surprise'), + ':-P' => array('tongue_laugh.gif', '19', '19', 'tongue laugh'), + '%-P' => array('tongue_rolleye.gif', '19', '19', 'tongue rolleye'), + ';-P' => array('tongue_wink.gif', '19', '19', 'tongue wink'), + ':P' => array('raspberry.gif', '19', '19', 'raspberry'), + ':blank:' => array('blank.gif', '19', '19', 'blank stare'), + ':long:' => array('longface.gif', '19', '19', 'long face'), + ':ohh:' => array('ohh.gif', '19', '19', 'ohh'), + ':grrr:' => array('grrr.gif', '19', '19', 'grrr'), + ':gulp:' => array('gulp.gif', '19', '19', 'gulp'), + '8-/' => array('ohoh.gif', '19', '19', 'oh oh'), + ':down:' => array('downer.gif', '19', '19', 'downer'), + ':red:' => array('embarrassed.gif', '19', '19', 'red face'), + ':sick:' => array('sick.gif', '19', '19', 'sick'), + ':shut:' => array('shuteye.gif', '19', '19', 'shut eye'), + ':-/' => array('hmm.gif', '19', '19', 'hmmm'), + '>:(' => array('mad.gif', '19', '19', 'mad'), + ':mad:' => array('mad.gif', '19', '19', 'mad'), + '>:-(' => array('angry.gif', '19', '19', 'angry'), + ':angry:' => array('angry.gif', '19', '19', 'angry'), + ':zip:' => array('zip.gif', '19', '19', 'zipper'), + ':kiss:' => array('kiss.gif', '19', '19', 'kiss'), + ':ahhh:' => array('shock.gif', '19', '19', 'shock'), + ':coolsmile:' => array('shade_smile.gif', '19', '19', 'cool smile'), + ':coolsmirk:' => array('shade_smirk.gif', '19', '19', 'cool smirk'), + ':coolgrin:' => array('shade_grin.gif', '19', '19', 'cool grin'), + ':coolhmm:' => array('shade_hmm.gif', '19', '19', 'cool hmm'), + ':coolmad:' => array('shade_mad.gif', '19', '19', 'cool mad'), + ':coolcheese:' => array('shade_cheese.gif', '19', '19', 'cool cheese'), + ':vampire:' => array('vampire.gif', '19', '19', 'vampire'), + ':snake:' => array('snake.gif', '19', '19', 'snake'), + ':exclaim:' => array('exclaim.gif', '19', '19', 'exclaim'), + ':question:' => array('question.gif', '19', '19', 'question') + +); diff --git a/application/config/user_agents.php b/application/config/user_agents.php new file mode 100644 index 0000000..b6c8563 --- /dev/null +++ b/application/config/user_agents.php @@ -0,0 +1,214 @@ + 'Windows 10', + 'windows nt 6.3' => 'Windows 8.1', + 'windows nt 6.2' => 'Windows 8', + 'windows nt 6.1' => 'Windows 7', + 'windows nt 6.0' => 'Windows Vista', + 'windows nt 5.2' => 'Windows 2003', + 'windows nt 5.1' => 'Windows XP', + 'windows nt 5.0' => 'Windows 2000', + 'windows nt 4.0' => 'Windows NT 4.0', + 'winnt4.0' => 'Windows NT 4.0', + 'winnt 4.0' => 'Windows NT', + 'winnt' => 'Windows NT', + 'windows 98' => 'Windows 98', + 'win98' => 'Windows 98', + 'windows 95' => 'Windows 95', + 'win95' => 'Windows 95', + 'windows phone' => 'Windows Phone', + 'windows' => 'Unknown Windows OS', + 'android' => 'Android', + 'blackberry' => 'BlackBerry', + 'iphone' => 'iOS', + 'ipad' => 'iOS', + 'ipod' => 'iOS', + 'os x' => 'Mac OS X', + 'ppc mac' => 'Power PC Mac', + 'freebsd' => 'FreeBSD', + 'ppc' => 'Macintosh', + 'linux' => 'Linux', + 'debian' => 'Debian', + 'sunos' => 'Sun Solaris', + 'beos' => 'BeOS', + 'apachebench' => 'ApacheBench', + 'aix' => 'AIX', + 'irix' => 'Irix', + 'osf' => 'DEC OSF', + 'hp-ux' => 'HP-UX', + 'netbsd' => 'NetBSD', + 'bsdi' => 'BSDi', + 'openbsd' => 'OpenBSD', + 'gnu' => 'GNU/Linux', + 'unix' => 'Unknown Unix OS', + 'symbian' => 'Symbian OS' +); + + +// The order of this array should NOT be changed. Many browsers return +// multiple browser types so we want to identify the sub-type first. +$browsers = array( + 'OPR' => 'Opera', + 'Flock' => 'Flock', + 'Edge' => 'Edge', + 'Chrome' => 'Chrome', + // Opera 10+ always reports Opera/9.80 and appends Version/Directory access is forbidden.
+ + + diff --git a/application/controllers/index.html b/application/controllers/index.html new file mode 100644 index 0000000..b702fbc --- /dev/null +++ b/application/controllers/index.html @@ -0,0 +1,11 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/core/index.html b/application/core/index.html new file mode 100644 index 0000000..b702fbc --- /dev/null +++ b/application/core/index.html @@ -0,0 +1,11 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/helpers/db_helper.php b/application/helpers/db_helper.php new file mode 100644 index 0000000..9a007a4 --- /dev/null +++ b/application/helpers/db_helper.php @@ -0,0 +1,69 @@ + + */ +defined('BASEPATH') OR exit('No direct script access allowed'); + +if ( ! function_exists('add_foreign_key')) +{ + /** + * @param string $table Table name + * @param string $foreign_key Collumn name having the Foreign Key + * @param string $references Table and column reference. Ex: users(id) + * @param string $on_delete RESTRICT, NO ACTION, CASCADE, SET NULL, SET DEFAULT + * @param string $on_update RESTRICT, NO ACTION, CASCADE, SET NULL, SET DEFAULT + * + * @return string SQL command + */ + function add_foreign_key($table, $foreign_key, $references, $on_delete = 'RESTRICT', $on_update = 'RESTRICT') + { + $references = explode('(', str_replace(')', '', str_replace('`', '', $references))); + + return "ALTER TABLE `{$table}` ADD CONSTRAINT `{$table}_{$foreign_key}_fk` FOREIGN KEY (`{$foreign_key}`) REFERENCES `{$references[0]}`(`{$references[1]}`) ON DELETE {$on_delete} ON UPDATE {$on_update}"; + } +} + +if ( ! function_exists('drop_foreign_key')) +{ + /** + * @param string $table Table name + * @param string $foreign_key Collumn name having the Foreign Key + * + * @return string SQL command + */ + function drop_foreign_key($table, $foreign_key) + { + return "ALTER TABLE `{$table}` DROP FOREIGN KEY `{$table}_{$foreign_key}_fk`"; + } +} + +if ( ! function_exists('add_trigger')) +{ + /** + * @param string $trigger_name Trigger name + * @param string $table Table name + * @param string $statement Command to run + * @param string $time BEFORE or AFTER + * @param string $event INSERT, UPDATE or DELETE + * @param string $type FOR EACH ROW [FOLLOWS|PRECEDES] + * + * @return string SQL Command + */ + function add_trigger($trigger_name, $table, $statement, $time = 'BEFORE', $event = 'INSERT', $type = 'FOR EACH ROW') + { + return 'DELIMITER ;;' . PHP_EOL . "CREATE TRIGGER `{$trigger_name}` {$time} {$event} ON `{$table}` {$type}" . PHP_EOL . 'BEGIN' . PHP_EOL . $statement . PHP_EOL . 'END;' . PHP_EOL . 'DELIMITER ;;'; + } +} + +if ( ! function_exists('drop_trigger')) +{ + /** + * @param string $trigger_name Trigger name + * + * @return string SQL Command + */ + function drop_trigger($trigger_name) + { + return "DROP TRIGGER {$trigger_name};"; + } +} diff --git a/application/helpers/index.html b/application/helpers/index.html new file mode 100644 index 0000000..b702fbc --- /dev/null +++ b/application/helpers/index.html @@ -0,0 +1,11 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/hooks/hooks.profiler.php b/application/hooks/hooks.profiler.php new file mode 100644 index 0000000..c376fdb --- /dev/null +++ b/application/hooks/hooks.profiler.php @@ -0,0 +1,18 @@ +output->enable_profiler( config_item('enable_profiling') ); + } +} +?> diff --git a/application/hooks/index.html b/application/hooks/index.html new file mode 100644 index 0000000..b702fbc --- /dev/null +++ b/application/hooks/index.html @@ -0,0 +1,11 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/index.html b/application/index.html new file mode 100644 index 0000000..b702fbc --- /dev/null +++ b/application/index.html @@ -0,0 +1,11 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/language/bulgarian/index.html b/application/language/bulgarian/index.html new file mode 100644 index 0000000..b702fbc --- /dev/null +++ b/application/language/bulgarian/index.html @@ -0,0 +1,11 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/language/bulgarian/rest_controller_lang.php b/application/language/bulgarian/rest_controller_lang.php new file mode 100644 index 0000000..4ba134d --- /dev/null +++ b/application/language/bulgarian/rest_controller_lang.php @@ -0,0 +1,18 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/language/dutch/rest_controller_lang.php b/application/language/dutch/rest_controller_lang.php new file mode 100644 index 0000000..182ca61 --- /dev/null +++ b/application/language/dutch/rest_controller_lang.php @@ -0,0 +1,16 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/language/english/rest_controller_lang.php b/application/language/english/rest_controller_lang.php new file mode 100644 index 0000000..06bf4b9 --- /dev/null +++ b/application/language/english/rest_controller_lang.php @@ -0,0 +1,18 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/language/french/rest_controller_lang.php b/application/language/french/rest_controller_lang.php new file mode 100644 index 0000000..f8c0d13 --- /dev/null +++ b/application/language/french/rest_controller_lang.php @@ -0,0 +1,18 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/language/german/rest_controller_lang.php b/application/language/german/rest_controller_lang.php new file mode 100644 index 0000000..4230c3c --- /dev/null +++ b/application/language/german/rest_controller_lang.php @@ -0,0 +1,18 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/language/indonesia/index.html b/application/language/indonesia/index.html new file mode 100644 index 0000000..b702fbc --- /dev/null +++ b/application/language/indonesia/index.html @@ -0,0 +1,11 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/language/indonesia/rest_controller_lang.php b/application/language/indonesia/rest_controller_lang.php new file mode 100644 index 0000000..771c683 --- /dev/null +++ b/application/language/indonesia/rest_controller_lang.php @@ -0,0 +1,18 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/language/italian/rest_controller_lang.php b/application/language/italian/rest_controller_lang.php new file mode 100644 index 0000000..783f16a --- /dev/null +++ b/application/language/italian/rest_controller_lang.php @@ -0,0 +1,16 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/language/korean/rest_controller_lang.php b/application/language/korean/rest_controller_lang.php new file mode 100644 index 0000000..df6f9b4 --- /dev/null +++ b/application/language/korean/rest_controller_lang.php @@ -0,0 +1,16 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/language/portuguese-brazilian/rest_controller_lang.php b/application/language/portuguese-brazilian/rest_controller_lang.php new file mode 100644 index 0000000..10c164c --- /dev/null +++ b/application/language/portuguese-brazilian/rest_controller_lang.php @@ -0,0 +1,18 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/language/romanian/rest_controller_lang.php b/application/language/romanian/rest_controller_lang.php new file mode 100644 index 0000000..f151d52 --- /dev/null +++ b/application/language/romanian/rest_controller_lang.php @@ -0,0 +1,18 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/language/serbian_cyr/rest_controller_lang.php b/application/language/serbian_cyr/rest_controller_lang.php new file mode 100644 index 0000000..e83ff4d --- /dev/null +++ b/application/language/serbian_cyr/rest_controller_lang.php @@ -0,0 +1,18 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/language/serbian_lat/rest_controller_lang.php b/application/language/serbian_lat/rest_controller_lang.php new file mode 100644 index 0000000..6046788 --- /dev/null +++ b/application/language/serbian_lat/rest_controller_lang.php @@ -0,0 +1,18 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/language/simplified-chinese/rest_controller_lang.php b/application/language/simplified-chinese/rest_controller_lang.php new file mode 100644 index 0000000..f32e9e7 --- /dev/null +++ b/application/language/simplified-chinese/rest_controller_lang.php @@ -0,0 +1,18 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/language/spanish/rest_controller_lang.php b/application/language/spanish/rest_controller_lang.php new file mode 100644 index 0000000..d96c62b --- /dev/null +++ b/application/language/spanish/rest_controller_lang.php @@ -0,0 +1,18 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/language/traditional-chinese/rest_controller_lang.php b/application/language/traditional-chinese/rest_controller_lang.php new file mode 100644 index 0000000..a8450f2 --- /dev/null +++ b/application/language/traditional-chinese/rest_controller_lang.php @@ -0,0 +1,18 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/language/turkish/rest_controller_lang.php b/application/language/turkish/rest_controller_lang.php new file mode 100644 index 0000000..589b28c --- /dev/null +++ b/application/language/turkish/rest_controller_lang.php @@ -0,0 +1,18 @@ +_CI = &get_instance(); + + // Load the inflector helper + $this->_CI->load->helper('inflector'); + + // If the provided data is already formatted we should probably convert it to an array + if ($from_type !== NULL) + { + if (method_exists($this, '_from_'.$from_type)) + { + $data = call_user_func([$this, '_from_'.$from_type], $data); + } + else + { + throw new Exception('Format class does not support conversion from "'.$from_type.'".'); + } + } + + // Set the member variable to the data passed + $this->_data = $data; + } + + /** + * Create an instance of the format class + * e.g: echo $this->format->factory(['foo' => 'bar'])->to_csv(); + * + * @param mixed $data Data to convert/parse + * @param string $from_type Type to convert from e.g. json, csv, html + * + * @return object Instance of the format class + */ + public static function factory($data, $from_type = NULL) + { + // $class = __CLASS__; + // return new $class(); + + return new static($data, $from_type); + } + + // FORMATTING OUTPUT --------------------------------------------------------- + + /** + * Format data as an array + * + * @param mixed|NULL $data Optional data to pass, so as to override the data passed + * to the constructor + * @return array Data parsed as an array; otherwise, an empty array + */ + public function to_array($data = NULL) + { + // If no data is passed as a parameter, then use the data passed + // via the constructor + if ($data === NULL && func_num_args() === 0) + { + $data = $this->_data; + } + + // Cast as an array if not already + if (is_array($data) === FALSE) + { + $data = (array) $data; + } + + $array = []; + foreach ((array) $data as $key => $value) + { + if (is_object($value) === TRUE || is_array($value) === TRUE) + { + $array[$key] = $this->to_array($value); + } + else + { + $array[$key] = $value; + } + } + + return $array; + } + + /** + * Format data as XML + * + * @param mixed|NULL $data Optional data to pass, so as to override the data passed + * to the constructor + * @param NULL $structure + * @param string $basenode + * @return mixed + */ + public function to_xml($data = NULL, $structure = NULL, $basenode = 'xml') + { + if ($data === NULL && func_num_args() === 0) + { + $data = $this->_data; + } + + if ($structure === NULL) + { + $structure = simplexml_load_string("<$basenode />"); + } + + // Force it to be something useful + if (is_array($data) === FALSE && is_object($data) === FALSE) + { + $data = (array) $data; + } + + foreach ($data as $key => $value) + { + + //change false/true to 0/1 + if (is_bool($value)) + { + $value = (int) $value; + } + + // no numeric keys in our xml please! + if (is_numeric($key)) + { + // make string key... + $key = (singular($basenode) != $basenode) ? singular($basenode) : 'item'; + } + + // replace anything not alpha numeric + $key = preg_replace('/[^a-z_\-0-9]/i', '', $key); + + if ($key === '_attributes' && (is_array($value) || is_object($value))) + { + $attributes = $value; + if (is_object($attributes)) + { + $attributes = get_object_vars($attributes); + } + + foreach ($attributes as $attribute_name => $attribute_value) + { + $structure->addAttribute($attribute_name, $attribute_value); + } + } + // if there is another array found recursively call this function + elseif (is_array($value) || is_object($value)) + { + $node = $structure->addChild($key); + + // recursive call. + $this->to_xml($value, $node, $key); + } + else + { + // add single node. + $value = htmlspecialchars(html_entity_decode($value, ENT_QUOTES, 'UTF-8'), ENT_QUOTES, 'UTF-8'); + + $structure->addChild($key, $value); + } + } + + return $structure->asXML(); + } + + /** + * Format data as HTML + * + * @param mixed|NULL $data Optional data to pass, so as to override the data passed + * to the constructor + * @return mixed + */ + public function to_html($data = NULL) + { + // If no data is passed as a parameter, then use the data passed + // via the constructor + if ($data === NULL && func_num_args() === 0) + { + $data = $this->_data; + } + + // Cast as an array if not already + if (is_array($data) === FALSE) + { + $data = (array) $data; + } + + // Check if it's a multi-dimensional array + if (isset($data[0]) && count($data) !== count($data, COUNT_RECURSIVE)) + { + // Multi-dimensional array + $headings = array_keys($data[0]); + } + else + { + // Single array + $headings = array_keys($data); + $data = [$data]; + } + + // Load the table library + $this->_CI->load->library('table'); + + $this->_CI->table->set_heading($headings); + + foreach ($data as $row) + { + // Suppressing the "array to string conversion" notice + // Keep the "evil" @ here + $row = @array_map('strval', $row); + + $this->_CI->table->add_row($row); + } + + return $this->_CI->table->generate(); + } + + /** + * @link http://www.metashock.de/2014/02/create-csv-file-in-memory-php/ + * @param mixed|NULL $data Optional data to pass, so as to override the data passed + * to the constructor + * @param string $delimiter The optional delimiter parameter sets the field + * delimiter (one character only). NULL will use the default value (,) + * @param string $enclosure The optional enclosure parameter sets the field + * enclosure (one character only). NULL will use the default value (") + * @return string A csv string + */ + public function to_csv($data = NULL, $delimiter = ',', $enclosure = '"') + { + // Use a threshold of 1 MB (1024 * 1024) + $handle = fopen('php://temp/maxmemory:1048576', 'w'); + if ($handle === FALSE) + { + return NULL; + } + + // If no data is passed as a parameter, then use the data passed + // via the constructor + if ($data === NULL && func_num_args() === 0) + { + $data = $this->_data; + } + + // If NULL, then set as the default delimiter + if ($delimiter === NULL) + { + $delimiter = ','; + } + + // If NULL, then set as the default enclosure + if ($enclosure === NULL) + { + $enclosure = '"'; + } + + // Cast as an array if not already + if (is_array($data) === FALSE) + { + $data = (array) $data; + } + + // Check if it's a multi-dimensional array + if (isset($data[0]) && count($data) !== count($data, COUNT_RECURSIVE)) + { + // Multi-dimensional array + $headings = array_keys($data[0]); + } + else + { + // Single array + $headings = array_keys($data); + $data = [$data]; + } + + // Apply the headings + fputcsv($handle, $headings, $delimiter, $enclosure); + + foreach ($data as $record) + { + // If the record is not an array, then break. This is because the 2nd param of + // fputcsv() should be an array + if (is_array($record) === FALSE) + { + break; + } + + // Suppressing the "array to string conversion" notice. + // Keep the "evil" @ here. + $record = @ array_map('strval', $record); + + // Returns the length of the string written or FALSE + fputcsv($handle, $record, $delimiter, $enclosure); + } + + // Reset the file pointer + rewind($handle); + + // Retrieve the csv contents + $csv = stream_get_contents($handle); + + // Close the handle + fclose($handle); + + // Convert UTF-8 encoding to UTF-16LE which is supported by MS Excel + $csv = mb_convert_encoding($csv, 'UTF-16LE', 'UTF-8'); + + return $csv; + } + + /** + * Encode data as json + * + * @param mixed|NULL $data Optional data to pass, so as to override the data passed + * to the constructor + * @return string Json representation of a value + */ + public function to_json($data = NULL) + { + // If no data is passed as a parameter, then use the data passed + // via the constructor + if ($data === NULL && func_num_args() === 0) + { + $data = $this->_data; + } + + // Get the callback parameter (if set) + $callback = $this->_CI->input->get('callback'); + + if (empty($callback) === TRUE) + { + return json_encode($data, JSON_UNESCAPED_UNICODE); + } + + // We only honour a jsonp callback which are valid javascript identifiers + elseif (preg_match('/^[a-z_\$][a-z0-9\$_]*(\.[a-z_\$][a-z0-9\$_]*)*$/i', $callback)) + { + // Return the data as encoded json with a callback + return $callback.'('.json_encode($data, JSON_UNESCAPED_UNICODE).');'; + } + + // An invalid jsonp callback function provided. + // Though I don't believe this should be hardcoded here + $data['warning'] = 'INVALID JSONP CALLBACK: '.$callback; + + return json_encode($data, JSON_UNESCAPED_UNICODE); + } + + /** + * Encode data as a serialized array + * + * @param mixed|NULL $data Optional data to pass, so as to override the data passed + * to the constructor + * @return string Serialized data + */ + public function to_serialized($data = NULL) + { + // If no data is passed as a parameter, then use the data passed + // via the constructor + if ($data === NULL && func_num_args() === 0) + { + $data = $this->_data; + } + + return serialize($data); + } + + /** + * Format data using a PHP structure + * + * @param mixed|NULL $data Optional data to pass, so as to override the data passed + * to the constructor + * @return mixed String representation of a variable + */ + public function to_php($data = NULL) + { + // If no data is passed as a parameter, then use the data passed + // via the constructor + if ($data === NULL && func_num_args() === 0) + { + $data = $this->_data; + } + + return var_export($data, TRUE); + } + + // INTERNAL FUNCTIONS + + /** + * @param string $data XML string + * @return array XML element object; otherwise, empty array + */ + protected function _from_xml($data) + { + return $data ? (array) simplexml_load_string($data, 'SimpleXMLElement', LIBXML_NOCDATA) : []; + } + + /** + * @param string $data CSV string + * @param string $delimiter The optional delimiter parameter sets the field + * delimiter (one character only). NULL will use the default value (,) + * @param string $enclosure The optional enclosure parameter sets the field + * enclosure (one character only). NULL will use the default value (") + * @return array A multi-dimensional array with the outer array being the number of rows + * and the inner arrays the individual fields + */ + protected function _from_csv($data, $delimiter = ',', $enclosure = '"') + { + // If NULL, then set as the default delimiter + if ($delimiter === NULL) + { + $delimiter = ','; + } + + // If NULL, then set as the default enclosure + if ($enclosure === NULL) + { + $enclosure = '"'; + } + + return str_getcsv($data, $delimiter, $enclosure); + } + + /** + * @param string $data Encoded json string + * @return mixed Decoded json string with leading and trailing whitespace removed + */ + protected function _from_json($data) + { + return json_decode(trim($data)); + } + + /** + * @param string $data Data to unserialize + * @return mixed Unserialized data + */ + protected function _from_serialize($data) + { + return unserialize(trim($data)); + } + + /** + * @param string $data Data to trim leading and trailing whitespace + * @return string Data with leading and trailing whitespace removed + */ + protected function _from_php($data) + { + return trim($data); + } +} diff --git a/application/libraries/REST_Controller.php b/application/libraries/REST_Controller.php new file mode 100644 index 0000000..d325057 --- /dev/null +++ b/application/libraries/REST_Controller.php @@ -0,0 +1,2178 @@ + 'application/json', + 'array' => 'application/json', + 'csv' => 'application/csv', + 'html' => 'text/html', + 'jsonp' => 'application/javascript', + 'php' => 'text/plain', + 'serialized' => 'application/vnd.php.serialized', + 'xml' => 'application/xml' + ]; + + /** + * Information about the current API user + * + * @var object + */ + protected $_apiuser; + + /** + * Whether or not to perform a CORS check and apply CORS headers to the request + * + * @var bool + */ + protected $check_cors = NULL; + + /** + * Enable XSS flag + * Determines whether the XSS filter is always active when + * GET, OPTIONS, HEAD, POST, PUT, DELETE and PATCH data is encountered + * Set automatically based on config setting + * + * @var bool + */ + protected $_enable_xss = FALSE; + + private $is_valid_request = TRUE; + + /** + * HTTP status codes and their respective description + * Note: Only the widely used HTTP status codes are used + * + * @var array + * @link http://www.restapitutorial.com/httpstatuscodes.html + */ + protected $http_status_codes = [ + self::HTTP_OK => 'OK', + self::HTTP_CREATED => 'CREATED', + self::HTTP_NO_CONTENT => 'NO CONTENT', + self::HTTP_NOT_MODIFIED => 'NOT MODIFIED', + self::HTTP_BAD_REQUEST => 'BAD REQUEST', + self::HTTP_UNAUTHORIZED => 'UNAUTHORIZED', + self::HTTP_FORBIDDEN => 'FORBIDDEN', + self::HTTP_NOT_FOUND => 'NOT FOUND', + self::HTTP_METHOD_NOT_ALLOWED => 'METHOD NOT ALLOWED', + self::HTTP_NOT_ACCEPTABLE => 'NOT ACCEPTABLE', + self::HTTP_CONFLICT => 'CONFLICT', + self::HTTP_INTERNAL_SERVER_ERROR => 'INTERNAL SERVER ERROR', + self::HTTP_NOT_IMPLEMENTED => 'NOT IMPLEMENTED' + ]; + + /** + * @var Format + */ + private $format; + /** + * @var bool + */ + private $auth_override; + + /** + * Extend this function to apply additional checking early on in the process + * + * @access protected + * @return void + */ + protected function early_checks() + { + } + + /** + * Constructor for the REST API + * + * @access public + * @param string $config Configuration filename minus the file extension + * e.g: my_rest.php is passed as 'my_rest' + */ + public function __construct($config = 'rest') + { + parent::__construct(); + + $this->preflight_checks(); + + // Set the default value of global xss filtering. Same approach as CodeIgniter 3 + $this->_enable_xss = ($this->config->item('global_xss_filtering') === TRUE); + + // Don't try to parse template variables like {elapsed_time} and {memory_usage} + // when output is displayed for not damaging data accidentally + $this->output->parse_exec_vars = FALSE; + + // Start the timer for how long the request takes + $this->_start_rtime = microtime(TRUE); + + // Load the rest.php configuration file + $this->get_local_config($config); + + // At present the library is bundled with REST_Controller 2.5+, but will eventually be part of CodeIgniter (no citation) + if (class_exists('Format')) { + try { + $this->format = new Format(); + } catch (Exception $e) { + echo $e; + } + } else { + $this->load->library('Format', NULL, 'libraryFormat'); + $this->format = $this->libraryFormat; + } + + + // Determine supported output formats from configuration + $supported_formats = $this->config->item('rest_supported_formats'); + + // Validate the configuration setting output formats + if (empty($supported_formats)) { + $supported_formats = []; + } + + if (!is_array($supported_formats)) { + $supported_formats = [$supported_formats]; + } + + // Add silently the default output format if it is missing + $default_format = $this->_get_default_output_format(); + if (!in_array($default_format, $supported_formats)) { + $supported_formats[] = $default_format; + } + + // Now update $this->_supported_formats + $this->_supported_formats = array_intersect_key($this->_supported_formats, array_flip($supported_formats)); + + // Get the language + $language = $this->config->item('rest_language'); + if ($language === NULL) { + $language = 'english'; + } + + // Load the language file + $this->lang->load('rest_controller', $language, FALSE, TRUE, __DIR__.'/../'); + //19930118 + + + // Initialise the response, request and rest objects + $this->request = new stdClass(); + $this->response = new stdClass(); + $this->rest = new stdClass(); + + // Check to see if the current IP address is blacklisted + if ($this->config->item('rest_ip_blacklist_enabled') === TRUE) { + $this->_check_blacklist_auth(); + } + + // Determine whether the connection is HTTPS + $this->request->ssl = is_https(); + + // How is this request being made? GET, POST, PATCH, DELETE, INSERT, PUT, HEAD or OPTIONS + $this->request->method = $this->_detect_method(); + + // Check for CORS access request + $check_cors = $this->config->item('check_cors'); + if ($check_cors === TRUE) { + $this->_check_cors(); + } + + // Create an argument container if it doesn't exist e.g. _get_args + if (isset($this->{'_' . $this->request->method . '_args'}) === FALSE) { + $this->{'_' . $this->request->method . '_args'} = []; + } + + // Set up the query parameters + $this->_parse_query(); + + // Set up the GET variables + $this->_get_args = array_merge($this->_get_args, $this->uri->ruri_to_assoc()); + + // Try to find a format for the request (means we have a request body) + $this->request->format = $this->_detect_input_format(); + + // Not all methods have a body attached with them + $this->request->body = NULL; + + $this->{'_parse_' . $this->request->method}(); + + // Fix parse method return arguments null + if ($this->{'_' . $this->request->method . '_args'} === null) { + $this->{'_' . $this->request->method . '_args'} = []; + } + + // Now we know all about our request, let's try and parse the body if it exists + if ($this->request->format && $this->request->body) { + $this->request->body = Format::factory($this->request->body, $this->request->format)->to_array(); + + // Assign payload arguments to proper method container + $this->{'_' . $this->request->method . '_args'} = $this->request->body; + } + + //get header vars + $this->_head_args = $this->input->request_headers(); + + // Merge both for one mega-args variable + $this->_args = array_merge( + $this->_get_args, + $this->_options_args, + $this->_patch_args, + $this->_head_args, + $this->_put_args, + $this->_post_args, + $this->_delete_args, + $this->{'_' . $this->request->method . '_args'} + ); + + // Which format should the data be returned in? + $this->response->format = $this->_detect_output_format(); + + // Which language should the data be returned in? + $this->response->lang = $this->_detect_lang(); + + // Extend this function to apply additional checking early on in the process + $this->early_checks(); + + // Load DB if its enabled + if ($this->config->item('rest_database_group') && ($this->config->item('rest_enable_keys') || $this->config->item('rest_enable_logging'))) { + $this->rest->db = $this->load->database($this->config->item('rest_database_group'), TRUE); + } // Use whatever database is in use (isset returns FALSE) + elseif (property_exists($this, 'db')) { + $this->rest->db = $this->db; + } + + // Check if there is a specific auth type for the current class/method + // _auth_override_check could exit so we need $this->rest->db initialized before + $this->auth_override = $this->_auth_override_check(); + + // Checking for keys? GET TO WorK! + // Skip keys test for $config['auth_override_class_method']['class'['method'] = 'none' + if ($this->config->item('rest_enable_keys') && $this->auth_override !== TRUE) { + $this->_allow = $this->_detect_api_key(); + } + + // Only allow ajax requests + if ($this->input->is_ajax_request() === FALSE && $this->config->item('rest_ajax_only')) { + // Display an error response + $this->response([ + $this->config->item('rest_status_field_name') => FALSE, + $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_ajax_only') + ], self::HTTP_NOT_ACCEPTABLE); + } + + // When there is no specific override for the current class/method, use the default auth value set in the config + if ($this->auth_override === FALSE && + (!($this->config->item('rest_enable_keys') && $this->_allow === TRUE) || + ($this->config->item('allow_auth_and_keys') === TRUE && $this->_allow === TRUE))) { + $rest_auth = strtolower($this->config->item('rest_auth')); + switch ($rest_auth) { + case 'basic': + $this->_prepare_basic_auth(); + break; + case 'digest': + $this->_prepare_digest_auth(); + break; + case 'session': + $this->_check_php_session(); + break; + } + if ($this->config->item('rest_ip_whitelist_enabled') === TRUE) { + $this->_check_whitelist_auth(); + } + } + } + + /** + * @param $config_file + */ + private function get_local_config($config_file) + { + if (file_exists(__DIR__ . "/../config/" . $config_file . ".php")) { + $config = array(); + include(__DIR__ . "/../config/" . $config_file . ".php"); + + foreach ($config AS $key => $value) { + $this->config->set_item($key, $value); + } + } + + $this->load->config($config_file, FALSE, TRUE); + } + + /** + * De-constructor + * + * @author Chris Kacerguis + * @access public + * @return void + */ + public function __destruct() + { + // Get the current timestamp + $this->_end_rtime = microtime(TRUE); + + // Log the loading time to the log table + if ($this->config->item('rest_enable_logging') === TRUE) { + $this->_log_access_time(); + } + } + + /** + * Checks to see if we have everything we need to run this library. + * + * @access protected + * @throws Exception + */ + protected function preflight_checks() + { + // Check to see if PHP is equal to or greater than 5.4.x + if (is_php('5.4') === FALSE) { + // CodeIgniter 3 is recommended for v5.4 or above + throw new Exception('Using PHP v' . PHP_VERSION . ', though PHP v5.4 or greater is required'); + } + + // Check to see if this is CI 3.x + if (explode('.', CI_VERSION, 2)[0] < 3) { + throw new Exception('REST Server requires CodeIgniter 3.x'); + } + } + + /** + * Requests are not made to methods directly, the request will be for + * an "object". This simply maps the object and method to the correct + * Controller method + * + * @access public + * @param string $object_called + * @param array $arguments The arguments passed to the controller method + * @throws Exception + */ + public function _remap($object_called, $arguments = []) + { + // Should we answer if not over SSL? + if ($this->config->item('force_https') && $this->request->ssl === FALSE) { + $this->response([ + $this->config->item('rest_status_field_name') => FALSE, + $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_unsupported') + ], self::HTTP_FORBIDDEN); + + $this->is_valid_request = false; + } + + // Remove the supported format from the function name e.g. index.json => index + $object_called = preg_replace('/^(.*)\.(?:' . implode('|', array_keys($this->_supported_formats)) . ')$/', '$1', $object_called); + + $controller_method = $object_called . '_' . $this->request->method; + // Does this method exist? If not, try executing an index method + if (!method_exists($this, $controller_method)) { + $controller_method = "index_" . $this->request->method; + array_unshift($arguments, $object_called); + } + + // Do we want to log this method (if allowed by config)? + $log_method = !(isset($this->methods[$controller_method]['log']) && $this->methods[$controller_method]['log'] === FALSE); + + // Use keys for this method? + $use_key = !(isset($this->methods[$controller_method]['key']) && $this->methods[$controller_method]['key'] === FALSE); + + // They provided a key, but it wasn't valid, so get them out of here + if ($this->config->item('rest_enable_keys') && $use_key && $this->_allow === FALSE) { + if ($this->config->item('rest_enable_logging') && $log_method) { + $this->_log_request(); + } + + // fix cross site to option request error + if ($this->request->method == 'options') { + exit; + } + + $this->response([ + $this->config->item('rest_status_field_name') => FALSE, + $this->config->item('rest_message_field_name') => sprintf($this->lang->line('text_rest_invalid_api_key'), $this->rest->key) + ], self::HTTP_FORBIDDEN); + + $this->is_valid_request = false; + } + + // Check to see if this key has access to the requested controller + if ($this->config->item('rest_enable_keys') && $use_key && empty($this->rest->key) === FALSE && $this->_check_access() === FALSE) { + if ($this->config->item('rest_enable_logging') && $log_method) { + $this->_log_request(); + } + + $this->response([ + $this->config->item('rest_status_field_name') => FALSE, + $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_api_key_unauthorized') + ], self::HTTP_UNAUTHORIZED); + + $this->is_valid_request = false; + } + + // Sure it exists, but can they do anything with it? + if (!method_exists($this, $controller_method)) { + $this->response([ + $this->config->item('rest_status_field_name') => FALSE, + $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_unknown_method') + ], self::HTTP_METHOD_NOT_ALLOWED); + + $this->is_valid_request = false; + } + + // Doing key related stuff? Can only do it if they have a key right? + if ($this->config->item('rest_enable_keys') && empty($this->rest->key) === FALSE) { + // Check the limit + if ($this->config->item('rest_enable_limits') && $this->_check_limit($controller_method) === FALSE) { + $response = [$this->config->item('rest_status_field_name') => FALSE, $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_api_key_time_limit')]; + $this->response($response, self::HTTP_UNAUTHORIZED); + + $this->is_valid_request = false; + } + + // If no level is set use 0, they probably aren't using permissions + $level = isset($this->methods[$controller_method]['level']) ? $this->methods[$controller_method]['level'] : 0; + + // If no level is set, or it is lower than/equal to the key's level + $authorized = $level <= $this->rest->level; + // IM TELLIN! + if ($this->config->item('rest_enable_logging') && $log_method) { + $this->_log_request($authorized); + } + if ($authorized === FALSE) { + // They don't have good enough perms + $response = [$this->config->item('rest_status_field_name') => FALSE, $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_api_key_permissions')]; + $this->response($response, self::HTTP_UNAUTHORIZED); + + $this->is_valid_request = false; + } + } //check request limit by ip without login + elseif ($this->config->item('rest_limits_method') == "IP_ADDRESS" && $this->config->item('rest_enable_limits') && $this->_check_limit($controller_method) === FALSE) { + $response = [$this->config->item('rest_status_field_name') => FALSE, $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_ip_address_time_limit')]; + $this->response($response, self::HTTP_UNAUTHORIZED); + + $this->is_valid_request = false; + } // No key stuff, but record that stuff is happening + elseif ($this->config->item('rest_enable_logging') && $log_method) { + $this->_log_request($authorized = TRUE); + } + + // Call the controller method and passed arguments + try { + if ($this->is_valid_request) { + call_user_func_array([$this, $controller_method], $arguments); + } + } catch (Exception $ex) { + if ($this->config->item('rest_handle_exceptions') === FALSE) { + throw $ex; + } + + // If the method doesn't exist, then the error will be caught and an error response shown + $_error = &load_class('Exceptions', 'core'); + $_error->show_exception($ex); + } + } + + /** + * Takes mixed data and optionally a status code, then creates the response + * + * @access public + * @param array|NULL $data Data to output to the user + * @param int|NULL $http_code HTTP status code + * @param bool $continue TRUE to flush the response to the client and continue + * running the script; otherwise, exit + */ + public function response($data = NULL, $http_code = NULL, $continue = FALSE) + { + //if profiling enabled then print profiling data + $isProfilingEnabled = $this->config->item('enable_profiling'); + if (!$isProfilingEnabled) { + ob_start(); + // If the HTTP status is not NULL, then cast as an integer + if ($http_code !== NULL) { + // So as to be safe later on in the process + $http_code = (int)$http_code; + } + + // Set the output as NULL by default + $output = NULL; + + // If data is NULL and no HTTP status code provided, then display, error and exit + if ($data === NULL && $http_code === NULL) { + $http_code = self::HTTP_NOT_FOUND; + } // If data is not NULL and a HTTP status code provided, then continue + elseif ($data !== NULL) { + // If the format method exists, call and return the output in that format + if (method_exists(Format::class, 'to_' . $this->response->format)) { + // Set the format header + $this->output->set_content_type($this->_supported_formats[$this->response->format], strtolower($this->config->item('charset'))); + $output = Format::factory($data)->{'to_' . $this->response->format}(); + + // An array must be parsed as a string, so as not to cause an array to string error + // Json is the most appropriate form for such a data type + if ($this->response->format === 'array') { + $output = Format::factory($output)->{'to_json'}(); + } + } else { + // If an array or object, then parse as a json, so as to be a 'string' + if (is_array($data) || is_object($data)) { + $data = Format::factory($data)->{'to_json'}(); + } + + // Format is not supported, so output the raw data as a string + $output = $data; + } + } + + // If not greater than zero, then set the HTTP status code as 200 by default + // Though perhaps 500 should be set instead, for the developer not passing a + // correct HTTP status code + $http_code > 0 || $http_code = self::HTTP_OK; + + $this->output->set_status_header($http_code); + + // JC: Log response code only if rest logging enabled + if ($this->config->item('rest_enable_logging') === TRUE) { + $this->_log_response_code($http_code); + } + + // Output the data + $this->output->set_output($output); + + if ($continue === FALSE) { + // Display the data and exit execution + $this->output->_display(); + exit; + } else { + ob_end_flush(); + } + + // Otherwise dump the output automatically + } else { + echo json_encode($data); + } + } + + /** + * Takes mixed data and optionally a status code, then creates the response + * within the buffers of the Output class. The response is sent to the client + * lately by the framework, after the current controller's method termination. + * All the hooks after the controller's method termination are executable + * + * @access public + * @param array|NULL $data Data to output to the user + * @param int|NULL $http_code HTTP status code + */ + public function set_response($data = NULL, $http_code = NULL) + { + $this->response($data, $http_code, TRUE); + } + + /** + * Get the input format e.g. json or xml + * + * @access protected + * @return string|NULL Supported input format; otherwise, NULL + */ + protected function _detect_input_format() + { + // Get the CONTENT-TYPE value from the SERVER variable + $content_type = $this->input->server('CONTENT_TYPE'); + + if (empty($content_type) === FALSE) { + // If a semi-colon exists in the string, then explode by ; and get the value of where + // the current array pointer resides. This will generally be the first element of the array + $content_type = (strpos($content_type, ';') !== FALSE ? current(explode(';', $content_type)) : $content_type); + + // Check all formats against the CONTENT-TYPE header + foreach ($this->_supported_formats as $type => $mime) { + // $type = format e.g. csv + // $mime = mime type e.g. application/csv + + // If both the mime types match, then return the format + if ($content_type === $mime) { + return $type; + } + } + } + + return NULL; + } + + /** + * Gets the default format from the configuration. Fallbacks to 'json' + * if the corresponding configuration option $config['rest_default_format'] + * is missing or is empty + * + * @access protected + * @return string The default supported input format + */ + protected function _get_default_output_format() + { + $default_format = (string)$this->config->item('rest_default_format'); + return $default_format === '' ? 'json' : $default_format; + } + + /** + * Detect which format should be used to output the data + * + * @access protected + * @return mixed|NULL|string Output format + */ + protected function _detect_output_format() + { + // Concatenate formats to a regex pattern e.g. \.(csv|json|xml) + $pattern = '/\.(' . implode('|', array_keys($this->_supported_formats)) . ')($|\/)/'; + $matches = []; + + // Check if a file extension is used e.g. http://example.com/api/index.json?param1=param2 + if (preg_match($pattern, $this->uri->uri_string(), $matches)) { + return $matches[1]; + } + + // Get the format parameter named as 'format' + if (isset($this->_get_args['format'])) { + $format = strtolower($this->_get_args['format']); + + if (isset($this->_supported_formats[$format]) === TRUE) { + return $format; + } + } + + // Get the HTTP_ACCEPT server variable + $http_accept = $this->input->server('HTTP_ACCEPT'); + + // Otherwise, check the HTTP_ACCEPT server variable + if ($this->config->item('rest_ignore_http_accept') === FALSE && $http_accept !== NULL) { + // Check all formats against the HTTP_ACCEPT header + foreach (array_keys($this->_supported_formats) as $format) { + // Has this format been requested? + if (strpos($http_accept, $format) !== FALSE) { + if ($format !== 'html' && $format !== 'xml') { + // If not HTML or XML assume it's correct + return $format; + } elseif ($format === 'html' && strpos($http_accept, 'xml') === FALSE) { + // HTML or XML have shown up as a match + // If it is truly HTML, it wont want any XML + return $format; + } else if ($format === 'xml' && strpos($http_accept, 'html') === FALSE) { + // If it is truly XML, it wont want any HTML + return $format; + } + } + } + } + + // Check if the controller has a default format + if (empty($this->rest_format) === FALSE) { + return $this->rest_format; + } + + // Obtain the default format from the configuration + return $this->_get_default_output_format(); + } + + /** + * Get the HTTP request string e.g. get or post + * + * @access protected + * @return string|NULL Supported request method as a lowercase string; otherwise, NULL if not supported + */ + protected function _detect_method() + { + // Declare a variable to store the method + $method = NULL; + + // Determine whether the 'enable_emulate_request' setting is enabled + if ($this->config->item('enable_emulate_request') === TRUE) { + $method = $this->input->post('_method'); + if ($method === NULL) { + $method = $this->input->server('HTTP_X_HTTP_METHOD_OVERRIDE'); + } + + $method = strtolower($method); + } + + if (empty($method)) { + // Get the request method as a lowercase string + $method = $this->input->method(); + } + + return in_array($method, $this->allowed_http_methods) && method_exists($this, '_parse_' . $method) ? $method : 'get'; + } + + /** + * See if the user has provided an API key + * + * @access protected + * @return bool + */ + protected function _detect_api_key() + { + // Get the api key name variable set in the rest config file + $api_key_variable = $this->config->item('rest_key_name'); + + // Work out the name of the SERVER entry based on config + $key_name = 'HTTP_' . strtoupper(str_replace('-', '_', $api_key_variable)); + + $this->rest->key = NULL; + $this->rest->level = NULL; + $this->rest->user_id = NULL; + $this->rest->ignore_limits = FALSE; + + // Find the key from server or arguments + if (($key = isset($this->_args[$api_key_variable]) ? $this->_args[$api_key_variable] : $this->input->server($key_name))) { + if (!($row = $this->rest->db->where($this->config->item('rest_key_column'), $key)->get($this->config->item('rest_keys_table'))->row())) { + return FALSE; + } + + $this->rest->key = $row->{$this->config->item('rest_key_column')}; + + isset($row->user_id) && $this->rest->user_id = $row->user_id; + isset($row->level) && $this->rest->level = $row->level; + isset($row->ignore_limits) && $this->rest->ignore_limits = $row->ignore_limits; + + $this->_apiuser = $row; + + /* + * If "is private key" is enabled, compare the ip address with the list + * of valid ip addresses stored in the database + */ + if (empty($row->is_private_key) === FALSE) { + // Check for a list of valid ip addresses + if (isset($row->ip_addresses)) { + // multiple ip addresses must be separated using a comma, explode and loop + $list_ip_addresses = explode(',', $row->ip_addresses); + $found_address = FALSE; + + foreach ($list_ip_addresses as $ip_address) { + if ($this->input->ip_address() === trim($ip_address)) { + // there is a match, set the the value to TRUE and break out of the loop + $found_address = TRUE; + break; + } + } + + return $found_address; + } else { + // There should be at least one IP address for this private key + return FALSE; + } + } + + return TRUE; + } + + // No key has been sent + return FALSE; + } + + /** + * Preferred return language + * + * @access protected + * @return string|NULL|array The language code + */ + protected function _detect_lang() + { + $lang = $this->input->server('HTTP_ACCEPT_LANGUAGE'); + if ($lang === NULL) { + return NULL; + } + + // It appears more than one language has been sent using a comma delimiter + if (strpos($lang, ',') !== FALSE) { + $langs = explode(',', $lang); + + $return_langs = []; + foreach ($langs as $lang) { + // Remove weight and trim leading and trailing whitespace + list($lang) = explode(';', $lang); + $return_langs[] = trim($lang); + } + + return $return_langs; + } + + // Otherwise simply return as a string + return $lang; + } + + /** + * Add the request to the log table + * + * @access protected + * @param bool $authorized TRUE the user is authorized; otherwise, FALSE + * @return bool TRUE the data was inserted; otherwise, FALSE + */ + protected function _log_request($authorized = FALSE) + { + // Insert the request into the log table + $is_inserted = $this->rest->db + ->insert( + $this->config->item('rest_logs_table'), [ + 'uri' => $this->uri->uri_string(), + 'method' => $this->request->method, + 'params' => $this->_args ? ($this->config->item('rest_logs_json_params') === TRUE ? json_encode($this->_args) : serialize($this->_args)) : NULL, + 'api_key' => isset($this->rest->key) ? $this->rest->key : '', + 'ip_address' => $this->input->ip_address(), + 'time' => time(), + 'authorized' => $authorized + ]); + + // Get the last insert id to update at a later stage of the request + $this->_insert_id = $this->rest->db->insert_id(); + + return $is_inserted; + } + + /** + * Check if the requests to a controller method exceed a limit + * + * @access protected + * @param string $controller_method The method being called + * @return bool TRUE the call limit is below the threshold; otherwise, FALSE + */ + protected function _check_limit($controller_method) + { + // They are special, or it might not even have a limit + if (empty($this->rest->ignore_limits) === FALSE) { + // Everything is fine + return TRUE; + } + + $api_key = isset($this->rest->key) ? $this->rest->key : ''; + + switch ($this->config->item('rest_limits_method')) { + case 'IP_ADDRESS': + $limited_uri = 'ip-address:' . $this->input->ip_address(); + $api_key = $this->input->ip_address(); + break; + + case 'API_KEY': + $limited_uri = 'api-key:' . $api_key; + break; + + case 'METHOD_NAME': + $limited_uri = 'method-name:' . $controller_method; + break; + + case 'ROUTED_URL': + default: + $limited_uri = $this->uri->ruri_string(); + if (strpos(strrev($limited_uri), strrev($this->response->format)) === 0) { + $limited_uri = substr($limited_uri, 0, -strlen($this->response->format) - 1); + } + $limited_uri = 'uri:' . $limited_uri . ':' . $this->request->method; // It's good to differentiate GET from PUT + break; + } + + if (isset($this->methods[$controller_method]['limit']) === FALSE) { + // Everything is fine + return TRUE; + } + + // How many times can you get to this method in a defined time_limit (default: 1 hour)? + $limit = $this->methods[$controller_method]['limit']; + + $time_limit = (isset($this->methods[$controller_method]['time']) ? $this->methods[$controller_method]['time'] : 3600); // 3600 = 60 * 60 + + // Get data about a keys' usage and limit to one row + $result = $this->rest->db + ->where('uri', $limited_uri) + ->where('api_key', $api_key) + ->get($this->config->item('rest_limits_table')) + ->row(); + + // No calls have been made for this key + if ($result === NULL) { + // Create a new row for the following key + $this->rest->db->insert($this->config->item('rest_limits_table'), [ + 'uri' => $limited_uri, + 'api_key' => $api_key, + 'count' => 1, + 'hour_started' => time() + ]); + } // Been a time limit (or by default an hour) since they called + elseif ($result->hour_started < (time() - $time_limit)) { + // Reset the started period and count + $this->rest->db + ->where('uri', $limited_uri) + ->where('api_key', $api_key) + ->set('hour_started', time()) + ->set('count', 1) + ->update($this->config->item('rest_limits_table')); + } // They have called within the hour, so lets update + else { + // The limit has been exceeded + if ($result->count >= $limit) { + return FALSE; + } + + // Increase the count by one + $this->rest->db + ->where('uri', $limited_uri) + ->where('api_key', $api_key) + ->set('count', 'count + 1', FALSE) + ->update($this->config->item('rest_limits_table')); + } + + return TRUE; + } + + /** + * Check if there is a specific auth type set for the current class/method/HTTP-method being called + * + * @access protected + * @return bool + */ + protected function _auth_override_check() + { + // Assign the class/method auth type override array from the config + $auth_override_class_method = $this->config->item('auth_override_class_method'); + + // Check to see if the override array is even populated + if (!empty($auth_override_class_method)) { + // Check for wildcard flag for rules for classes + if (!empty($auth_override_class_method[$this->router->class]['*'])) // Check for class overrides + { + // No auth override found, prepare nothing but send back a TRUE override flag + if ($auth_override_class_method[$this->router->class]['*'] === 'none') { + return TRUE; + } + + // Basic auth override found, prepare basic + if ($auth_override_class_method[$this->router->class]['*'] === 'basic') { + $this->_prepare_basic_auth(); + + return TRUE; + } + + // Digest auth override found, prepare digest + if ($auth_override_class_method[$this->router->class]['*'] === 'digest') { + $this->_prepare_digest_auth(); + + return TRUE; + } + + // Session auth override found, check session + if ($auth_override_class_method[$this->router->class]['*'] === 'session') { + $this->_check_php_session(); + + return TRUE; + } + + // Whitelist auth override found, check client's ip against config whitelist + if ($auth_override_class_method[$this->router->class]['*'] === 'whitelist') { + $this->_check_whitelist_auth(); + + return TRUE; + } + } + + // Check to see if there's an override value set for the current class/method being called + if (!empty($auth_override_class_method[$this->router->class][$this->router->method])) { + // None auth override found, prepare nothing but send back a TRUE override flag + if ($auth_override_class_method[$this->router->class][$this->router->method] === 'none') { + return TRUE; + } + + // Basic auth override found, prepare basic + if ($auth_override_class_method[$this->router->class][$this->router->method] === 'basic') { + $this->_prepare_basic_auth(); + + return TRUE; + } + + // Digest auth override found, prepare digest + if ($auth_override_class_method[$this->router->class][$this->router->method] === 'digest') { + $this->_prepare_digest_auth(); + + return TRUE; + } + + // Session auth override found, check session + if ($auth_override_class_method[$this->router->class][$this->router->method] === 'session') { + $this->_check_php_session(); + + return TRUE; + } + + // Whitelist auth override found, check client's ip against config whitelist + if ($auth_override_class_method[$this->router->class][$this->router->method] === 'whitelist') { + $this->_check_whitelist_auth(); + + return TRUE; + } + } + } + + // Assign the class/method/HTTP-method auth type override array from the config + $auth_override_class_method_http = $this->config->item('auth_override_class_method_http'); + + // Check to see if the override array is even populated + if (!empty($auth_override_class_method_http)) { + // check for wildcard flag for rules for classes + if (!empty($auth_override_class_method_http[$this->router->class]['*'][$this->request->method])) { + // None auth override found, prepare nothing but send back a TRUE override flag + if ($auth_override_class_method_http[$this->router->class]['*'][$this->request->method] === 'none') { + return TRUE; + } + + // Basic auth override found, prepare basic + if ($auth_override_class_method_http[$this->router->class]['*'][$this->request->method] === 'basic') { + $this->_prepare_basic_auth(); + + return TRUE; + } + + // Digest auth override found, prepare digest + if ($auth_override_class_method_http[$this->router->class]['*'][$this->request->method] === 'digest') { + $this->_prepare_digest_auth(); + + return TRUE; + } + + // Session auth override found, check session + if ($auth_override_class_method_http[$this->router->class]['*'][$this->request->method] === 'session') { + $this->_check_php_session(); + + return TRUE; + } + + // Whitelist auth override found, check client's ip against config whitelist + if ($auth_override_class_method_http[$this->router->class]['*'][$this->request->method] === 'whitelist') { + $this->_check_whitelist_auth(); + + return TRUE; + } + } + + // Check to see if there's an override value set for the current class/method/HTTP-method being called + if (!empty($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method])) { + // None auth override found, prepare nothing but send back a TRUE override flag + if ($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method] === 'none') { + return TRUE; + } + + // Basic auth override found, prepare basic + if ($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method] === 'basic') { + $this->_prepare_basic_auth(); + + return TRUE; + } + + // Digest auth override found, prepare digest + if ($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method] === 'digest') { + $this->_prepare_digest_auth(); + + return TRUE; + } + + // Session auth override found, check session + if ($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method] === 'session') { + $this->_check_php_session(); + + return TRUE; + } + + // Whitelist auth override found, check client's ip against config whitelist + if ($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method] === 'whitelist') { + $this->_check_whitelist_auth(); + + return TRUE; + } + } + } + return FALSE; + } + + /** + * Parse the GET request arguments + * + * @access protected + * @return void + */ + protected function _parse_get() + { + // Merge both the URI segments and query parameters + $this->_get_args = array_merge($this->_get_args, $this->_query_args); + } + + /** + * Parse the POST request arguments + * + * @access protected + * @return void + */ + protected function _parse_post() + { + $this->_post_args = $_POST; + + if ($this->request->format) { + $this->request->body = $this->input->raw_input_stream; + } + } + + /** + * Parse the PUT request arguments + * + * @access protected + * @return void + */ + protected function _parse_put() + { + if ($this->request->format) { + $this->request->body = $this->input->raw_input_stream; + if ($this->request->format === 'json') { + $this->_put_args = json_decode($this->input->raw_input_stream); + } + } else if ($this->input->method() === 'put') { + // If no file type is provided, then there are probably just arguments + $this->_put_args = $this->input->input_stream(); + } + } + + /** + * Parse the HEAD request arguments + * + * @access protected + * @return void + */ + protected function _parse_head() + { + // Parse the HEAD variables + parse_str(parse_url($this->input->server('REQUEST_URI'), PHP_URL_QUERY), $head); + + // Merge both the URI segments and HEAD params + $this->_head_args = array_merge($this->_head_args, $head); + } + + /** + * Parse the OPTIONS request arguments + * + * @access protected + * @return void + */ + protected function _parse_options() + { + // Parse the OPTIONS variables + parse_str(parse_url($this->input->server('REQUEST_URI'), PHP_URL_QUERY), $options); + + // Merge both the URI segments and OPTIONS params + $this->_options_args = array_merge($this->_options_args, $options); + } + + /** + * Parse the PATCH request arguments + * + * @access protected + * @return void + */ + protected function _parse_patch() + { + // It might be a HTTP body + if ($this->request->format) { + $this->request->body = $this->input->raw_input_stream; + } else if ($this->input->method() === 'patch') { + // If no file type is provided, then there are probably just arguments + $this->_patch_args = $this->input->input_stream(); + } + } + + /** + * Parse the DELETE request arguments + * + * @access protected + * @return void + */ + protected function _parse_delete() + { + // These should exist if a DELETE request + if ($this->input->method() === 'delete') { + $this->_delete_args = $this->input->input_stream(); + } + } + + /** + * Parse the query parameters + * + * @access protected + * @return void + */ + protected function _parse_query() + { + $this->_query_args = $this->input->get(); + } + + // INPUT FUNCTION -------------------------------------------------------------- + + /** + * Retrieve a value from a GET request + * + * @access public + * @param NULL $key Key to retrieve from the GET request + * If NULL an array of arguments is returned + * @param NULL $xss_clean Whether to apply XSS filtering + * @return array|string|NULL Value from the GET request; otherwise, NULL + */ + public function get($key = NULL, $xss_clean = NULL) + { + if ($key === NULL) { + return $this->_get_args; + } + + return isset($this->_get_args[$key]) ? $this->_xss_clean($this->_get_args[$key], $xss_clean) : NULL; + } + + /** + * Retrieve a value from a OPTIONS request + * + * @access public + * @param NULL $key Key to retrieve from the OPTIONS request. + * If NULL an array of arguments is returned + * @param NULL $xss_clean Whether to apply XSS filtering + * @return array|string|NULL Value from the OPTIONS request; otherwise, NULL + */ + public function options($key = NULL, $xss_clean = NULL) + { + if ($key === NULL) { + return $this->_options_args; + } + + return isset($this->_options_args[$key]) ? $this->_xss_clean($this->_options_args[$key], $xss_clean) : NULL; + } + + /** + * Retrieve a value from a HEAD request + * + * @access public + * @param NULL $key Key to retrieve from the HEAD request + * If NULL an array of arguments is returned + * @param NULL $xss_clean Whether to apply XSS filtering + * @return array|string|NULL Value from the HEAD request; otherwise, NULL + */ + public function head($key = NULL, $xss_clean = NULL) + { + if ($key === NULL) { + return $this->_head_args; + } + + return isset($this->_head_args[$key]) ? $this->_xss_clean($this->_head_args[$key], $xss_clean) : NULL; + } + + /** + * Retrieve a value from a POST request + * + * @access public + * @param NULL $key Key to retrieve from the POST request + * If NULL an array of arguments is returned + * @param NULL $xss_clean Whether to apply XSS filtering + * @return array|string|NULL Value from the POST request; otherwise, NULL + */ + public function post($key = NULL, $xss_clean = NULL) + { + if ($key === NULL) { + return $this->_post_args; + } + + return isset($this->_post_args[$key]) ? $this->_xss_clean($this->_post_args[$key], $xss_clean) : NULL; + } + + /** + * Retrieve a value from a PUT request + * + * @access public + * @param NULL $key Key to retrieve from the PUT request + * If NULL an array of arguments is returned + * @param NULL $xss_clean Whether to apply XSS filtering + * @return array|string|NULL Value from the PUT request; otherwise, NULL + */ + public function put($key = NULL, $xss_clean = NULL) + { + if ($key === NULL) { + return $this->_put_args; + } + + return isset($this->_put_args[$key]) ? $this->_xss_clean($this->_put_args[$key], $xss_clean) : NULL; + } + + /** + * Retrieve a value from a DELETE request + * + * @access public + * @param NULL $key Key to retrieve from the DELETE request + * If NULL an array of arguments is returned + * @param NULL $xss_clean Whether to apply XSS filtering + * @return array|string|NULL Value from the DELETE request; otherwise, NULL + */ + public function delete($key = NULL, $xss_clean = NULL) + { + if ($key === NULL) { + return $this->_delete_args; + } + + return isset($this->_delete_args[$key]) ? $this->_xss_clean($this->_delete_args[$key], $xss_clean) : NULL; + } + + /** + * Retrieve a value from a PATCH request + * + * @access public + * @param NULL $key Key to retrieve from the PATCH request + * If NULL an array of arguments is returned + * @param NULL $xss_clean Whether to apply XSS filtering + * @return array|string|NULL Value from the PATCH request; otherwise, NULL + */ + public function patch($key = NULL, $xss_clean = NULL) + { + if ($key === NULL) { + return $this->_patch_args; + } + + return isset($this->_patch_args[$key]) ? $this->_xss_clean($this->_patch_args[$key], $xss_clean) : NULL; + } + + /** + * Retrieve a value from the query parameters + * + * @access public + * @param NULL $key Key to retrieve from the query parameters + * If NULL an array of arguments is returned + * @param NULL $xss_clean Whether to apply XSS filtering + * @return array|string|NULL Value from the query parameters; otherwise, NULL + */ + public function query($key = NULL, $xss_clean = NULL) + { + if ($key === NULL) { + return $this->_query_args; + } + + return isset($this->_query_args[$key]) ? $this->_xss_clean($this->_query_args[$key], $xss_clean) : NULL; + } + + /** + * Sanitizes data so that Cross Site Scripting Hacks can be + * prevented + * + * @access protected + * @param string $value Input data + * @param bool $xss_clean Whether to apply XSS filtering + * @return string + */ + protected function _xss_clean($value, $xss_clean) + { + is_bool($xss_clean) || $xss_clean = $this->_enable_xss; + + return $xss_clean === TRUE ? $this->security->xss_clean($value) : $value; + } + + /** + * Retrieve the validation errors + * + * @access public + * @return array + */ + public function validation_errors() + { + $string = strip_tags($this->form_validation->error_string()); + + return explode(PHP_EOL, trim($string, PHP_EOL)); + } + + // SECURITY FUNCTIONS --------------------------------------------------------- + + /** + * Perform LDAP Authentication + * + * @access protected + * @param string $username The username to validate + * @param string $password The password to validate + * @return bool + */ + protected function _perform_ldap_auth($username = '', $password = NULL) + { + if (empty($username)) { + log_message('debug', 'LDAP Auth: failure, empty username'); + return FALSE; + } + + log_message('debug', 'LDAP Auth: Loading configuration'); + + $this->config->load('ldap', TRUE); + + $ldap = [ + 'timeout' => $this->config->item('timeout', 'ldap'), + 'host' => $this->config->item('server', 'ldap'), + 'port' => $this->config->item('port', 'ldap'), + 'rdn' => $this->config->item('binduser', 'ldap'), + 'pass' => $this->config->item('bindpw', 'ldap'), + 'basedn' => $this->config->item('basedn', 'ldap'), + ]; + + log_message('debug', 'LDAP Auth: Connect to ' . (isset($ldaphost) ? $ldaphost : '[ldap not configured]')); + + // Connect to the ldap server + $ldapconn = ldap_connect($ldap['host'], $ldap['port']); + if ($ldapconn) { + log_message('debug', 'Setting timeout to ' . $ldap['timeout'] . ' seconds'); + + ldap_set_option($ldapconn, LDAP_OPT_NETWORK_TIMEOUT, $ldap['timeout']); + + log_message('debug', 'LDAP Auth: Binding to ' . $ldap['host'] . ' with dn ' . $ldap['rdn']); + + // Binding to the ldap server + $ldapbind = ldap_bind($ldapconn, $ldap['rdn'], $ldap['pass']); + + // Verify the binding + if ($ldapbind === FALSE) { + log_message('error', 'LDAP Auth: bind was unsuccessful'); + return FALSE; + } + + log_message('debug', 'LDAP Auth: bind successful'); + } + + // Search for user + if (($res_id = ldap_search($ldapconn, $ldap['basedn'], "uid=$username")) === FALSE) { + log_message('error', 'LDAP Auth: User ' . $username . ' not found in search'); + return FALSE; + } + + if (ldap_count_entries($ldapconn, $res_id) !== 1) { + log_message('error', 'LDAP Auth: Failure, username ' . $username . 'found more than once'); + return FALSE; + } + + if (($entry_id = ldap_first_entry($ldapconn, $res_id)) === FALSE) { + log_message('error', 'LDAP Auth: Failure, entry of search result could not be fetched'); + return FALSE; + } + + if (($user_dn = ldap_get_dn($ldapconn, $entry_id)) === FALSE) { + log_message('error', 'LDAP Auth: Failure, user-dn could not be fetched'); + return FALSE; + } + + // User found, could not authenticate as user + if (($link_id = ldap_bind($ldapconn, $user_dn, $password)) === FALSE) { + log_message('error', 'LDAP Auth: Failure, username/password did not match: ' . $user_dn); + return FALSE; + } + + log_message('debug', 'LDAP Auth: Success ' . $user_dn . ' authenticated successfully'); + + $this->_user_ldap_dn = $user_dn; + + ldap_close($ldapconn); + + return TRUE; + } + + /** + * Perform Library Authentication - Override this function to change the way the library is called + * + * @access protected + * @param string $username The username to validate + * @param string $password The password to validate + * @return bool + */ + protected function _perform_library_auth($username = '', $password = NULL) + { + if (empty($username)) { + log_message('error', 'Library Auth: Failure, empty username'); + return FALSE; + } + + $auth_library_class = strtolower($this->config->item('auth_library_class')); + $auth_library_function = strtolower($this->config->item('auth_library_function')); + + if (empty($auth_library_class)) { + log_message('debug', 'Library Auth: Failure, empty auth_library_class'); + return FALSE; + } + + if (empty($auth_library_function)) { + log_message('debug', 'Library Auth: Failure, empty auth_library_function'); + return FALSE; + } + + if (is_callable([$auth_library_class, $auth_library_function]) === FALSE) { + $this->load->library($auth_library_class); + } + + return $this->{$auth_library_class}->$auth_library_function($username, $password); + } + + /** + * Check if the user is logged in + * + * @access protected + * @param string $username The user's name + * @param bool|string $password The user's password + * @return bool + */ + protected function _check_login($username = NULL, $password = FALSE) + { + if (empty($username)) { + return FALSE; + } + + $auth_source = strtolower($this->config->item('auth_source')); + $rest_auth = strtolower($this->config->item('rest_auth')); + $valid_logins = $this->config->item('rest_valid_logins'); + + if (!$this->config->item('auth_source') && $rest_auth === 'digest') { + // For digest we do not have a password passed as argument + return md5($username . ':' . $this->config->item('rest_realm') . ':' . (isset($valid_logins[$username]) ? $valid_logins[$username] : '')); + } + + if ($password === FALSE) { + return FALSE; + } + + if ($auth_source === 'ldap') { + log_message('debug', "Performing LDAP authentication for $username"); + + return $this->_perform_ldap_auth($username, $password); + } + + if ($auth_source === 'library') { + log_message('debug', "Performing Library authentication for $username"); + + return $this->_perform_library_auth($username, $password); + } + + if (array_key_exists($username, $valid_logins) === FALSE) { + return FALSE; + } + + if ($valid_logins[$username] !== $password) { + return FALSE; + } + + return TRUE; + } + + /** + * Check to see if the user is logged in with a PHP session key + * + * @access protected + * @return void + */ + protected function _check_php_session() + { + // Get the auth_source config item + $key = $this->config->item('auth_source'); + + // If false, then the user isn't logged in + if (!$this->session->userdata($key)) { + // Display an error response + $this->response([ + $this->config->item('rest_status_field_name') => FALSE, + $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_unauthorized') + ], self::HTTP_UNAUTHORIZED); + } + } + + /** + * Prepares for basic authentication + * + * @access protected + * @return void + */ + protected function _prepare_basic_auth() + { + // If whitelist is enabled it has the first chance to kick them out + if ($this->config->item('rest_ip_whitelist_enabled')) { + $this->_check_whitelist_auth(); + } + + // Returns NULL if the SERVER variables PHP_AUTH_USER and HTTP_AUTHENTICATION don't exist + $username = $this->input->server('PHP_AUTH_USER'); + $http_auth = $this->input->server('HTTP_AUTHENTICATION') ?: $this->input->server('HTTP_AUTHORIZATION'); + + $password = NULL; + if ($username !== NULL) { + $password = $this->input->server('PHP_AUTH_PW'); + } elseif ($http_auth !== NULL) { + // If the authentication header is set as basic, then extract the username and password from + // HTTP_AUTHORIZATION e.g. my_username:my_password. This is passed in the .htaccess file + if (strpos(strtolower($http_auth), 'basic') === 0) { + // Search online for HTTP_AUTHORIZATION workaround to explain what this is doing + list($username, $password) = explode(':', base64_decode(substr($this->input->server('HTTP_AUTHORIZATION'), 6))); + } + } + + // Check if the user is logged into the system + if ($this->_check_login($username, $password) === FALSE) { + $this->_force_login(); + } + } + + /** + * Prepares for digest authentication + * + * @access protected + * @return void + */ + protected function _prepare_digest_auth() + { + // If whitelist is enabled it has the first chance to kick them out + if ($this->config->item('rest_ip_whitelist_enabled')) { + $this->_check_whitelist_auth(); + } + + // We need to test which server authentication variable to use, + // because the PHP ISAPI module in IIS acts different from CGI + $digest_string = $this->input->server('PHP_AUTH_DIGEST'); + if ($digest_string === NULL) { + $digest_string = $this->input->server('HTTP_AUTHORIZATION'); + } + + $unique_id = uniqid(); + + // The $_SESSION['error_prompted'] variable is used to ask the password + // again if none given or if the user enters wrong auth information + if (empty($digest_string)) { + $this->_force_login($unique_id); + } + + // We need to retrieve authentication data from the $digest_string variable + $matches = []; + preg_match_all('@(username|nonce|uri|nc|cnonce|qop|response)=[\'"]?([^\'",]+)@', $digest_string, $matches); + $digest = (empty($matches[1]) || empty($matches[2])) ? [] : array_combine($matches[1], $matches[2]); + + // For digest authentication the library function should return already stored md5(username:restrealm:password) for that username see rest.php::auth_library_function config + $username = $this->_check_login($digest['username'], TRUE); + if (array_key_exists('username', $digest) === FALSE || $username === FALSE) { + $this->_force_login($unique_id); + } + + $md5 = md5(strtoupper($this->request->method) . ':' . $digest['uri']); + $valid_response = md5($username . ':' . $digest['nonce'] . ':' . $digest['nc'] . ':' . $digest['cnonce'] . ':' . $digest['qop'] . ':' . $md5); + + // Check if the string don't compare (case-insensitive) + if (strcasecmp($digest['response'], $valid_response) !== 0) { + // Display an error response + $this->response([ + $this->config->item('rest_status_field_name') => FALSE, + $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_invalid_credentials') + ], self::HTTP_UNAUTHORIZED); + } + } + + /** + * Checks if the client's ip is in the 'rest_ip_blacklist' config and generates a 401 response + * + * @access protected + * @return void + */ + protected function _check_blacklist_auth() + { + // Match an ip address in a blacklist e.g. 127.0.0.0, 0.0.0.0 + $pattern = sprintf('/(?:,\s*|^)\Q%s\E(?=,\s*|$)/m', $this->input->ip_address()); + + // Returns 1, 0 or FALSE (on error only). Therefore implicitly convert 1 to TRUE + if (preg_match($pattern, $this->config->item('rest_ip_blacklist'))) { + // Display an error response + $this->response([ + $this->config->item('rest_status_field_name') => FALSE, + $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_ip_denied') + ], self::HTTP_UNAUTHORIZED); + } + } + + /** + * Check if the client's ip is in the 'rest_ip_whitelist' config and generates a 401 response + * + * @access protected + * @return void + */ + protected function _check_whitelist_auth() + { + $whitelist = explode(',', $this->config->item('rest_ip_whitelist')); + + array_push($whitelist, '127.0.0.1', '0.0.0.0'); + + foreach ($whitelist as &$ip) { + // As $ip is a reference, trim leading and trailing whitespace, then store the new value + // using the reference + $ip = trim($ip); + } + + if (in_array($this->input->ip_address(), $whitelist) === FALSE) { + $this->response([ + $this->config->item('rest_status_field_name') => FALSE, + $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_ip_unauthorized') + ], self::HTTP_UNAUTHORIZED); + } + } + + /** + * Force logging in by setting the WWW-Authenticate header + * + * @access protected + * @param string $nonce A server-specified data string which should be uniquely generated + * each time + * @return void + */ + protected function _force_login($nonce = '') + { + $rest_auth = $this->config->item('rest_auth'); + $rest_realm = $this->config->item('rest_realm'); + if (strtolower($rest_auth) === 'basic') { + // See http://tools.ietf.org/html/rfc2617#page-5 + header('WWW-Authenticate: Basic realm="' . $rest_realm . '"'); + } elseif (strtolower($rest_auth) === 'digest') { + // See http://tools.ietf.org/html/rfc2617#page-18 + header( + 'WWW-Authenticate: Digest realm="' . $rest_realm + . '", qop="auth", nonce="' . $nonce + . '", opaque="' . md5($rest_realm) . '"'); + } + + if ($this->config->item('strict_api_and_auth') === true) { + $this->is_valid_request = false; + } + + // Display an error response + $this->response([ + $this->config->item('rest_status_field_name') => FALSE, + $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_unauthorized') + ], self::HTTP_UNAUTHORIZED); + } + + /** + * Updates the log table with the total access time + * + * @access protected + * @author Chris Kacerguis + * @return bool TRUE log table updated; otherwise, FALSE + */ + protected function _log_access_time() + { + if ($this->_insert_id == '') { + return false; + } + + $payload['rtime'] = $this->_end_rtime - $this->_start_rtime; + + return $this->rest->db->update( + $this->config->item('rest_logs_table'), $payload, [ + 'id' => $this->_insert_id + ]); + } + + /** + * Updates the log table with HTTP response code + * + * @access protected + * @author Justin Chen + * @param $http_code int HTTP status code + * @return bool TRUE log table updated; otherwise, FALSE + */ + protected function _log_response_code($http_code) + { + if ($this->_insert_id == '') { + return false; + } + + $payload['response_code'] = $http_code; + + return $this->rest->db->update( + $this->config->item('rest_logs_table'), $payload, [ + 'id' => $this->_insert_id + ]); + } + + /** + * Check to see if the API key has access to the controller and methods + * + * @access protected + * @return bool TRUE the API key has access; otherwise, FALSE + */ + protected function _check_access() + { + // If we don't want to check access, just return TRUE + if ($this->config->item('rest_enable_access') === FALSE) { + return TRUE; + } + + //check if the key has all_access + $accessRow = $this->rest->db + ->where('key', $this->rest->key) + ->get($this->config->item('rest_access_table'))->row_array(); + + if (!empty($accessRow) && !empty($accessRow['all_access'])) { + return TRUE; + } + + // Fetch controller based on path and controller name + $controller = implode( + '/', [ + $this->router->directory, + $this->router->class + ]); + + // Remove any double slashes for safety + $controller = str_replace('//', '/', $controller); + + // Query the access table and get the number of results + return $this->rest->db + ->where('key', $this->rest->key) + ->where('controller', $controller) + ->get($this->config->item('rest_access_table')) + ->num_rows() > 0; + } + + /** + * Checks allowed domains, and adds appropriate headers for HTTP access control (CORS) + * + * @access protected + * @return void + */ + protected function _check_cors() + { + // Convert the config items into strings + $allowed_headers = implode(', ', $this->config->item('allowed_cors_headers')); + $allowed_methods = implode(', ', $this->config->item('allowed_cors_methods')); + + // If we want to allow any domain to access the API + if ($this->config->item('allow_any_cors_domain') === TRUE) { + header('Access-Control-Allow-Origin: *'); + header('Access-Control-Allow-Headers: ' . $allowed_headers); + header('Access-Control-Allow-Methods: ' . $allowed_methods); + } else { + // We're going to allow only certain domains access + // Store the HTTP Origin header + $origin = $this->input->server('HTTP_ORIGIN'); + if ($origin === NULL) { + $origin = ''; + } + + // If the origin domain is in the allowed_cors_origins list, then add the Access Control headers + if (in_array($origin, $this->config->item('allowed_cors_origins'))) { + header('Access-Control-Allow-Origin: ' . $origin); + header('Access-Control-Allow-Headers: ' . $allowed_headers); + header('Access-Control-Allow-Methods: ' . $allowed_methods); + } + } + + // If there are headers that should be forced in the CORS check, add them now + if (is_array($this->config->item('forced_cors_headers'))) { + foreach ($this->config->item('forced_cors_headers') as $header => $value) { + header($header . ': ' . $value); + } + } + + // If the request HTTP method is 'OPTIONS', kill the response and send it to the client + if ($this->input->method() === 'options') { + // Load DB if needed for logging + if (!isset($this->rest->db) && $this->config->item('rest_enable_logging')) { + $this->rest->db = $this->load->database($this->config->item('rest_database_group'), TRUE); + } + exit; + } + } +} diff --git a/application/libraries/index.html b/application/libraries/index.html new file mode 100644 index 0000000..b702fbc --- /dev/null +++ b/application/libraries/index.html @@ -0,0 +1,11 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/logs/index.html b/application/logs/index.html new file mode 100644 index 0000000..b702fbc --- /dev/null +++ b/application/logs/index.html @@ -0,0 +1,11 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/models/index.html b/application/models/index.html new file mode 100644 index 0000000..b702fbc --- /dev/null +++ b/application/models/index.html @@ -0,0 +1,11 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/third_party/index.html b/application/third_party/index.html new file mode 100644 index 0000000..b702fbc --- /dev/null +++ b/application/third_party/index.html @@ -0,0 +1,11 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/views/errors/cli/error_404.php b/application/views/errors/cli/error_404.php new file mode 100644 index 0000000..6984b61 --- /dev/null +++ b/application/views/errors/cli/error_404.php @@ -0,0 +1,8 @@ + + +An uncaught Exception was encountered + +Type: +Message: +Filename: getFile(), "\n"; ?> +Line Number: getLine(); ?> + + + +Backtrace: +getTrace() as $error): ?> + + File: + Line: + Function: + + + + diff --git a/application/views/errors/cli/error_general.php b/application/views/errors/cli/error_general.php new file mode 100644 index 0000000..6984b61 --- /dev/null +++ b/application/views/errors/cli/error_general.php @@ -0,0 +1,8 @@ + + +A PHP Error was encountered + +Severity: +Message: +Filename: +Line Number: + + + +Backtrace: + + + File: + Line: + Function: + + + + diff --git a/application/views/errors/cli/index.html b/application/views/errors/cli/index.html new file mode 100644 index 0000000..b702fbc --- /dev/null +++ b/application/views/errors/cli/index.html @@ -0,0 +1,11 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/views/errors/html/error_404.php b/application/views/errors/html/error_404.php new file mode 100644 index 0000000..756ea9d --- /dev/null +++ b/application/views/errors/html/error_404.php @@ -0,0 +1,64 @@ + + + + +Type:
+Message:
+Filename: getFile(); ?>
+Line Number: getLine(); ?>
+ + + +Backtrace:
+ getTrace() as $error): ?> + + + +
+ File:
+ Line:
+ Function:
+
Severity:
+Message:
+Filename:
+Line Number:
+ + + +Backtrace:
+ + + + +
+ File:
+ Line:
+ Function:
+
Directory access is forbidden.
+ + + diff --git a/application/views/errors/index.html b/application/views/errors/index.html new file mode 100644 index 0000000..b702fbc --- /dev/null +++ b/application/views/errors/index.html @@ -0,0 +1,11 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/views/index.html b/application/views/index.html new file mode 100644 index 0000000..b702fbc --- /dev/null +++ b/application/views/index.html @@ -0,0 +1,11 @@ + + + +Directory access is forbidden.
+ + + diff --git a/application/views/rest_server.php b/application/views/rest_server.php new file mode 100644 index 0000000..5212e6d --- /dev/null +++ b/application/views/rest_server.php @@ -0,0 +1,222 @@ + + + + + + ++ See the article + + http://net.tutsplus.com/tutorials/php/working-with-restful-services-in-codeigniter-2/ + +
+ ++ The master project repository is + + https://github.com/chriskacerguis/codeigniter-restserver + +
+ ++ Click on the links to check whether the REST server is working. +
+ +The page you are looking at is being generated dynamically by CodeIgniter.
+ +If you would like to edit this page you'll find it located at:
+application/views/welcome_message.php
+
+ The corresponding controller for this page is found at:
+application/controllers/Welcome.php
+
+ If you are exploring CodeIgniter for the very first time, you should start by reading the User Guide.
+'.(is_array($message) ? implode('
', $message) : $message).'
'; + $template = 'html'.DIRECTORY_SEPARATOR.$template; + } + + if (ob_get_level() > $this->ob_level + 1) + { + ob_end_flush(); + } + ob_start(); + include($templates_path.$template.'.php'); + $buffer = ob_get_contents(); + ob_end_clean(); + return $buffer; + } + + // -------------------------------------------------------------------- + + public function show_exception($exception) + { + $templates_path = config_item('error_views_path'); + if (empty($templates_path)) + { + $templates_path = VIEWPATH.'errors'.DIRECTORY_SEPARATOR; + } + + $message = $exception->getMessage(); + if (empty($message)) + { + $message = '(null)'; + } + + if (is_cli()) + { + $templates_path .= 'cli'.DIRECTORY_SEPARATOR; + } + else + { + $templates_path .= 'html'.DIRECTORY_SEPARATOR; + } + + if (ob_get_level() > $this->ob_level + 1) + { + ob_end_flush(); + } + + ob_start(); + include($templates_path.'error_exception.php'); + $buffer = ob_get_contents(); + ob_end_clean(); + echo $buffer; + } + + // -------------------------------------------------------------------- + + /** + * Native PHP error handler + * + * @param int $severity Error level + * @param string $message Error message + * @param string $filepath File path + * @param int $line Line number + * @return void + */ + public function show_php_error($severity, $message, $filepath, $line) + { + $templates_path = config_item('error_views_path'); + if (empty($templates_path)) + { + $templates_path = VIEWPATH.'errors'.DIRECTORY_SEPARATOR; + } + + $severity = isset($this->levels[$severity]) ? $this->levels[$severity] : $severity; + + // For safety reasons we don't show the full file path in non-CLI requests + if ( ! is_cli()) + { + $filepath = str_replace('\\', '/', $filepath); + if (FALSE !== strpos($filepath, '/')) + { + $x = explode('/', $filepath); + $filepath = $x[count($x)-2].'/'.end($x); + } + + $template = 'html'.DIRECTORY_SEPARATOR.'error_php'; + } + else + { + $template = 'cli'.DIRECTORY_SEPARATOR.'error_php'; + } + + if (ob_get_level() > $this->ob_level + 1) + { + ob_end_flush(); + } + ob_start(); + include($templates_path.$template.'.php'); + $buffer = ob_get_contents(); + ob_end_clean(); + echo $buffer; + } + +} diff --git a/system/core/Hooks.php b/system/core/Hooks.php new file mode 100644 index 0000000..82c68b1 --- /dev/null +++ b/system/core/Hooks.php @@ -0,0 +1,266 @@ +item('enable_hooks') === FALSE) + { + return; + } + + // Grab the "hooks" definition file. + if (file_exists(APPPATH.'config/hooks.php')) + { + include(APPPATH.'config/hooks.php'); + } + + if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/hooks.php')) + { + include(APPPATH.'config/'.ENVIRONMENT.'/hooks.php'); + } + + // If there are no hooks, we're done. + if ( ! isset($hook) OR ! is_array($hook)) + { + return; + } + + $this->hooks =& $hook; + $this->enabled = TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Call Hook + * + * Calls a particular hook. Called by CodeIgniter.php. + * + * @uses CI_Hooks::_run_hook() + * + * @param string $which Hook name + * @return bool TRUE on success or FALSE on failure + */ + public function call_hook($which = '') + { + if ( ! $this->enabled OR ! isset($this->hooks[$which])) + { + return FALSE; + } + + if (is_array($this->hooks[$which]) && ! isset($this->hooks[$which]['function'])) + { + foreach ($this->hooks[$which] as $val) + { + $this->_run_hook($val); + } + } + else + { + $this->_run_hook($this->hooks[$which]); + } + + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Run Hook + * + * Runs a particular hook + * + * @param array $data Hook details + * @return bool TRUE on success or FALSE on failure + */ + protected function _run_hook($data) + { + // Closures/lambda functions and array($object, 'method') callables + if (is_callable($data)) + { + is_array($data) + ? $data[0]->{$data[1]}() + : $data(); + + return TRUE; + } + elseif ( ! is_array($data)) + { + return FALSE; + } + + // ----------------------------------- + // Safety - Prevents run-away loops + // ----------------------------------- + + // If the script being called happens to have the same + // hook call within it a loop can happen + if ($this->_in_progress === TRUE) + { + return; + } + + // ----------------------------------- + // Set file path + // ----------------------------------- + + if ( ! isset($data['filepath'], $data['filename'])) + { + return FALSE; + } + + $filepath = APPPATH.$data['filepath'].'/'.$data['filename']; + + if ( ! file_exists($filepath)) + { + return FALSE; + } + + // 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 (empty($function)) + { + return FALSE; + } + + // Set the _in_progress flag + $this->_in_progress = TRUE; + + // Call the requested class and/or function + if ($class !== FALSE) + { + // The object is stored? + if (isset($this->_objects[$class])) + { + if (method_exists($this->_objects[$class], $function)) + { + $this->_objects[$class]->$function($params); + } + else + { + return $this->_in_progress = FALSE; + } + } + else + { + class_exists($class, FALSE) OR require_once($filepath); + + if ( ! class_exists($class, FALSE) OR ! method_exists($class, $function)) + { + return $this->_in_progress = FALSE; + } + + // Store the object and execute the method + $this->_objects[$class] = new $class(); + $this->_objects[$class]->$function($params); + } + } + else + { + function_exists($function) OR require_once($filepath); + + if ( ! function_exists($function)) + { + return $this->_in_progress = FALSE; + } + + $function($params); + } + + $this->_in_progress = FALSE; + return TRUE; + } + +} diff --git a/system/core/Input.php b/system/core/Input.php new file mode 100644 index 0000000..143babf --- /dev/null +++ b/system/core/Input.php @@ -0,0 +1,895 @@ +_allow_get_array = (config_item('allow_get_array') !== FALSE); + $this->_enable_xss = (config_item('global_xss_filtering') === TRUE); + $this->_enable_csrf = (config_item('csrf_protection') === TRUE); + $this->_standardize_newlines = (bool) config_item('standardize_newlines'); + + $this->security =& load_class('Security', 'core'); + + // Do we need the UTF-8 class? + if (UTF8_ENABLED === TRUE) + { + $this->uni =& load_class('Utf8', 'core'); + } + + // Sanitize global arrays + $this->_sanitize_globals(); + + // CSRF Protection check + if ($this->_enable_csrf === TRUE && ! is_cli()) + { + $this->security->csrf_verify(); + } + + log_message('info', 'Input Class Initialized'); + } + + // -------------------------------------------------------------------- + + /** + * Fetch from array + * + * Internal method used to retrieve values from global arrays. + * + * @param array &$array $_GET, $_POST, $_COOKIE, $_SERVER, etc. + * @param mixed $index Index for item to be fetched from $array + * @param bool $xss_clean Whether to apply XSS filtering + * @return mixed + */ + protected function _fetch_from_array(&$array, $index = NULL, $xss_clean = NULL) + { + is_bool($xss_clean) OR $xss_clean = $this->_enable_xss; + + // If $index is NULL, it means that the whole $array is requested + isset($index) OR $index = array_keys($array); + + // allow fetching multiple keys at once + if (is_array($index)) + { + $output = array(); + foreach ($index as $key) + { + $output[$key] = $this->_fetch_from_array($array, $key, $xss_clean); + } + + return $output; + } + + if (isset($array[$index])) + { + $value = $array[$index]; + } + elseif (($count = preg_match_all('/(?:^[^\[]+)|\[[^]]*\]/', $index, $matches)) > 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 (isset($value[$key])) + { + $value = $value[$key]; + } + else + { + return NULL; + } + } + } + else + { + return NULL; + } + + return ($xss_clean === TRUE) + ? $this->security->xss_clean($value) + : $value; + } + + // -------------------------------------------------------------------- + + /** + * Fetch an item from the GET array + * + * @param mixed $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 = NULL) + { + return $this->_fetch_from_array($_GET, $index, $xss_clean); + } + + // -------------------------------------------------------------------- + + /** + * Fetch an item from the POST array + * + * @param mixed $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 = NULL) + { + return $this->_fetch_from_array($_POST, $index, $xss_clean); + } + + // -------------------------------------------------------------------- + + /** + * Fetch an item from POST data with fallback to GET + * + * @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 post_get($index, $xss_clean = NULL) + { + return isset($_POST[$index]) + ? $this->post($index, $xss_clean) + : $this->get($index, $xss_clean); + } + + // -------------------------------------------------------------------- + + /** + * 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 = NULL) + { + return isset($_GET[$index]) + ? $this->get($index, $xss_clean) + : $this->post($index, $xss_clean); + } + + // -------------------------------------------------------------------- + + /** + * Fetch an item from the COOKIE array + * + * @param mixed $index Index for item to be fetched from $_COOKIE + * @param bool $xss_clean Whether to apply XSS filtering + * @return mixed + */ + public function cookie($index = NULL, $xss_clean = NULL) + { + return $this->_fetch_from_array($_COOKIE, $index, $xss_clean); + } + + // -------------------------------------------------------------------- + + /** + * Fetch an item from the SERVER array + * + * @param mixed $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 = NULL) + { + 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 = NULL, $xss_clean = NULL) + { + // Prior to PHP 5.6, 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)) + { + // $this->raw_input_stream will trigger __get(). + parse_str($this->raw_input_stream, $this->_input_stream); + is_array($this->_input_stream) OR $this->_input_stream = array(); + } + + return $this->_fetch_from_array($this->_input_stream, $index, $xss_clean); + } + + // ------------------------------------------------------------------------ + + /** + * Set cookie + * + * Accepts an arbitrary number of parameters (up to 7) or an associative + * array in the first parameter containing all the values. + * + * @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 = NULL, $httponly = NULL) + { + if (is_array($name)) + { + // always leave 'name' in last place, as the loop will break otherwise, due to $$item + foreach (array('value', 'expire', 'domain', 'path', 'prefix', 'secure', 'httponly', 'name') as $item) + { + if (isset($name[$item])) + { + $$item = $name[$item]; + } + } + } + + if ($prefix === '' && config_item('cookie_prefix') !== '') + { + $prefix = config_item('cookie_prefix'); + } + + if ($domain == '' && config_item('cookie_domain') != '') + { + $domain = config_item('cookie_domain'); + } + + if ($path === '/' && config_item('cookie_path') !== '/') + { + $path = config_item('cookie_path'); + } + + $secure = ($secure === NULL && config_item('cookie_secure') !== NULL) + ? (bool) config_item('cookie_secure') + : (bool) $secure; + + $httponly = ($httponly === NULL && config_item('cookie_httponly') !== NULL) + ? (bool) config_item('cookie_httponly') + : (bool) $httponly; + + if ( ! is_numeric($expire)) + { + $expire = time() - 86500; + } + else + { + $expire = ($expire > 0) ? time() + $expire : 0; + } + + setcookie($prefix.$name, $value, $expire, $path, $domain, $secure, $httponly); + } + + // -------------------------------------------------------------------- + + /** + * Fetch the IP Address + * + * Determines and validates the visitor's IP address. + * + * @return string IP address + */ + public function ip_address() + { + if ($this->ip_address !== FALSE) + { + return $this->ip_address; + } + + $proxy_ips = config_item('proxy_ips'); + if ( ! empty($proxy_ips) && ! is_array($proxy_ips)) + { + $proxy_ips = explode(',', str_replace(' ', '', $proxy_ips)); + } + + $this->ip_address = $this->server('REMOTE_ADDR'); + + 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) + { + // 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); + + if ( ! $this->valid_ip($spoof)) + { + $spoof = NULL; + } + else + { + break; + } + } + } + + 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; + } + + // 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 ($j = 0; $j < 8; $j++) + { + $ip[$j] = intval($ip[$j], 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 ($j = 0; $j < 8; $j++) + { + $netaddr[$j] = intval($netaddr[$j], 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)) + { + return $this->ip_address = '0.0.0.0'; + } + + return $this->ip_address; + } + + // -------------------------------------------------------------------- + + /** + * Validate IP Address + * + * @param string $ip IP address + * @param string $which IP protocol: 'ipv4' or 'ipv6' + * @return bool + */ + public function valid_ip($ip, $which = '') + { + switch (strtolower($which)) + { + case 'ipv4': + $which = FILTER_FLAG_IPV4; + break; + case 'ipv6': + $which = FILTER_FLAG_IPV6; + break; + default: + $which = NULL; + break; + } + + return (bool) filter_var($ip, FILTER_VALIDATE_IP, $which); + } + + // -------------------------------------------------------------------- + + /** + * Fetch User Agent string + * + * @return string|null User Agent string or NULL if it doesn't exist + */ + public function user_agent($xss_clean = NULL) + { + return $this->_fetch_from_array($_SERVER, 'HTTP_USER_AGENT', $xss_clean); + } + + // -------------------------------------------------------------------- + + /** + * Sanitize Globals + * + * Internal method serving for the following purposes: + * + * - Unsets $_GET data, if query strings are not enabled + * - Cleans POST, COOKIE and SERVER data + * - Standardizes newline characters to PHP_EOL + * + * @return void + */ + protected function _sanitize_globals() + { + // Is $_GET data allowed? If not we'll set the $_GET to an empty array + if ($this->_allow_get_array === FALSE) + { + $_GET = array(); + } + elseif (is_array($_GET)) + { + foreach ($_GET as $key => $val) + { + $_GET[$this->_clean_input_keys($key)] = $this->_clean_input_data($val); + } + } + + // Clean $_POST Data + if (is_array($_POST)) + { + foreach ($_POST as $key => $val) + { + $_POST[$this->_clean_input_keys($key)] = $this->_clean_input_data($val); + } + } + + // Clean $_COOKIE Data + if (is_array($_COOKIE)) + { + // Also get rid of specially treated cookies that might be set by a server + // or silly application, that are of no use to a CI application anyway + // but that when present will trip our 'Disallowed Key Characters' alarm + // http://www.ietf.org/rfc/rfc2109.txt + // note that the key names below are single quoted strings, and are not PHP variables + unset( + $_COOKIE['$Version'], + $_COOKIE['$Path'], + $_COOKIE['$Domain'] + ); + + foreach ($_COOKIE as $key => $val) + { + if (($cookie_key = $this->_clean_input_keys($key)) !== FALSE) + { + $_COOKIE[$cookie_key] = $this->_clean_input_data($val); + } + else + { + unset($_COOKIE[$key]); + } + } + } + + // Sanitize PHP_SELF + $_SERVER['PHP_SELF'] = strip_tags($_SERVER['PHP_SELF']); + + log_message('debug', 'Global POST, GET and COOKIE data sanitized'); + } + + // -------------------------------------------------------------------- + + /** + * Clean Input Data + * + * Internal method that aids in escaping data and + * standardizing newline characters to PHP_EOL. + * + * @param string|string[] $str Input string(s) + * @return string + */ + protected function _clean_input_data($str) + { + if (is_array($str)) + { + $new_array = array(); + foreach (array_keys($str) as $key) + { + $new_array[$this->_clean_input_keys($key)] = $this->_clean_input_data($str[$key]); + } + return $new_array; + } + + /* We strip slashes if magic quotes is on to keep things consistent + + NOTE: In PHP 5.4 get_magic_quotes_gpc() will always return 0 and + it will probably not exist in future versions at all. + */ + if ( ! is_php('5.4') && get_magic_quotes_gpc()) + { + $str = stripslashes($str); + } + + // Clean UTF-8 if supported + if (UTF8_ENABLED === TRUE) + { + $str = $this->uni->clean_string($str); + } + + // Remove control characters + $str = remove_invisible_characters($str, FALSE); + + // Standardize newlines if needed + if ($this->_standardize_newlines === TRUE) + { + return preg_replace('/(?:\r\n|[\r\n])/', PHP_EOL, $str); + } + + return $str; + } + + // -------------------------------------------------------------------- + + /** + * Clean Keys + * + * 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 $str Input string + * @param bool $fatal Whether to terminate script exection + * or to return FALSE if an invalid + * key is encountered + * @return string|bool + */ + protected function _clean_input_keys($str, $fatal = TRUE) + { + if ( ! preg_match('/^[a-z0-9:_\/|-]+$/i', $str)) + { + if ($fatal === TRUE) + { + return FALSE; + } + else + { + set_status_header(503); + echo 'Disallowed Key Characters.'; + exit(7); // EXIT_USER_INPUT + } + } + + // Clean UTF-8 if supported + if (UTF8_ENABLED === TRUE) + { + return $this->uni->clean_string($str); + } + + return $str; + } + + // -------------------------------------------------------------------- + + /** + * Request Headers + * + * @param bool $xss_clean Whether to apply XSS filtering + * @return array + */ + public function request_headers($xss_clean = FALSE) + { + // If header is already defined, return it immediately + if ( ! empty($this->headers)) + { + return $this->_fetch_from_array($this->headers, NULL, $xss_clean); + } + + // In Apache, you can simply call apache_request_headers() + if (function_exists('apache_request_headers')) + { + $this->headers = apache_request_headers(); + } + else + { + isset($_SERVER['CONTENT_TYPE']) && $this->headers['Content-Type'] = $_SERVER['CONTENT_TYPE']; + + foreach ($_SERVER as $key => $val) + { + 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[$header] = $_SERVER[$key]; + } + } + } + + return $this->_fetch_from_array($this->headers, NULL, $xss_clean); + } + + // -------------------------------------------------------------------- + + /** + * Get Request Header + * + * Returns the value of a single member of the headers class member + * + * @param string $index Header name + * @param bool $xss_clean Whether to apply XSS filtering + * @return string|null The requested header on success or NULL on failure + */ + public function get_request_header($index, $xss_clean = FALSE) + { + static $headers; + + if ( ! isset($headers)) + { + empty($this->headers) && $this->request_headers(); + foreach ($this->headers as $key => $value) + { + $headers[strtolower($key)] = $value; + } + } + + $index = strtolower($index); + + if ( ! isset($headers[$index])) + { + return NULL; + } + + return ($xss_clean === TRUE) + ? $this->security->xss_clean($headers[$index]) + : $headers[$index]; + } + + // -------------------------------------------------------------------- + + /** + * Is AJAX request? + * + * Test to see if a request contains the HTTP_X_REQUESTED_WITH header. + * + * @return bool + */ + public function is_ajax_request() + { + return ( ! empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'); + } + + // -------------------------------------------------------------------- + + /** + * Is CLI request? + * + * Test to see if a request was made from the command line. + * + * @deprecated 3.0.0 Use is_cli() instead + * @return bool + */ + public function is_cli_request() + { + return is_cli(); + } + + // -------------------------------------------------------------------- + + /** + * Get Request Method + * + * Return the request method + * + * @param bool $upper Whether to return in upper or lower case + * (default: FALSE) + * @return string + */ + public function method($upper = FALSE) + { + return ($upper) + ? strtoupper($this->server('REQUEST_METHOD')) + : strtolower($this->server('REQUEST_METHOD')); + } + + // ------------------------------------------------------------------------ + + /** + * Magic __get() + * + * Allows read access to protected properties + * + * @param string $name + * @return mixed + */ + public function __get($name) + { + if ($name === 'raw_input_stream') + { + isset($this->_raw_input_stream) OR $this->_raw_input_stream = file_get_contents('php://input'); + return $this->_raw_input_stream; + } + elseif ($name === 'ip_address') + { + return $this->ip_address; + } + } + +} diff --git a/system/core/Lang.php b/system/core/Lang.php new file mode 100644 index 0000000..39fdd06 --- /dev/null +++ b/system/core/Lang.php @@ -0,0 +1,203 @@ +load($value, $idiom, $return, $add_suffix, $alt_path); + } + + return; + } + + $langfile = str_replace('.php', '', $langfile); + + if ($add_suffix === TRUE) + { + $langfile = preg_replace('/_lang$/', '', $langfile).'_lang'; + } + + $langfile .= '.php'; + + if (empty($idiom) OR ! preg_match('/^[a-z_-]+$/i', $idiom)) + { + $config =& get_config(); + $idiom = empty($config['language']) ? 'english' : $config['language']; + } + + if ($return === FALSE && isset($this->is_loaded[$langfile]) && $this->is_loaded[$langfile] === $idiom) + { + return; + } + + // Load the base file, so any others found can override it + $basepath = BASEPATH.'language/'.$idiom.'/'.$langfile; + if (($found = file_exists($basepath)) === TRUE) + { + 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 + { + foreach (get_instance()->load->get_package_paths(TRUE) as $package_path) + { + $package_path .= 'language/'.$idiom.'/'.$langfile; + if ($basepath !== $package_path && file_exists($package_path)) + { + include($package_path); + $found = TRUE; + break; + } + } + } + + if ($found !== TRUE) + { + show_error('Unable to load the requested language file: language/'.$idiom.'/'.$langfile); + } + + if ( ! isset($lang) OR ! is_array($lang)) + { + log_message('error', 'Language file contains no data: language/'.$idiom.'/'.$langfile); + + if ($return === TRUE) + { + return array(); + } + return; + } + + if ($return === TRUE) + { + return $lang; + } + + $this->is_loaded[$langfile] = $idiom; + $this->language = array_merge($this->language, $lang); + + log_message('info', 'Language file loaded: language/'.$idiom.'/'.$langfile); + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Language line + * + * Fetches a single line of text from the language array + * + * @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, $log_errors = TRUE) + { + $value = isset($this->language[$line]) ? $this->language[$line] : FALSE; + + // Because killer robots like unicorns! + if ($value === FALSE && $log_errors === TRUE) + { + log_message('error', 'Could not find the language line "'.$line.'"'); + } + + return $value; + } + +} diff --git a/system/core/Loader.php b/system/core/Loader.php new file mode 100644 index 0000000..c84aff3 --- /dev/null +++ b/system/core/Loader.php @@ -0,0 +1,1415 @@ + TRUE); + + /** + * List of paths to load libraries from + * + * @var array + */ + protected $_ci_library_paths = array(APPPATH, BASEPATH); + + /** + * List of paths to load models from + * + * @var array + */ + protected $_ci_model_paths = array(APPPATH); + + /** + * List of paths to load helpers from + * + * @var array + */ + protected $_ci_helper_paths = array(APPPATH, BASEPATH); + + /** + * List of cached variables + * + * @var array + */ + protected $_ci_cached_vars = array(); + + /** + * List of loaded classes + * + * @var array + */ + protected $_ci_classes = array(); + + /** + * List of loaded models + * + * @var array + */ + protected $_ci_models = array(); + + /** + * List of loaded helpers + * + * @var array + */ + protected $_ci_helpers = array(); + + /** + * List of class name mappings + * + * @var array + */ + protected $_ci_varmap = array( + 'unit_test' => 'unit', + 'user_agent' => 'agent' + ); + + // -------------------------------------------------------------------- + + /** + * Class constructor + * + * Sets component load paths, gets the initial output buffering level. + * + * @return void + */ + public function __construct() + { + $this->_ci_ob_level = ob_get_level(); + $this->_ci_classes =& is_loaded(); + + log_message('info', 'Loader Class Initialized'); + } + + // -------------------------------------------------------------------- + + /** + * Initializer + * + * @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_autoloader(); + } + + // -------------------------------------------------------------------- + + /** + * Is Loaded + * + * A utility method to test if a class is in the self::$_ci_classes array. + * + * @used-by Mainly used by Form Helper function _get_validation_object(). + * + * @param string $class Class name to check for + * @return string|bool Class object name if loaded or FALSE + */ + public function is_loaded($class) + { + return array_search(ucfirst($class), $this->_ci_classes, TRUE); + } + + // -------------------------------------------------------------------- + + /** + * Library Loader + * + * Loads and instantiates libraries. + * Designed to be called from application controllers. + * + * @param mixed $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 object + */ + public function library($library, $params = NULL, $object_name = NULL) + { + if (empty($library)) + { + return $this; + } + elseif (is_array($library)) + { + foreach ($library as $key => $value) + { + if (is_int($key)) + { + $this->library($value, $params); + } + else + { + $this->library($key, $params, $value); + } + } + + return $this; + } + + if ($params !== NULL && ! is_array($params)) + { + $params = NULL; + } + + $this->_ci_load_library($library, $params, $object_name); + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Model Loader + * + * Loads and instantiates models. + * + * @param mixed $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 object + */ + public function model($model, $name = '', $db_conn = FALSE) + { + if (empty($model)) + { + return $this; + } + elseif (is_array($model)) + { + foreach ($model as $key => $value) + { + is_int($key) ? $this->model($value, '', $db_conn) : $this->model($key, $value, $db_conn); + } + + return $this; + } + + $path = ''; + + // Is the model in a sub-folder? If so, parse out the filename and path. + if (($last_slash = strrpos($model, '/')) !== FALSE) + { + // The path is in front of the last slash + $path = substr($model, 0, ++$last_slash); + + // And the model name behind it + $model = substr($model, $last_slash); + } + + if (empty($name)) + { + $name = $model; + } + + if (in_array($name, $this->_ci_models, TRUE)) + { + return $this; + } + + $CI =& get_instance(); + if (isset($CI->$name)) + { + throw new RuntimeException('The model name you are loading is the name of a resource that is already being used: '.$name); + } + + if ($db_conn !== FALSE && ! class_exists('CI_DB', FALSE)) + { + if ($db_conn === TRUE) + { + $db_conn = ''; + } + + $this->database($db_conn, FALSE, TRUE); + } + + // Note: All of the code under this condition used to be just: + // + // load_class('Model', 'core'); + // + // However, load_class() instantiates classes + // to cache them for later use and that prevents + // MY_Model from being an abstract class and is + // sub-optimal otherwise anyway. + if ( ! class_exists('CI_Model', FALSE)) + { + $app_path = APPPATH.'core'.DIRECTORY_SEPARATOR; + if (file_exists($app_path.'Model.php')) + { + require_once($app_path.'Model.php'); + if ( ! class_exists('CI_Model', FALSE)) + { + throw new RuntimeException($app_path."Model.php exists, but doesn't declare class CI_Model"); + } + + log_message('info', 'CI_Model class loaded'); + } + elseif ( ! class_exists('CI_Model', FALSE)) + { + require_once(BASEPATH.'core'.DIRECTORY_SEPARATOR.'Model.php'); + } + + $class = config_item('subclass_prefix').'Model'; + if (file_exists($app_path.$class.'.php')) + { + require_once($app_path.$class.'.php'); + if ( ! class_exists($class, FALSE)) + { + throw new RuntimeException($app_path.$class.".php exists, but doesn't declare class ".$class); + } + + log_message('info', config_item('subclass_prefix').'Model class loaded'); + } + } + + $model = ucfirst($model); + if ( ! class_exists($model, FALSE)) + { + foreach ($this->_ci_model_paths as $mod_path) + { + if ( ! file_exists($mod_path.'models/'.$path.$model.'.php')) + { + continue; + } + + require_once($mod_path.'models/'.$path.$model.'.php'); + if ( ! class_exists($model, FALSE)) + { + throw new RuntimeException($mod_path."models/".$path.$model.".php exists, but doesn't declare class ".$model); + } + + break; + } + + if ( ! class_exists($model, FALSE)) + { + throw new RuntimeException('Unable to locate the model you have specified: '.$model); + } + } + elseif ( ! is_subclass_of($model, 'CI_Model')) + { + throw new RuntimeException("Class ".$model." already exists and doesn't extend CI_Model"); + } + + $this->_ci_models[] = $name; + $model = new $model(); + $CI->$name = $model; + log_message('info', 'Model "'.get_class($model).'" initialized'); + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Database Loader + * + * @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 object|bool Database object if $return is set to TRUE, + * FALSE on failure, CI_Loader instance in any other case + */ + public function database($params = '', $return = FALSE, $query_builder = NULL) + { + // Grab the super object + $CI =& get_instance(); + + // Do we even need to load the database class? + if ($return === FALSE && $query_builder === NULL && isset($CI->db) && is_object($CI->db) && ! empty($CI->db->conn_id)) + { + return FALSE; + } + + require_once(BASEPATH.'database/DB.php'); + + if ($return === TRUE) + { + return DB($params, $query_builder); + } + + // Initialize the db variable. Needed to prevent + // reference errors with some configurations + $CI->db = ''; + + // Load the DB class + $CI->db =& DB($params, $query_builder); + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Load the Database Utilities Class + * + * @param object $db Database object + * @param bool $return Whether to return the DB Utilities class object or not + * @return object + */ + public function dbutil($db = NULL, $return = FALSE) + { + $CI =& get_instance(); + + 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/'.$db->dbdriver.'/'.$db->dbdriver.'_utility.php'); + $class = 'CI_DB_'.$db->dbdriver.'_utility'; + + if ($return === TRUE) + { + return new $class($db); + } + + $CI->dbutil = new $class($db); + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Load the Database Forge Class + * + * @param object $db Database object + * @param bool $return Whether to return the DB Forge class object or not + * @return object + */ + public function dbforge($db = NULL, $return = FALSE) + { + $CI =& get_instance(); + 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_forge.php'); + require_once(BASEPATH.'database/drivers/'.$db->dbdriver.'/'.$db->dbdriver.'_forge.php'); + + 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); + return $this; + } + + // -------------------------------------------------------------------- + + /** + * View Loader + * + * Loads "view" files. + * + * @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 object|string + */ + public function view($view, $vars = array(), $return = FALSE) + { + return $this->_ci_load(array('_ci_view' => $view, '_ci_vars' => $this->_ci_prepare_view_vars($vars), '_ci_return' => $return)); + } + + // -------------------------------------------------------------------- + + /** + * Generic File Loader + * + * @param string $path File path + * @param bool $return Whether to return the file output + * @return object|string + */ + public function file($path, $return = FALSE) + { + return $this->_ci_load(array('_ci_path' => $path, '_ci_return' => $return)); + } + + // -------------------------------------------------------------------- + + /** + * Set Variables + * + * Once variables are set they become available within + * the controller class and its "view" files. + * + * @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 object + */ + public function vars($vars, $val = '') + { + $vars = is_string($vars) + ? array($vars => $val) + : $this->_ci_prepare_view_vars($vars); + + foreach ($vars as $key => $val) + { + $this->_ci_cached_vars[$key] = $val; + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Clear Cached Variables + * + * Clears the cached variables. + * + * @return CI_Loader + */ + public function clear_vars() + { + $this->_ci_cached_vars = array(); + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Get Variable + * + * Check if a variable is set and retrieve it. + * + * @param string $key Variable name + * @return mixed The variable or NULL if not found + */ + public function get_var($key) + { + return isset($this->_ci_cached_vars[$key]) ? $this->_ci_cached_vars[$key] : NULL; + } + + // -------------------------------------------------------------------- + + /** + * Get Variables + * + * Retrieves all loaded variables. + * + * @return array + */ + public function get_vars() + { + return $this->_ci_cached_vars; + } + + // -------------------------------------------------------------------- + + /** + * Helper Loader + * + * @param string|string[] $helpers Helper name(s) + * @return object + */ + public function helper($helpers = array()) + { + is_array($helpers) OR $helpers = array($helpers); + foreach ($helpers as &$helper) + { + $filename = basename($helper); + $filepath = ($filename === $helper) ? '' : substr($helper, 0, strlen($helper) - strlen($filename)); + $filename = strtolower(preg_replace('#(_helper)?(\.php)?$#i', '', $filename)).'_helper'; + $helper = $filepath.$filename; + + if (isset($this->_ci_helpers[$helper])) + { + continue; + } + + // Is this a helper extension request? + $ext_helper = config_item('subclass_prefix').$filename; + $ext_loaded = FALSE; + foreach ($this->_ci_helper_paths as $path) + { + 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($base_helper); + $this->_ci_helpers[$helper] = TRUE; + log_message('info', 'Helper loaded: '.$helper); + continue; + } + + // No extensions found ... try loading regular helpers and/or overrides + foreach ($this->_ci_helper_paths as $path) + { + if (file_exists($path.'helpers/'.$helper.'.php')) + { + include_once($path.'helpers/'.$helper.'.php'); + + $this->_ci_helpers[$helper] = TRUE; + log_message('info', 'Helper loaded: '.$helper); + break; + } + } + + // unable to load the helper + if ( ! isset($this->_ci_helpers[$helper])) + { + show_error('Unable to load the requested file: helpers/'.$helper.'.php'); + } + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Load Helpers + * + * An alias for the helper() method in case the developer has + * written the plural form of it. + * + * @uses CI_Loader::helper() + * @param string|string[] $helpers Helper name(s) + * @return object + */ + public function helpers($helpers = array()) + { + return $this->helper($helpers); + } + + // -------------------------------------------------------------------- + + /** + * Language Loader + * + * Loads language files. + * + * @param string|string[] $files List of language file names to load + * @param string Language name + * @return object + */ + public function language($files, $lang = '') + { + get_instance()->lang->load($files, $lang); + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Config Loader + * + * 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) + { + return get_instance()->config->load($file, $use_sections, $fail_gracefully); + } + + // -------------------------------------------------------------------- + + /** + * Driver Loader + * + * Loads a driver library. + * + * @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 object|bool Object or FALSE on failure if $library is a string + * and $object_name is set. CI_Loader instance otherwise. + */ + public function driver($library, $params = NULL, $object_name = NULL) + { + if (is_array($library)) + { + foreach ($library as $key => $value) + { + if (is_int($key)) + { + $this->driver($value, $params); + } + else + { + $this->driver($key, $params, $value); + } + } + + return $this; + } + elseif (empty($library)) + { + return FALSE; + } + + if ( ! class_exists('CI_Driver_Library', 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, + // and typically identically named to the library + if ( ! strpos($library, '/')) + { + $library = ucfirst($library).'/'.$library; + } + + return $this->library($library, $params, $object_name); + } + + // -------------------------------------------------------------------- + + /** + * Add Package Path + * + * Prepends a parent path to the library, model, helper and config + * path arrays. + * + * @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 object + */ + public function add_package_path($path, $view_cascade = TRUE) + { + $path = rtrim($path, '/').'/'; + + array_unshift($this->_ci_library_paths, $path); + array_unshift($this->_ci_model_paths, $path); + array_unshift($this->_ci_helper_paths, $path); + + $this->_ci_view_paths = array($path.'views/' => $view_cascade) + $this->_ci_view_paths; + + // Add config file path + $config =& $this->_ci_get_component('config'); + $config->_config_paths[] = $path; + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Get Package Paths + * + * Return a list of all package paths. + * + * @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; + } + + // -------------------------------------------------------------------- + + /** + * Remove Package Path + * + * 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 $path Path to remove + * @return object + */ + public function remove_package_path($path = '') + { + $config =& $this->_ci_get_component('config'); + + if ($path === '') + { + array_shift($this->_ci_library_paths); + array_shift($this->_ci_model_paths); + array_shift($this->_ci_helper_paths); + array_shift($this->_ci_view_paths); + array_pop($config->_config_paths); + } + else + { + $path = rtrim($path, '/').'/'; + foreach (array('_ci_library_paths', '_ci_model_paths', '_ci_helper_paths') as $var) + { + if (($key = array_search($path, $this->{$var})) !== FALSE) + { + unset($this->{$var}[$key]); + } + } + + if (isset($this->_ci_view_paths[$path.'views/'])) + { + unset($this->_ci_view_paths[$path.'views/']); + } + + if (($key = array_search($path, $config->_config_paths)) !== FALSE) + { + unset($config->_config_paths[$key]); + } + } + + // make sure the application default paths are still in the array + $this->_ci_library_paths = array_unique(array_merge($this->_ci_library_paths, array(APPPATH, BASEPATH))); + $this->_ci_helper_paths = array_unique(array_merge($this->_ci_helper_paths, array(APPPATH, BASEPATH))); + $this->_ci_model_paths = array_unique(array_merge($this->_ci_model_paths, array(APPPATH))); + $this->_ci_view_paths = array_merge($this->_ci_view_paths, array(APPPATH.'views/' => TRUE)); + $config->_config_paths = array_unique(array_merge($config->_config_paths, array(APPPATH))); + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Internal CI Data Loader + * + * Used to load views and files. + * + * Variables are prefixed with _ci_ to avoid symbol collision with + * variables made available to view files. + * + * @used-by CI_Loader::view() + * @used-by CI_Loader::file() + * @param array $_ci_data Data to load + * @return object + */ + protected function _ci_load($_ci_data) + { + // Set the default data variables + foreach (array('_ci_view', '_ci_vars', '_ci_path', '_ci_return') as $_ci_val) + { + $$_ci_val = isset($_ci_data[$_ci_val]) ? $_ci_data[$_ci_val] : FALSE; + } + + $file_exists = FALSE; + + // Set the path to the requested file + if (is_string($_ci_path) && $_ci_path !== '') + { + $_ci_x = explode('/', $_ci_path); + $_ci_file = end($_ci_x); + } + else + { + $_ci_ext = pathinfo($_ci_view, PATHINFO_EXTENSION); + $_ci_file = ($_ci_ext === '') ? $_ci_view.'.php' : $_ci_view; + + foreach ($this->_ci_view_paths as $_ci_view_file => $cascade) + { + if (file_exists($_ci_view_file.$_ci_file)) + { + $_ci_path = $_ci_view_file.$_ci_file; + $file_exists = TRUE; + break; + } + + if ( ! $cascade) + { + break; + } + } + } + + if ( ! $file_exists && ! file_exists($_ci_path)) + { + show_error('Unable to load the requested file: '.$_ci_file); + } + + // This allows anything loaded using $this->load (views, files, etc.) + // to become accessible from within the Controller and Model functions. + $_ci_CI =& get_instance(); + foreach (get_object_vars($_ci_CI) as $_ci_key => $_ci_var) + { + if ( ! isset($this->$_ci_key)) + { + $this->$_ci_key =& $_ci_CI->$_ci_key; + } + } + + /* + * Extract and cache variables + * + * You can either set variables using the dedicated $this->load->vars() + * function or via the second parameter of this function. We'll merge + * the two types and cache them so that views that are embedded within + * other views can have access to these variables. + */ + empty($_ci_vars) OR $this->_ci_cached_vars = array_merge($this->_ci_cached_vars, $_ci_vars); + extract($this->_ci_cached_vars); + + /* + * Buffer the output + * + * 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. + */ + 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') && ! ini_get('short_open_tag') && config_item('rewrite_short_tags') === TRUE) + { + echo eval('?>'.preg_replace('/;*\s*\?>/', '; ?>', str_replace('=', ' $this->_ci_ob_level + 1) + { + ob_end_flush(); + } + else + { + $_ci_CI->output->append_output(ob_get_contents()); + @ob_end_clean(); + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Internal CI Library Loader + * + * @used-by CI_Loader::library() + * @uses CI_Loader::_ci_init_library() + * + * @param string $class Class name to load + * @param mixed $params Optional parameters to pass to the class constructor + * @param string $object_name Optional object name to assign to + * @return void + */ + protected function _ci_load_library($class, $params = NULL, $object_name = NULL) + { + // Get the class name, and while we're at it trim any slashes. + // The directory path can be included as part of the class name, + // but we don't want a leading slash + $class = str_replace('.php', '', trim($class, '/')); + + // Was the path included with the class name? + // We look for a slash to determine this + if (($last_slash = strrpos($class, '/')) !== FALSE) + { + // Extract the path + $subdir = substr($class, 0, ++$last_slash); + + // Get the filename from the path + $class = substr($class, $last_slash); + } + else + { + $subdir = ''; + } + + $class = ucfirst($class); + + // Is this a stock library? There are a few special conditions if so ... + if (file_exists(BASEPATH.'libraries/'.$subdir.$class.'.php')) + { + return $this->_ci_load_stock_library($class, $subdir, $params, $object_name); + } + + // Safety: Was the class already loaded by a previous call? + if (class_exists($class, FALSE)) + { + $property = $object_name; + if (empty($property)) + { + $property = strtolower($class); + isset($this->_ci_varmap[$property]) && $property = $this->_ci_varmap[$property]; + } + + $CI =& get_instance(); + if (isset($CI->$property)) + { + log_message('debug', $class.' class already loaded. Second attempt ignored.'); + return; + } + + return $this->_ci_init_library($class, '', $params, $object_name); + } + + // Let's search for the requested library file and load it. + foreach ($this->_ci_library_paths as $path) + { + // BASEPATH has already been checked for + if ($path === BASEPATH) + { + continue; + } + + $filepath = $path.'libraries/'.$subdir.$class.'.php'; + // Does the file exist? No? Bummer... + if ( ! file_exists($filepath)) + { + continue; + } + + include_once($filepath); + return $this->_ci_init_library($class, '', $params, $object_name); + } + + // One last attempt. Maybe the library is in a subdirectory, but it wasn't specified? + if ($subdir === '') + { + return $this->_ci_load_library($class.'/'.$class, $params, $object_name); + } + + // If we got this far we were unable to find the requested class. + log_message('error', 'Unable to load the requested class: '.$class); + show_error('Unable to load the requested class: '.$class); + } + + // -------------------------------------------------------------------- + + /** + * Internal CI Stock Library Loader + * + * @used-by CI_Loader::_ci_load_library() + * @uses CI_Loader::_ci_init_library() + * + * @param string $library_name Library name to load + * @param string $file_path Path to the library filename, relative to libraries/ + * @param mixed $params Optional parameters to pass to the class constructor + * @param string $object_name Optional object name to assign to + * @return void + */ + protected function _ci_load_stock_library($library_name, $file_path, $params, $object_name) + { + $prefix = 'CI_'; + + if (class_exists($prefix.$library_name, FALSE)) + { + if (class_exists(config_item('subclass_prefix').$library_name, FALSE)) + { + $prefix = config_item('subclass_prefix'); + } + + $property = $object_name; + if (empty($property)) + { + $property = strtolower($library_name); + isset($this->_ci_varmap[$property]) && $property = $this->_ci_varmap[$property]; + } + + $CI =& get_instance(); + if ( ! isset($CI->$property)) + { + return $this->_ci_init_library($library_name, $prefix, $params, $object_name); + } + + log_message('debug', $library_name.' class already loaded. Second attempt ignored.'); + return; + } + + $paths = $this->_ci_library_paths; + array_pop($paths); // BASEPATH + array_pop($paths); // APPPATH (needs to be the first path checked) + array_unshift($paths, APPPATH); + + foreach ($paths as $path) + { + if (file_exists($path = $path.'libraries/'.$file_path.$library_name.'.php')) + { + // Override + include_once($path); + if (class_exists($prefix.$library_name, FALSE)) + { + return $this->_ci_init_library($library_name, $prefix, $params, $object_name); + } + + log_message('debug', $path.' exists, but does not declare '.$prefix.$library_name); + } + } + + include_once(BASEPATH.'libraries/'.$file_path.$library_name.'.php'); + + // Check for extensions + $subclass = config_item('subclass_prefix').$library_name; + foreach ($paths as $path) + { + if (file_exists($path = $path.'libraries/'.$file_path.$subclass.'.php')) + { + include_once($path); + if (class_exists($subclass, FALSE)) + { + $prefix = config_item('subclass_prefix'); + break; + } + + log_message('debug', $path.' exists, but does not declare '.$subclass); + } + } + + return $this->_ci_init_library($library_name, $prefix, $params, $object_name); + } + + // -------------------------------------------------------------------- + + /** + * Internal CI Library Instantiator + * + * @used-by CI_Loader::_ci_load_stock_library() + * @used-by CI_Loader::_ci_load_library() + * + * @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_library($class, $prefix, $config = FALSE, $object_name = NULL) + { + // Is there an associated config file for this class? Note: these should always be lowercase + if ($config === NULL) + { + // Fetch the config paths containing any package paths + $config_component = $this->_ci_get_component('config'); + + if (is_array($config_component->_config_paths)) + { + $found = FALSE; + foreach ($config_component->_config_paths as $path) + { + // We test for both uppercase and lowercase, for servers that + // are case-sensitive with regard to file names. Load global first, + // override with environment next + if (file_exists($path.'config/'.strtolower($class).'.php')) + { + include($path.'config/'.strtolower($class).'.php'); + $found = TRUE; + } + elseif (file_exists($path.'config/'.ucfirst(strtolower($class)).'.php')) + { + include($path.'config/'.ucfirst(strtolower($class)).'.php'); + $found = TRUE; + } + + if (file_exists($path.'config/'.ENVIRONMENT.'/'.strtolower($class).'.php')) + { + include($path.'config/'.ENVIRONMENT.'/'.strtolower($class).'.php'); + $found = TRUE; + } + elseif (file_exists($path.'config/'.ENVIRONMENT.'/'.ucfirst(strtolower($class)).'.php')) + { + include($path.'config/'.ENVIRONMENT.'/'.ucfirst(strtolower($class)).'.php'); + $found = TRUE; + } + + // Break on the first found configuration, thus package + // files are not overridden by default paths + if ($found === TRUE) + { + break; + } + } + } + } + + $class_name = $prefix.$class; + + // Is the class name valid? + if ( ! class_exists($class_name, FALSE)) + { + log_message('error', 'Non-existent class: '.$class_name); + show_error('Non-existent class: '.$class_name); + } + + // Set the variable name we will assign the class to + // Was a custom class name supplied? If so we'll use it + if (empty($object_name)) + { + $object_name = strtolower($class); + if (isset($this->_ci_varmap[$object_name])) + { + $object_name = $this->_ci_varmap[$object_name]; + } + } + + // Don't overwrite existing properties + $CI =& get_instance(); + if (isset($CI->$object_name)) + { + if ($CI->$object_name instanceof $class_name) + { + log_message('debug', $class_name." has already been instantiated as '".$object_name."'. Second attempt aborted."); + return; + } + + show_error("Resource '".$object_name."' already exists and is not a ".$class_name." instance."); + } + + // Save the class name and object name + $this->_ci_classes[$object_name] = $class; + + // Instantiate the class + $CI->$object_name = isset($config) + ? new $class_name($config) + : new $class_name(); + } + + // -------------------------------------------------------------------- + + /** + * CI Autoloader + * + * Loads component listed in the config/autoload.php file. + * + * @used-by CI_Loader::initialize() + * @return void + */ + protected function _ci_autoloader() + { + if (file_exists(APPPATH.'config/autoload.php')) + { + include(APPPATH.'config/autoload.php'); + } + + if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/autoload.php')) + { + include(APPPATH.'config/'.ENVIRONMENT.'/autoload.php'); + } + + if ( ! isset($autoload)) + { + return; + } + + // Autoload packages + if (isset($autoload['packages'])) + { + foreach ($autoload['packages'] as $package_path) + { + $this->add_package_path($package_path); + } + } + + // Load any custom config file + if (count($autoload['config']) > 0) + { + foreach ($autoload['config'] as $val) + { + $this->config($val); + } + } + + // Autoload helpers and languages + foreach (array('helper', 'language') as $type) + { + if (isset($autoload[$type]) && count($autoload[$type]) > 0) + { + $this->$type($autoload[$type]); + } + } + + // Autoload drivers + if (isset($autoload['drivers'])) + { + $this->driver($autoload['drivers']); + } + + // Load libraries + if (isset($autoload['libraries']) && count($autoload['libraries']) > 0) + { + // Load the database driver. + if (in_array('database', $autoload['libraries'])) + { + $this->database(); + $autoload['libraries'] = array_diff($autoload['libraries'], array('database')); + } + + // Load all other libraries + $this->library($autoload['libraries']); + } + + // Autoload models + if (isset($autoload['model'])) + { + $this->model($autoload['model']); + } + } + + // -------------------------------------------------------------------- + + /** + * Prepare variables for _ci_vars, to be later extract()-ed inside views + * + * Converts objects to associative arrays and filters-out internal + * variable names (i.e. keys prefixed with '_ci_'). + * + * @param mixed $vars + * @return array + */ + protected function _ci_prepare_view_vars($vars) + { + if ( ! is_array($vars)) + { + $vars = is_object($vars) + ? get_object_vars($vars) + : array(); + } + + foreach (array_keys($vars) as $key) + { + if (strncmp($key, '_ci_', 4) === 0) + { + unset($vars[$key]); + } + } + + return $vars; + } + + // -------------------------------------------------------------------- + + /** + * CI Component getter + * + * Get a reference to a specific library or model. + * + * @param string $component Component name + * @return bool + */ + protected function &_ci_get_component($component) + { + $CI =& get_instance(); + return $CI->$component; + } +} diff --git a/system/core/Log.php b/system/core/Log.php new file mode 100644 index 0000000..a2b464c --- /dev/null +++ b/system/core/Log.php @@ -0,0 +1,296 @@ + 1, 'DEBUG' => 2, 'INFO' => 3, 'ALL' => 4); + + /** + * mbstring.func_overload flag + * + * @var bool + */ + protected static $func_overload; + + // -------------------------------------------------------------------- + + /** + * Class constructor + * + * @return void + */ + public function __construct() + { + $config =& get_config(); + + isset(self::$func_overload) OR self::$func_overload = (extension_loaded('mbstring') && ini_get('mbstring.func_overload')); + + $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, 0755, TRUE); + + if ( ! is_dir($this->_log_path) OR ! is_really_writable($this->_log_path)) + { + $this->_enabled = FALSE; + } + + if (is_numeric($config['log_threshold'])) + { + $this->_threshold = (int) $config['log_threshold']; + } + elseif (is_array($config['log_threshold'])) + { + $this->_threshold = 0; + $this->_threshold_array = array_flip($config['log_threshold']); + } + + if ( ! empty($config['log_date_format'])) + { + $this->_date_fmt = $config['log_date_format']; + } + + if ( ! empty($config['log_file_permissions']) && is_int($config['log_file_permissions'])) + { + $this->_file_permissions = $config['log_file_permissions']; + } + } + + // -------------------------------------------------------------------- + + /** + * Write Log File + * + * Generally this function will be called using the global log_message() function + * + * @param string $level The error level: 'error', 'debug' or 'info' + * @param string $msg The error message + * @return bool + */ + public function write_log($level, $msg) + { + if ($this->_enabled === FALSE) + { + return FALSE; + } + + $level = strtoupper($level); + + if (( ! isset($this->_levels[$level]) OR ($this->_levels[$level] > $this->_threshold)) + && ! isset($this->_threshold_array[$this->_levels[$level]])) + { + return FALSE; + } + + $filepath = $this->_log_path.'log-'.date('Y-m-d').'.'.$this->_file_ext; + $message = ''; + + if ( ! file_exists($filepath)) + { + $newfile = TRUE; + // Only add protection to php files + if ($this->_file_ext === 'php') + { + $message .= "\n\n"; + } + } + + if ( ! $fp = @fopen($filepath, 'ab')) + { + return FALSE; + } + + flock($fp, LOCK_EX); + + // Instantiating DateTime with microseconds appended to initial date is needed for proper support of this format + if (strpos($this->_date_fmt, 'u') !== FALSE) + { + $microtime_full = microtime(TRUE); + $microtime_short = sprintf("%06d", ($microtime_full - floor($microtime_full)) * 1000000); + $date = new DateTime(date('Y-m-d H:i:s.'.$microtime_short, $microtime_full)); + $date = $date->format($this->_date_fmt); + } + else + { + $date = date($this->_date_fmt); + } + + $message .= $this->_format_line($level, $date, $msg); + + for ($written = 0, $length = self::strlen($message); $written < $length; $written += $result) + { + if (($result = fwrite($fp, self::substr($message, $written))) === FALSE) + { + break; + } + } + + flock($fp, LOCK_UN); + fclose($fp); + + if (isset($newfile) && $newfile === TRUE) + { + chmod($filepath, $this->_file_permissions); + } + + return is_int($result); + } + + // -------------------------------------------------------------------- + + /** + * Format the log line. + * + * This is for extensibility of log formatting + * If you want to change the log format, extend the CI_Log class and override this method + * + * @param string $level The error level + * @param string $date Formatted date string + * @param string $message The log message + * @return string Formatted log line with a new line character '\n' at the end + */ + protected function _format_line($level, $date, $message) + { + return $level.' - '.$date.' --> '.$message."\n"; + } + + // -------------------------------------------------------------------- + + /** + * Byte-safe strlen() + * + * @param string $str + * @return int + */ + protected static function strlen($str) + { + return (self::$func_overload) + ? mb_strlen($str, '8bit') + : strlen($str); + } + + // -------------------------------------------------------------------- + + /** + * Byte-safe substr() + * + * @param string $str + * @param int $start + * @param int $length + * @return string + */ + protected static function substr($str, $start, $length = NULL) + { + if (self::$func_overload) + { + // mb_substr($str, $start, null, '8bit') returns an empty + // string on PHP 5.3 + isset($length) OR $length = ($start >= 0 ? self::strlen($str) - $start : -$start); + return mb_substr($str, $start, $length, '8bit'); + } + + return isset($length) + ? substr($str, $start, $length) + : substr($str, $start); + } +} diff --git a/system/core/Model.php b/system/core/Model.php new file mode 100644 index 0000000..dd11dd2 --- /dev/null +++ b/system/core/Model.php @@ -0,0 +1,76 @@ +$key; + } + +} diff --git a/system/core/Output.php b/system/core/Output.php new file mode 100644 index 0000000..3cda062 --- /dev/null +++ b/system/core/Output.php @@ -0,0 +1,842 @@ +_zlib_oc = (bool) ini_get('zlib.output_compression'); + $this->_compress_output = ( + $this->_zlib_oc === FALSE + && config_item('compress_output') === TRUE + && extension_loaded('zlib') + ); + + isset(self::$func_overload) OR self::$func_overload = (extension_loaded('mbstring') && ini_get('mbstring.func_overload')); + + // Get mime types for later + $this->mimes =& get_mimes(); + + log_message('info', 'Output Class Initialized'); + } + + // -------------------------------------------------------------------- + + /** + * Get Output + * + * Returns the current output string. + * + * @return string + */ + public function get_output() + { + return $this->final_output; + } + + // -------------------------------------------------------------------- + + /** + * Set Output + * + * Sets the output string. + * + * @param string $output Output data + * @return CI_Output + */ + public function set_output($output) + { + $this->final_output = $output; + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Append Output + * + * Appends data onto the output string. + * + * @param string $output Data to append + * @return CI_Output + */ + public function append_output($output) + { + $this->final_output .= $output; + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Set Header + * + * 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. + * @todo We need to figure out how to permit headers to be cached. + * + * @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) + { + // If zlib.output_compression is enabled it will compress the output, + // but it will not modify the content-length header to compensate for + // the reduction, causing the browser to hang waiting for more data. + // We'll just skip content-length in those cases. + if ($this->_zlib_oc && strncasecmp($header, 'content-length', 14) === 0) + { + return $this; + } + + $this->headers[] = array($header, $replace); + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Set Content-Type Header + * + * @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) + { + if (strpos($mime_type, '/') === FALSE) + { + $extension = ltrim($mime_type, '.'); + + // Is this extension supported? + if (isset($this->mimes[$extension])) + { + $mime_type =& $this->mimes[$extension]; + + if (is_array($mime_type)) + { + $mime_type = current($mime_type); + } + } + } + + $this->mime_type = $mime_type; + + if (empty($charset)) + { + $charset = config_item('charset'); + } + + $header = 'Content-Type: '.$mime_type + .(empty($charset) ? '' : '; charset='.$charset); + + $this->headers[] = array($header, TRUE); + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Get Current Content-Type Header + * + * @return string 'text/html', if not already set + */ + public function get_content_type() + { + for ($i = 0, $c = count($this->headers); $i < $c; $i++) + { + if (sscanf($this->headers[$i][0], 'Content-Type: %[^;]', $content_type) === 1) + { + return $content_type; + } + } + + return 'text/html'; + } + + // -------------------------------------------------------------------- + + /** + * Get Header + * + * @param string $header + * @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; + } + + // Count backwards, in order to get the last matching header + for ($c = count($headers) - 1; $c > -1; $c--) + { + if (strncasecmp($header, $headers[$c], $l = self::strlen($header)) === 0) + { + return trim(self::substr($headers[$c], $l+1)); + } + } + + return NULL; + } + + // -------------------------------------------------------------------- + + /** + * Set HTTP Status Header + * + * 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 = '') + { + set_status_header($code, $text); + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Enable/disable Profiler + * + * @param bool $val TRUE to enable or FALSE to disable + * @return CI_Output + */ + public function enable_profiler($val = TRUE) + { + $this->enable_profiler = is_bool($val) ? $val : TRUE; + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Set Profiler Sections + * + * Allows override of default/config settings for + * Profiler section display. + * + * @param array $sections Profiler sections + * @return CI_Output + */ + public function set_profiler_sections($sections) + { + if (isset($sections['query_toggle_count'])) + { + $this->_profiler_sections['query_toggle_count'] = (int) $sections['query_toggle_count']; + unset($sections['query_toggle_count']); + } + + foreach ($sections as $section => $enable) + { + $this->_profiler_sections[$section] = ($enable !== FALSE); + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Set Cache + * + * @param int $time Cache expiration time in minutes + * @return CI_Output + */ + public function cache($time) + { + $this->cache_expiration = is_numeric($time) ? $time : 0; + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Display Output + * + * Processes and 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. + * + * Note: All "view" data is automatically put into $this->final_output + * by controller class. + * + * @uses CI_Output::$final_output + * @param string $output Output data override + * @return void + */ + public function _display($output = '') + { + // Note: We use load_class() because we can't use $CI =& get_instance() + // since this function is sometimes called by the caching mechanism, + // which happens before the CI super object is available. + $BM =& load_class('Benchmark', 'core'); + $CFG =& load_class('Config', 'core'); + + // Grab the super object if we can. + if (class_exists('CI_Controller', FALSE)) + { + $CI =& get_instance(); + } + + // -------------------------------------------------------------------- + + // Set the output data + if ($output === '') + { + $output =& $this->final_output; + } + + // -------------------------------------------------------------------- + + // 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')) + { + $this->_write_cache($output); + } + + // -------------------------------------------------------------------- + + // Parse out the elapsed time and memory usage, + // then swap the pseudo-variables with the data + + $elapsed = $BM->elapsed_time('total_execution_time_start', 'total_execution_time_end'); + + if ($this->parse_exec_vars === TRUE) + { + $memory = round(memory_get_usage() / 1024 / 1024, 2).'MB'; + $output = str_replace(array('{elapsed_time}', '{memory_usage}'), array($elapsed, $memory), $output); + } + + // -------------------------------------------------------------------- + + // Is compression requested? + if (isset($CI) // This means that we're not serving a cache file, if we were, it would already be compressed + && $this->_compress_output === TRUE + && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE) + { + ob_start('ob_gzhandler'); + } + + // -------------------------------------------------------------------- + + // Are there any server headers to send? + if (count($this->headers) > 0) + { + foreach ($this->headers as $header) + { + @header($header[0], $header[1]); + } + } + + // -------------------------------------------------------------------- + + // Does the $CI object exist? + // If not we know we are dealing with a cache file so we'll + // simply echo out the data and exit. + if ( ! isset($CI)) + { + if ($this->_compress_output === TRUE) + { + if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE) + { + header('Content-Encoding: gzip'); + header('Content-Length: '.self::strlen($output)); + } + else + { + // User agent doesn't support gzip compression, + // so we'll have to decompress our cache + $output = gzinflate(self::substr($output, 10, -8)); + } + } + + echo $output; + log_message('info', 'Final output sent to browser'); + log_message('debug', 'Total execution time: '.$elapsed); + return; + } + + // -------------------------------------------------------------------- + + // Do we need to generate profile data? + // If so, load the Profile class and run it. + if ($this->enable_profiler === TRUE) + { + $CI->load->library('profiler'); + if ( ! empty($this->_profiler_sections)) + { + $CI->profiler->set_sections($this->_profiler_sections); + } + + // If the output data contains closing