kenan 1 year ago
parent
commit
d8670db5fb
100 changed files with 26012 additions and 0 deletions
  1. 7 0
      admin.php
  2. 6 0
      apps/application/.htaccess
  3. 11 0
      apps/application/cache/index.html
  4. 135 0
      apps/application/config/autoload.php
  5. 523 0
      apps/application/config/config.php
  6. 85 0
      apps/application/config/constants.php
  7. 28 0
      apps/application/config/database.php
  8. 24 0
      apps/application/config/doctypes.php
  9. 103 0
      apps/application/config/foreign_chars.php
  10. 13 0
      apps/application/config/hooks.php
  11. 11 0
      apps/application/config/index.html
  12. 19 0
      apps/application/config/memcached.php
  13. 84 0
      apps/application/config/migration.php
  14. 183 0
      apps/application/config/mimes.php
  15. 14 0
      apps/application/config/profiler.php
  16. 14 0
      apps/application/config/routes.php
  17. 64 0
      apps/application/config/smileys.php
  18. 214 0
      apps/application/config/user_agents.php
  19. 86 0
      apps/application/controllers/Home.php
  20. 61 0
      apps/application/controllers/Index.php
  21. 65 0
      apps/application/controllers/Install.php
  22. 108 0
      apps/application/controllers/Login.php
  23. 115 0
      apps/application/controllers/M3u8.php
  24. 51 0
      apps/application/controllers/Play.php
  25. 240 0
      apps/application/controllers/Setting.php
  26. 171 0
      apps/application/controllers/Ting.php
  27. 33 0
      apps/application/controllers/Tongbu.php
  28. 267 0
      apps/application/controllers/Upapi.php
  29. 254 0
      apps/application/controllers/Upload.php
  30. 501 0
      apps/application/controllers/Video.php
  31. 132 0
      apps/application/controllers/Vlist.php
  32. 11 0
      apps/application/controllers/index.html
  33. 39 0
      apps/application/core/MY_Input.php
  34. 11 0
      apps/application/core/index.html
  35. 394 0
      apps/application/helpers/common_helper.php
  36. 11 0
      apps/application/helpers/index.html
  37. 11 0
      apps/application/hooks/index.html
  38. 11 0
      apps/application/index.html
  39. 11 0
      apps/application/language/english/index.html
  40. 11 0
      apps/application/language/index.html
  41. 37 0
      apps/application/libraries/Abc.php
  42. 159 0
      apps/application/libraries/Def.php
  43. 1340 0
      apps/application/libraries/Ffm.php
  44. 112 0
      apps/application/libraries/Hke.php
  45. 82 0
      apps/application/libraries/Pdx.php
  46. 238 0
      apps/application/libraries/Xyz.php
  47. 137 0
      apps/application/libraries/Zm.php
  48. 11 0
      apps/application/libraries/index.html
  49. 11 0
      apps/application/logs/index.html
  50. 50 0
      apps/application/models/User.php
  51. 11 0
      apps/application/models/index.html
  52. 11 0
      apps/application/third_party/index.html
  53. 11 0
      apps/application/views/index.html
  54. 6 0
      apps/system/.htaccess
  55. 133 0
      apps/system/core/Benchmark.php
  56. 559 0
      apps/system/core/CodeIgniter.php
  57. 849 0
      apps/system/core/Common.php
  58. 379 0
      apps/system/core/Config.php
  59. 96 0
      apps/system/core/Controller.php
  60. 274 0
      apps/system/core/Exceptions.php
  61. 266 0
      apps/system/core/Hooks.php
  62. 895 0
      apps/system/core/Input.php
  63. 203 0
      apps/system/core/Lang.php
  64. 1415 0
      apps/system/core/Loader.php
  65. 296 0
      apps/system/core/Log.php
  66. 76 0
      apps/system/core/Model.php
  67. 842 0
      apps/system/core/Output.php
  68. 511 0
      apps/system/core/Router.php
  69. 1080 0
      apps/system/core/Security.php
  70. 643 0
      apps/system/core/URI.php
  71. 164 0
      apps/system/core/Utf8.php
  72. 254 0
      apps/system/core/compat/hash.php
  73. 11 0
      apps/system/core/compat/index.html
  74. 149 0
      apps/system/core/compat/mbstring.php
  75. 251 0
      apps/system/core/compat/password.php
  76. 182 0
      apps/system/core/compat/standard.php
  77. 11 0
      apps/system/core/index.html
  78. 218 0
      apps/system/database/DB.php
  79. 221 0
      apps/system/database/DB_cache.php
  80. 1991 0
      apps/system/database/DB_driver.php
  81. 1030 0
      apps/system/database/DB_forge.php
  82. 2803 0
      apps/system/database/DB_query_builder.php
  83. 665 0
      apps/system/database/DB_result.php
  84. 424 0
      apps/system/database/DB_utility.php
  85. 405 0
      apps/system/database/drivers/cubrid/cubrid_driver.php
  86. 230 0
      apps/system/database/drivers/cubrid/cubrid_forge.php
  87. 177 0
      apps/system/database/drivers/cubrid/cubrid_result.php
  88. 79 0
      apps/system/database/drivers/cubrid/cubrid_utility.php
  89. 11 0
      apps/system/database/drivers/cubrid/index.html
  90. 413 0
      apps/system/database/drivers/ibase/ibase_driver.php
  91. 251 0
      apps/system/database/drivers/ibase/ibase_forge.php
  92. 161 0
      apps/system/database/drivers/ibase/ibase_result.php
  93. 69 0
      apps/system/database/drivers/ibase/ibase_utility.php
  94. 11 0
      apps/system/database/drivers/ibase/index.html
  95. 11 0
      apps/system/database/drivers/index.html
  96. 11 0
      apps/system/database/drivers/mssql/index.html
  97. 518 0
      apps/system/database/drivers/mssql/mssql_driver.php
  98. 151 0
      apps/system/database/drivers/mssql/mssql_forge.php
  99. 198 0
      apps/system/database/drivers/mssql/mssql_result.php
  100. 77 0
      apps/system/database/drivers/mssql/mssql_utility.php

+ 7 - 0
admin.php

@@ -0,0 +1,7 @@
+<?php
+define('IS_ADMIN', true);
+define('SELF', pathinfo(preg_replace("@\\(.*\\(.*\$@", "", __FILE__), PATHINFO_BASENAME));
+define('FCPATH', str_replace('\\', DIRECTORY_SEPARATOR, dirname(preg_replace("@\\(.*\\(.*\$@", "", __FILE__)) . DIRECTORY_SEPARATOR));
+require('index.php');
+
+

+ 6 - 0
apps/application/.htaccess

@@ -0,0 +1,6 @@
+<IfModule authz_core_module>
+    Require all denied
+</IfModule>
+<IfModule !authz_core_module>
+    Deny from all
+</IfModule>

+ 11 - 0
apps/application/cache/index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>403 Forbidden</title>
+</head>
+<body>
+
+<p>Directory access is forbidden.</p>
+
+</body>
+</html>

+ 135 - 0
apps/application/config/autoload.php

@@ -0,0 +1,135 @@
+<?php
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/*
+| -------------------------------------------------------------------
+| AUTO-LOADER
+| -------------------------------------------------------------------
+| This file specifies which systems should be loaded by default.
+|
+| In order to keep the framework as light-weight as possible only the
+| absolute minimal resources are loaded by default. For example,
+| the database is not connected to automatically since no assumption
+| is made regarding whether you intend to use it.  This file lets
+| you globally define which systems you would like loaded with every
+| request.
+|
+| -------------------------------------------------------------------
+| Instructions
+| -------------------------------------------------------------------
+|
+| These are the things you can load automatically:
+|
+| 1. Packages
+| 2. Libraries
+| 3. Drivers
+| 4. Helper files
+| 5. Custom config files
+| 6. Language files
+| 7. Models
+|
+*/
+
+/*
+| -------------------------------------------------------------------
+|  Auto-load Packages
+| -------------------------------------------------------------------
+| Prototype:
+|
+|  $autoload['packages'] = array(APPPATH.'third_party', '/usr/local/shared');
+|
+*/
+$autoload['packages'] = array();
+
+/*
+| -------------------------------------------------------------------
+|  Auto-load Libraries
+| -------------------------------------------------------------------
+| These are the classes located in system/libraries/ or your
+| application/libraries/ directory, with the addition of the
+| 'database' library, which is somewhat of a special case.
+|
+| Prototype:
+|
+|	$autoload['libraries'] = array('database', 'email', 'session');
+|
+| You can also supply an alternative library name to be assigned
+| in the controller:
+|
+|	$autoload['libraries'] = array('user_agent' => 'ua');
+*/
+$autoload['libraries'] = array();
+
+/*
+| -------------------------------------------------------------------
+|  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','cookie','common');
+
+/*
+| -------------------------------------------------------------------
+|  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();

+ 523 - 0
apps/application/config/config.php

@@ -0,0 +1,523 @@
+<?php
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/*
+|--------------------------------------------------------------------------
+| Base Site URL
+|--------------------------------------------------------------------------
+|
+| URL to your CodeIgniter root. Typically this will be your base URL,
+| WITH a trailing slash:
+|
+|	http://example.com/
+|
+| WARNING: You MUST set this value!
+|
+| If it is not set, then CodeIgniter will try guess the protocol and path
+| your installation, but due to security concerns the hostname will be set
+| to $_SERVER['SERVER_ADDR'] if available, or localhost otherwise.
+| The auto-detection mechanism exists only for convenience during
+| development and MUST NOT be used in production!
+|
+| If you need to allow multiple domains, remember that this file is still
+| a PHP script and you can easily do that on your own.
+|
+*/
+$config['base_url'] = Web_Path;
+
+/*
+|--------------------------------------------------------------------------
+| Index File
+|--------------------------------------------------------------------------
+|
+| Typically this will be your index.php file, unless you've renamed it to
+| something else. If you are using mod_rewrite to remove the page set this
+| variable so that it is blank.
+|
+*/
+$config['index_page'] = SELF;
+
+/*
+|--------------------------------------------------------------------------
+| URI PROTOCOL
+|--------------------------------------------------------------------------
+|
+| This item determines which server global should be used to retrieve the
+| URI string.  The default setting of 'REQUEST_URI' works for most servers.
+| If your links do not seem to work, try one of the other delicious flavors:
+|
+| 'REQUEST_URI'    Uses $_SERVER['REQUEST_URI']
+| 'QUERY_STRING'   Uses $_SERVER['QUERY_STRING']
+| 'PATH_INFO'      Uses $_SERVER['PATH_INFO']
+|
+| WARNING: If you set this to 'PATH_INFO', URIs will always be URL-decoded!
+*/
+$config['uri_protocol']	= 'REQUEST_URI';
+
+/*
+|--------------------------------------------------------------------------
+| URL suffix
+|--------------------------------------------------------------------------
+|
+| This option allows you to add a suffix to all URLs generated by CodeIgniter.
+| For more information please see the user guide:
+|
+| https://codeigniter.com/user_guide/general/urls.html
+*/
+$config['url_suffix'] = '.html';
+
+/*
+|--------------------------------------------------------------------------
+| Default Language
+|--------------------------------------------------------------------------
+|
+| This determines which set of language files should be used. Make sure
+| there is an available translation if you intend to use something other
+| than english.
+|
+*/
+$config['language']	= 'english';
+
+/*
+|--------------------------------------------------------------------------
+| Default Character Set
+|--------------------------------------------------------------------------
+|
+| This determines which character set is used by default in various methods
+| that require a character set to be provided.
+|
+| See http://php.net/htmlspecialchars for a list of supported charsets.
+|
+*/
+$config['charset'] = 'UTF-8';
+
+/*
+|--------------------------------------------------------------------------
+| Enable/Disable System Hooks
+|--------------------------------------------------------------------------
+|
+| If you would like to use the 'hooks' feature you must enable it by
+| setting this variable to TRUE (boolean).  See the user guide for details.
+|
+*/
+$config['enable_hooks'] = FALSE;
+
+/*
+|--------------------------------------------------------------------------
+| Class Extension Prefix
+|--------------------------------------------------------------------------
+|
+| This item allows you to set the filename/classname prefix when extending
+| native libraries.  For more information please see the user guide:
+|
+| https://codeigniter.com/user_guide/general/core_classes.html
+| https://codeigniter.com/user_guide/general/creating_libraries.html
+|
+*/
+$config['subclass_prefix'] = 'MY_';
+
+/*
+|--------------------------------------------------------------------------
+| Composer auto-loading
+|--------------------------------------------------------------------------
+|
+| Enabling this setting will tell CodeIgniter to look for a Composer
+| package auto-loader script in application/vendor/autoload.php.
+|
+|	$config['composer_autoload'] = TRUE;
+|
+| Or if you have your vendor/ directory located somewhere else, you
+| can opt to set a specific path as well:
+|
+|	$config['composer_autoload'] = '/path/to/vendor/autoload.php';
+|
+| For more information about Composer, please visit http://getcomposer.org/
+|
+| Note: This will NOT disable or override the CodeIgniter-specific
+|	autoloading (application/config/autoload.php)
+*/
+$config['composer_autoload'] = FALSE;
+
+/*
+|--------------------------------------------------------------------------
+| Allowed URL Characters
+|--------------------------------------------------------------------------
+|
+| This lets you specify which characters are permitted within your URLs.
+| When someone tries to submit a URL with disallowed characters they will
+| get a warning message.
+|
+| As a security measure you are STRONGLY encouraged to restrict URLs to
+| as few characters as possible.  By default only these are allowed: a-z 0-9~%.:_-
+|
+| Leave blank to allow all characters -- but only if you are insane.
+|
+| The configured value is actually a regular expression character group
+| and it will be executed as: ! preg_match('/^[<permitted_uri_chars>]+$/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'] = 'ctcms';
+
+/*
+|--------------------------------------------------------------------------
+| 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'] = FCPATH.'caches/session';
+$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']	= 'ctcms_';
+$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'] = '';

+ 85 - 0
apps/application/config/constants.php

@@ -0,0 +1,85 @@
+<?php
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/*
+|--------------------------------------------------------------------------
+| Display Debug backtrace
+|--------------------------------------------------------------------------
+|
+| If set to TRUE, a backtrace will be displayed along with php errors. If
+| error_reporting is disabled, the backtrace will not display, regardless
+| of this setting
+|
+*/
+defined('SHOW_DEBUG_BACKTRACE') OR define('SHOW_DEBUG_BACKTRACE', TRUE);
+
+/*
+|--------------------------------------------------------------------------
+| File and Directory Modes
+|--------------------------------------------------------------------------
+|
+| These prefs are used when checking and setting modes when working
+| with the file system.  The defaults are fine on servers with proper
+| security, but you may wish (or even need) to change the values in
+| certain environments (Apache running a separate process for each
+| user, PHP under CGI with Apache suEXEC, etc.).  Octal values should
+| always be used to set the mode correctly.
+|
+*/
+defined('FILE_READ_MODE')  OR define('FILE_READ_MODE', 0644);
+defined('FILE_WRITE_MODE') OR define('FILE_WRITE_MODE', 0666);
+defined('DIR_READ_MODE')   OR define('DIR_READ_MODE', 0755);
+defined('DIR_WRITE_MODE')  OR define('DIR_WRITE_MODE', 0755);
+
+/*
+|--------------------------------------------------------------------------
+| File Stream Modes
+|--------------------------------------------------------------------------
+|
+| These modes are used when working with fopen()/popen()
+|
+*/
+defined('FOPEN_READ')                           OR define('FOPEN_READ', 'rb');
+defined('FOPEN_READ_WRITE')                     OR define('FOPEN_READ_WRITE', 'r+b');
+defined('FOPEN_WRITE_CREATE_DESTRUCTIVE')       OR define('FOPEN_WRITE_CREATE_DESTRUCTIVE', 'wb'); // truncates existing file data, use with care
+defined('FOPEN_READ_WRITE_CREATE_DESTRUCTIVE')  OR define('FOPEN_READ_WRITE_CREATE_DESTRUCTIVE', 'w+b'); // truncates existing file data, use with care
+defined('FOPEN_WRITE_CREATE')                   OR define('FOPEN_WRITE_CREATE', 'ab');
+defined('FOPEN_READ_WRITE_CREATE')              OR define('FOPEN_READ_WRITE_CREATE', 'a+b');
+defined('FOPEN_WRITE_CREATE_STRICT')            OR define('FOPEN_WRITE_CREATE_STRICT', 'xb');
+defined('FOPEN_READ_WRITE_CREATE_STRICT')       OR define('FOPEN_READ_WRITE_CREATE_STRICT', 'x+b');
+
+/*
+|--------------------------------------------------------------------------
+| Exit Status Codes
+|--------------------------------------------------------------------------
+|
+| Used to indicate the conditions under which the script is exit()ing.
+| While there is no universal standard for error codes, there are some
+| broad conventions.  Three such conventions are mentioned below, for
+| those who wish to make use of them.  The CodeIgniter defaults were
+| chosen for the least overlap with these conventions, while still
+| leaving room for others to be defined in future versions and user
+| applications.
+|
+| The three main conventions used for determining exit status codes
+| are as follows:
+|
+|    Standard C/C++ Library (stdlibc):
+|       http://www.gnu.org/software/libc/manual/html_node/Exit-Status.html
+|       (This link also contains other GNU-specific conventions)
+|    BSD sysexits.h:
+|       http://www.gsp.com/cgi-bin/man.cgi?section=3&topic=sysexits
+|    Bash scripting:
+|       http://tldp.org/LDP/abs/html/exitcodes.html
+|
+*/
+defined('EXIT_SUCCESS')        OR define('EXIT_SUCCESS', 0); // no errors
+defined('EXIT_ERROR')          OR define('EXIT_ERROR', 1); // generic error
+defined('EXIT_CONFIG')         OR define('EXIT_CONFIG', 3); // configuration error
+defined('EXIT_UNKNOWN_FILE')   OR define('EXIT_UNKNOWN_FILE', 4); // file not found
+defined('EXIT_UNKNOWN_CLASS')  OR define('EXIT_UNKNOWN_CLASS', 5); // unknown class
+defined('EXIT_UNKNOWN_METHOD') OR define('EXIT_UNKNOWN_METHOD', 6); // unknown class member
+defined('EXIT_USER_INPUT')     OR define('EXIT_USER_INPUT', 7); // invalid user input
+defined('EXIT_DATABASE')       OR define('EXIT_DATABASE', 8); // database error
+defined('EXIT__AUTO_MIN')      OR define('EXIT__AUTO_MIN', 9); // lowest automatically-assigned error code
+defined('EXIT__AUTO_MAX')      OR define('EXIT__AUTO_MAX', 125); // highest automatically-assigned error code

+ 28 - 0
apps/application/config/database.php

@@ -0,0 +1,28 @@
+<?php
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+
+$active_group = 'default';
+$query_builder = TRUE;
+
+$db['default'] = array(
+	'dsn'	=> '',
+	'hostname' => CS_Sqlserver,
+	'username' => CS_Sqluid,
+	'password' => CS_Sqlpwd,
+	'database' => CS_Sqlname,
+	'dbdriver' => CS_Dbdriver,
+	'dbprefix' => CS_SqlPrefix,
+	'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
+);

+ 24 - 0
apps/application/config/doctypes.php

@@ -0,0 +1,24 @@
+<?php
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+$_doctypes = array(
+	'xhtml11' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
+	'xhtml1-strict' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
+	'xhtml1-trans' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
+	'xhtml1-frame' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
+	'xhtml-basic11' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">',
+	'html5' => '<!DOCTYPE html>',
+	'html4-strict' => '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">',
+	'html4-trans' => '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">',
+	'html4-frame' => '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">',
+	'mathml1' => '<!DOCTYPE math SYSTEM "http://www.w3.org/Math/DTD/mathml1/mathml.dtd">',
+	'mathml2' => '<!DOCTYPE math PUBLIC "-//W3C//DTD MathML 2.0//EN" "http://www.w3.org/Math/DTD/mathml2/mathml2.dtd">',
+	'svg10' => '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">',
+	'svg11' => '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">',
+	'svg11-basic' => '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Basic//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd">',
+	'svg11-tiny' => '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Tiny//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd">',
+	'xhtml-math-svg-xh' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd">',
+	'xhtml-math-svg-sh' => '<!DOCTYPE svg:svg PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd">',
+	'xhtml-rdfa-1' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">',
+	'xhtml-rdfa-2' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.1//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-2.dtd">'
+);

+ 103 - 0
apps/application/config/foreign_chars.php

@@ -0,0 +1,103 @@
+<?php
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/*
+| -------------------------------------------------------------------
+| Foreign Characters
+| -------------------------------------------------------------------
+| This file contains an array of foreign characters for transliteration
+| conversion used by the Text helper
+|
+*/
+$foreign_characters = array(
+	'/ä|æ|ǽ/' => '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'
+);

+ 13 - 0
apps/application/config/hooks.php

@@ -0,0 +1,13 @@
+<?php
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/*
+| -------------------------------------------------------------------------
+| Hooks
+| -------------------------------------------------------------------------
+| This file lets you define "hooks" to extend CI without hacking the core
+| files.  Please see the user guide for info:
+|
+|	https://codeigniter.com/user_guide/general/hooks.html
+|
+*/

+ 11 - 0
apps/application/config/index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>403 Forbidden</title>
+</head>
+<body>
+
+<p>Directory access is forbidden.</p>
+
+</body>
+</html>

+ 19 - 0
apps/application/config/memcached.php

@@ -0,0 +1,19 @@
+<?php
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/*
+| -------------------------------------------------------------------------
+| Memcached settings
+| -------------------------------------------------------------------------
+| Your Memcached servers can be specified below.
+|
+|	See: https://codeigniter.com/user_guide/libraries/caching.html#memcached
+|
+*/
+$config = array(
+	'default' => array(
+		'hostname' => '127.0.0.1',
+		'port'     => '11211',
+		'weight'   => '1',
+	),
+);

+ 84 - 0
apps/application/config/migration.php

@@ -0,0 +1,84 @@
+<?php
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/*
+|--------------------------------------------------------------------------
+| Enable/Disable Migrations
+|--------------------------------------------------------------------------
+|
+| Migrations are disabled by default for security reasons.
+| You should enable migrations whenever you intend to do a schema migration
+| and disable it back when you're done.
+|
+*/
+$config['migration_enabled'] = FALSE;
+
+/*
+|--------------------------------------------------------------------------
+| Migration Type
+|--------------------------------------------------------------------------
+|
+| Migration file names may be based on a sequential identifier or on
+| a timestamp. Options are:
+|
+|   'sequential' = Sequential migration naming (001_add_blog.php)
+|   'timestamp'  = Timestamp migration naming (20121031104401_add_blog.php)
+|                  Use timestamp format YYYYMMDDHHIISS.
+|
+| Note: If this configuration value is missing the Migration library
+|       defaults to 'sequential' for backward compatibility with CI2.
+|
+*/
+$config['migration_type'] = 'timestamp';
+
+/*
+|--------------------------------------------------------------------------
+| Migrations table
+|--------------------------------------------------------------------------
+|
+| This is the name of the table that will store the current migrations state.
+| When migrations runs it will store in a database table which migration
+| level the system is at. It then compares the migration level in this
+| table to the $config['migration_version'] if they are not the same it
+| will migrate up. This must be set.
+|
+*/
+$config['migration_table'] = 'migrations';
+
+/*
+|--------------------------------------------------------------------------
+| Auto Migrate To Latest
+|--------------------------------------------------------------------------
+|
+| If this is set to TRUE when you load the migrations class and have
+| $config['migration_enabled'] set to TRUE the system will auto migrate
+| to your latest migration (whatever $config['migration_version'] is
+| set to). This way you do not have to call migrations anywhere else
+| in your code to have the latest migration.
+|
+*/
+$config['migration_auto_latest'] = FALSE;
+
+/*
+|--------------------------------------------------------------------------
+| Migrations version
+|--------------------------------------------------------------------------
+|
+| This is used to set migration version that the file system should be on.
+| If you run $this->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/';

+ 183 - 0
apps/application/config/mimes.php

@@ -0,0 +1,183 @@
+<?php
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/*
+| -------------------------------------------------------------------
+| MIME TYPES
+| -------------------------------------------------------------------
+| This file contains an array of mime types.  It is used by the
+| Upload class to help identify allowed file types.
+|
+*/
+return array(
+	'hqx'	=>	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',
+	'7zip'	=>	array('application/x-compressed', 'application/x-zip-compressed', 'application/zip', 'multipart/x-zip'),
+	'cdr'	=>	array('application/cdr', 'application/coreldraw', 'application/x-cdr', 'application/x-coreldraw', 'image/cdr', 'image/x-cdr', 'zz-application/zz-winassoc-cdr'),
+	'wma'	=>	array('audio/x-ms-wma', 'video/x-ms-asf'),
+	'jar'	=>	array('application/java-archive', 'application/x-java-application', 'application/x-jar', 'application/x-compressed'),
+	'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'
+);

+ 14 - 0
apps/application/config/profiler.php

@@ -0,0 +1,14 @@
+<?php
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/*
+| -------------------------------------------------------------------------
+| Profiler Sections
+| -------------------------------------------------------------------------
+| This file lets you determine whether or not various sections of Profiler
+| data are displayed when the Profiler is enabled.
+| Please see the user guide for info:
+|
+|	https://codeigniter.com/user_guide/general/profiling.html
+|
+*/

+ 14 - 0
apps/application/config/routes.php

@@ -0,0 +1,14 @@
+<?php
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+$route['default_controller'] = 'index';
+$route['404_override'] = '';
+$route['translate_uri_dashes'] = FALSE;
+
+$route['m3u8/([a-zA-Z0-9]+)/(.+)'] = 'm3u8/$1/$2';
+$route['m3u8/(.+)'] = 'm3u8/index/$1';
+$route['play/(\d+)'] = 'play/index/$1';
+$route['share/([a-zA-Z0-9]+)'] = 'play/index/$1';
+if(SELF == 'index.php'){
+	$route['default_controller'] = 'home';
+}

+ 64 - 0
apps/application/config/smileys.php

@@ -0,0 +1,64 @@
+<?php
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/*
+| -------------------------------------------------------------------
+| SMILEYS
+| -------------------------------------------------------------------
+| This file contains an array of smileys for use with the emoticon helper.
+| Individual images can be used to replace multiple smileys.  For example:
+| :-) and :) use the same image replacement.
+|
+| Please see user guide for more info:
+| https://codeigniter.com/user_guide/helpers/smiley_helper.html
+|
+*/
+$smileys = array(
+
+//	smiley			image name						width	height	alt
+
+	':-)'			=>	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')
+
+);

+ 214 - 0
apps/application/config/user_agents.php

@@ -0,0 +1,214 @@
+<?php
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/*
+| -------------------------------------------------------------------
+| USER AGENT TYPES
+| -------------------------------------------------------------------
+| This file contains four arrays of user agent data. It is used by the
+| User Agent Class to help identify browser, platform, robot, and
+| mobile device data. The array keys are used to identify the device
+| and the array values are used to set the actual name of the item.
+*/
+$platforms = array(
+	'windows nt 10.0'	=> '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'			=> 'Spartan',
+	'Chrome'		=> 'Chrome',
+	// Opera 10+ always reports Opera/9.80 and appends Version/<real version> to the user agent string
+	'Opera.*?Version'	=> 'Opera',
+	'Opera'			=> 'Opera',
+	'MSIE'			=> 'Internet Explorer',
+	'Internet Explorer'	=> 'Internet Explorer',
+	'Trident.* rv'	=> 'Internet Explorer',
+	'Shiira'		=> 'Shiira',
+	'Firefox'		=> 'Firefox',
+	'Chimera'		=> 'Chimera',
+	'Phoenix'		=> 'Phoenix',
+	'Firebird'		=> 'Firebird',
+	'Camino'		=> 'Camino',
+	'Netscape'		=> 'Netscape',
+	'OmniWeb'		=> 'OmniWeb',
+	'Safari'		=> 'Safari',
+	'Mozilla'		=> 'Mozilla',
+	'Konqueror'		=> 'Konqueror',
+	'icab'			=> 'iCab',
+	'Lynx'			=> 'Lynx',
+	'Links'			=> 'Links',
+	'hotjava'		=> 'HotJava',
+	'amaya'			=> 'Amaya',
+	'IBrowse'		=> 'IBrowse',
+	'Maxthon'		=> 'Maxthon',
+	'Ubuntu'		=> 'Ubuntu Web Browser'
+);
+
+$mobiles = array(
+	// legacy array, old values commented out
+	'mobileexplorer'	=> 'Mobile Explorer',
+//  'openwave'			=> 'Open Wave',
+//	'opera mini'		=> 'Opera Mini',
+//	'operamini'			=> 'Opera Mini',
+//	'elaine'			=> 'Palm',
+	'palmsource'		=> 'Palm',
+//	'digital paths'		=> 'Palm',
+//	'avantgo'			=> 'Avantgo',
+//	'xiino'				=> 'Xiino',
+	'palmscape'			=> 'Palmscape',
+//	'nokia'				=> 'Nokia',
+//	'ericsson'			=> 'Ericsson',
+//	'blackberry'		=> 'BlackBerry',
+//	'motorola'			=> 'Motorola'
+
+	// Phones and Manufacturers
+	'motorola'		=> 'Motorola',
+	'nokia'			=> 'Nokia',
+	'palm'			=> 'Palm',
+	'iphone'		=> 'Apple iPhone',
+	'ipad'			=> 'iPad',
+	'ipod'			=> 'Apple iPod Touch',
+	'sony'			=> 'Sony Ericsson',
+	'ericsson'		=> 'Sony Ericsson',
+	'blackberry'	=> 'BlackBerry',
+	'cocoon'		=> 'O2 Cocoon',
+	'blazer'		=> 'Treo',
+	'lg'			=> 'LG',
+	'amoi'			=> 'Amoi',
+	'xda'			=> 'XDA',
+	'mda'			=> 'MDA',
+	'vario'			=> 'Vario',
+	'htc'			=> 'HTC',
+	'samsung'		=> 'Samsung',
+	'sharp'			=> 'Sharp',
+	'sie-'			=> 'Siemens',
+	'alcatel'		=> 'Alcatel',
+	'benq'			=> 'BenQ',
+	'ipaq'			=> 'HP iPaq',
+	'mot-'			=> 'Motorola',
+	'playstation portable'	=> 'PlayStation Portable',
+	'playstation 3'		=> 'PlayStation 3',
+	'playstation vita'  	=> 'PlayStation Vita',
+	'hiptop'		=> 'Danger Hiptop',
+	'nec-'			=> 'NEC',
+	'panasonic'		=> 'Panasonic',
+	'philips'		=> 'Philips',
+	'sagem'			=> 'Sagem',
+	'sanyo'			=> 'Sanyo',
+	'spv'			=> 'SPV',
+	'zte'			=> 'ZTE',
+	'sendo'			=> 'Sendo',
+	'nintendo dsi'	=> 'Nintendo DSi',
+	'nintendo ds'	=> 'Nintendo DS',
+	'nintendo 3ds'	=> 'Nintendo 3DS',
+	'wii'			=> 'Nintendo Wii',
+	'open web'		=> 'Open Web',
+	'openweb'		=> 'OpenWeb',
+
+	// Operating Systems
+	'android'		=> 'Android',
+	'symbian'		=> 'Symbian',
+	'SymbianOS'		=> 'SymbianOS',
+	'elaine'		=> 'Palm',
+	'series60'		=> 'Symbian S60',
+	'windows ce'	=> 'Windows CE',
+
+	// Browsers
+	'obigo'			=> 'Obigo',
+	'netfront'		=> 'Netfront Browser',
+	'openwave'		=> 'Openwave Browser',
+	'mobilexplorer'	=> 'Mobile Explorer',
+	'operamini'		=> 'Opera Mini',
+	'opera mini'	=> 'Opera Mini',
+	'opera mobi'	=> 'Opera Mobile',
+	'fennec'		=> 'Firefox Mobile',
+
+	// Other
+	'digital paths'	=> 'Digital Paths',
+	'avantgo'		=> 'AvantGo',
+	'xiino'			=> 'Xiino',
+	'novarra'		=> 'Novarra Transcoder',
+	'vodafone'		=> 'Vodafone',
+	'docomo'		=> 'NTT DoCoMo',
+	'o2'			=> 'O2',
+
+	// Fallback
+	'mobile'		=> 'Generic Mobile',
+	'wireless'		=> 'Generic Mobile',
+	'j2me'			=> 'Generic Mobile',
+	'midp'			=> 'Generic Mobile',
+	'cldc'			=> 'Generic Mobile',
+	'up.link'		=> 'Generic Mobile',
+	'up.browser'	=> 'Generic Mobile',
+	'smartphone'	=> 'Generic Mobile',
+	'cellphone'		=> 'Generic Mobile'
+);
+
+// There are hundreds of bots but these are the most common.
+$robots = array(
+	'googlebot'		=> 'Googlebot',
+	'msnbot'		=> 'MSNBot',
+	'baiduspider'		=> 'Baiduspider',
+	'bingbot'		=> 'Bing',
+	'slurp'			=> 'Inktomi Slurp',
+	'yahoo'			=> 'Yahoo',
+	'ask jeeves'		=> 'Ask Jeeves',
+	'fastcrawler'		=> 'FastCrawler',
+	'infoseek'		=> 'InfoSeek Robot 1.0',
+	'lycos'			=> 'Lycos',
+	'yandex'		=> 'YandexBot',
+	'mediapartners-google'	=> 'MediaPartners Google',
+	'CRAZYWEBCRAWLER'	=> 'Crazy Webcrawler',
+	'adsbot-google'		=> 'AdsBot Google',
+	'feedfetcher-google'	=> 'Feedfetcher Google',
+	'curious george'	=> 'Curious George',
+	'ia_archiver'		=> 'Alexa Crawler',
+	'MJ12bot'		=> 'Majestic-12',
+	'Uptimebot'		=> 'Uptimebot'
+);

+ 86 - 0
apps/application/controllers/Home.php

@@ -0,0 +1,86 @@
+<?php 
+
+
+defined('BASEPATH') or exit( 'No direct script access allowed' );
+
+class Home extends CI_Controller
+{
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    public function index($cid = 0, $page = "null")
+    {
+        if( !defined("Web_On") || Web_On == 0 ) 
+        {
+            exit( "Hello World" );
+        }
+
+        $this->load->database();
+        $cid = (int) $cid;
+        if( $page == "null" ) 
+        {
+            $page = $cid;
+        }
+        else
+        {
+            $page = (int) $page;
+        }
+
+        if( $page == 0 ) 
+        {
+            $page = 1;
+        }
+
+        $where_str = (0 < $cid ? "where cid=" . $cid : "");
+        $per_page = 20;
+        $sel_sql = "select * from " . CS_SqlPrefix . "video " . $where_str . " order by id desc";
+        $tol_sql = "select count(*) as count from " . CS_SqlPrefix . "video " . $where_str;
+        $total_res = $this->db->query($tol_sql)->row();
+        $total = $total_res->count;
+        $totalPages = (ceil($total / $per_page) ? ceil($total / $per_page) : 1);
+        $page = ($totalPages < $page ? $totalPages : $page);
+        if( $total < $per_page ) 
+        {
+            $per_page = $total;
+        }
+
+        $sel_sql .= " limit " . $per_page * ($page - 1) . "," . $per_page;
+        $query = $this->db->query($sel_sql)->result();
+        $_pageNum = 5;
+        $pages = ($page < $totalPages ? $totalPages : $page);
+        $pages = ($totalPages < $page ? $page : $totalPages);
+        $_start = $page - floor($_pageNum / 2);
+        $_start = ($_start < 1 ? 1 : $_start);
+        $_end = $page + floor($_pageNum / 2);
+        $_end = ($pages < $_end ? $pages : $_end);
+        $_curPageNum = $_end - $_start + 1;
+        if( $_curPageNum < $_pageNum && 1 < $_start ) 
+        {
+            $_start = $_start - ($_pageNum - $_curPageNum);
+            $_start = ($_start < 1 ? 1 : $_start);
+            $_curPageNum = $_end - $_start + 1;
+        }
+
+        if( $_curPageNum < $_pageNum && $_end < $pages ) 
+        {
+            $_end = $_end + $_pageNum - $_curPageNum;
+            $_end = ($pages < $_end ? $pages : $_end);
+        }
+
+        $data["cid"] = $cid;
+        $data["page"] = $page;
+        $data["pagestart"] = $_start;
+        $data["pageend"] = $_end;
+        $data["pagejs"] = $totalPages;
+        $data["pagenum"] = $total;
+        $data["video"] = $query;
+        $data["vlist"] = $this->db->query("select * from " . CS_SqlPrefix . "class order by id asc limit 6")->result();
+        $this->load->view("home.html", $data);
+    }
+
+}
+
+
+

+ 61 - 0
apps/application/controllers/Index.php

@@ -0,0 +1,61 @@
+<?php 
+
+defined('BASEPATH') or exit( 'No direct script access allowed' );
+class Index extends CI_Controller
+{
+    public function __construct()
+    {
+        parent::__construct();
+        $this->load->database();
+        $this->load->model("User");
+        $this->User->Login();
+    }
+    public function index()
+    {
+        $ip = ("/" == DIRECTORY_SEPARATOR ? $_SERVER["SERVER_ADDR"] : gethostbyname($_SERVER["SERVER_NAME"]));
+
+        $table = CS_SqlPrefix . "video";
+        if( !$this->db->field_exists("pic", $table) ) 
+        {
+            $this->db->query("ALTER TABLE " . $table . " ADD pic varchar(255) DEFAULT '' COMMENT '图片保存路径'");
+            $this->db->query("ALTER TABLE " . $table . " ADD m3u8 varchar(255) DEFAULT '' COMMENT '图片保存路径'");
+        }
+        $data["ctcms"] = $arr;
+        $data["news_api"] = "http://m3u8.ctcms.cn/api/news";
+        $this->load->view("index.html", $data);
+    }
+    public function main()
+    {
+        $data["ver"] = file_get_contents(FCPATH . "libraries/ver.txt");
+        $this->load->view("main.html", $data);
+    }
+    public function update()
+    {
+        $ver = file_get_contents(FCPATH . "libraries/ver.txt");
+        $ip = ("/" == DIRECTORY_SEPARATOR ? $_SERVER["SERVER_ADDR"] : gethostbyname($_SERVER["SERVER_NAME"]));
+        $json = get_curl("http://m3u8.ctcms.cn/api/update?ip=" . $ip . "&ver=" . $ver . "&php=" . PHP_VERSION);
+        $arr = json_decode($json, 1);
+        if( $ver == $arr["ver"] ) 
+        {
+            echo "当前已经是最新版本,无需更新~!";
+        }
+        else
+        {
+            if( empty($arr["list"]) ) 
+            {
+                exit( "更新文件列表为空" );
+            }
+            foreach( $arr["list"] as $k => $filename ) 
+            {
+                $data = get_curl("http://m3u8.ctcms.cn/api/update/data?ip=" . $ip . "&ver=" . $ver . "&filename=" . $filename . "&php=" . PHP_VERSION);
+                if( !empty($data) && strpos($data, "--ctyunend--") !== false ) 
+                {
+                    write_file(FCPATH . $filename, str_replace("--ctyunend--", "", $data));
+                }
+            }
+            write_file(FCPATH . 'libraries/ver.txt', $arr["ver"]);
+            echo 'ok';
+        }
+    }
+}
+

File diff suppressed because it is too large
+ 65 - 0
apps/application/controllers/Install.php


+ 108 - 0
apps/application/controllers/Login.php

@@ -0,0 +1,108 @@
+<?php 
+
+
+
+
+
+
+defined('BASEPATH') or exit( 'No direct script access allowed' );
+
+class Login extends CI_Controller
+{
+    public function __construct()
+    {
+        parent::__construct();
+        $this->load->database();
+    }
+
+    public function index()
+    {
+        $data["backurl"] = "";
+        $this->load->view("login.html", $data);
+    }
+
+    public function save()
+    {
+        $this->deldir();
+        $adminname = $this->input->get_post("adminname", true);
+        $adminpass = $this->input->get_post("adminpass", true);
+        if( empty($adminname) || empty($adminpass) ) 
+        {
+            getjson("账号密码不能留空");
+        }
+
+        if( $adminname != Admin_Name || $adminpass != Admin_Code ) 
+        {
+            getjson("账号密码不正确");
+        }
+
+        set_cookie('user_name', $adminname, time() + 86400 * 30);
+        set_cookie('user_login', md5($adminname . $adminpass . "zhw"), time() + 86400 * 30);
+        $info["url"] = site_url("index");
+        getjson($info, 0);
+    }
+
+    public function ext()
+    {
+        set_cookie("user_name", "", time() - 3600);
+        set_cookie('user_login', time() - 3600);
+        header('location:' . site_url('login'));
+        exit();
+    }
+
+    public function deldir($dir = "", $sid = 0)
+    {
+        if( $dir == "" ) 
+        {
+            if( substr(Video_Path, 0, 2) == "./" ) 
+            {
+                $Video_Path = FCPATH . substr(Video_Path, 2);
+            }
+            else
+            {
+                $Video_Path = Video_Path;
+            }
+
+            $dir = $Video_Path . "/temp";
+        }
+
+        if( !is_dir($dir) ) 
+        {
+            return true;
+        }
+
+        $dh = opendir($dir);
+        while( $file = readdir($dh) ) 
+        {
+            if( $file != "." && $file != ".." ) 
+            {
+                $fullpath = $dir . "/" . $file;
+                if( !is_dir($fullpath) ) 
+                {
+                    unlink($fullpath);
+                }
+                else
+                {
+                    if( $file != date("Ymd") ) 
+                    {
+                        $this->deldir($fullpath, 1);
+                    }
+
+                }
+
+            }
+
+        }
+        closedir($dh);
+        if( $sid == 1 ) 
+        {
+            rmdir($dir);
+        }
+
+        return true;
+    }
+
+}
+
+
+

+ 115 - 0
apps/application/controllers/M3u8.php

@@ -0,0 +1,115 @@
+<?php
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+class M3u8 extends CI_Controller {
+
+	function __construct(){
+	    parent::__construct();
+		//防盗链
+		if(!is_referer(1)){
+			Header("HTTP/1.1 404 Not Found");
+			exit('The path is not correct');
+		}
+	}
+
+	//m3u8列表
+	public function index($uri=''){
+		$m3u8path = sys_auth(str_replace('.m3u8', '', $uri),1);
+		if(empty($m3u8path)){
+			Header("HTTP/1.1 404 Not Found");
+			exit('File address error');
+		}
+		$Mu_Path = substr(Mu_Path,0,2) == './' ? FCPATH.substr(Mu_Path,2) : Mu_Path;
+		$m3u8path = $m3u8path;
+		if(!file_exists($Mu_Path.$m3u8path)){
+			Header("HTTP/1.1 404 Not Found");
+			exit('404');
+		}
+		//获取实际路径
+		$varr = explode('/', $m3u8path);
+		array_pop($varr);
+		$vpath = implode('/',$varr).'/';
+		$data = file_get_contents($Mu_Path.$m3u8path);
+		$marr = explode("\n", $data);
+		for ($i=0; $i < count($marr); $i++) { 
+			if(substr($marr[$i],-3) == '.ts'){
+				$yts = str_replace("\r", "", $marr[$i]);
+				if(Mu_Tsext == ''){
+					$xts = 'ts/'.sys_auth($vpath.$yts).'.ts';
+				}else{
+					$xts = 'ts/'.sys_auth($vpath.$yts).'.'.Mu_Tsext;
+				}
+				$data = str_replace($yts, $xts, $data);
+			}
+		}
+		$xkey = sys_auth($vpath.'key.key');
+		$data = str_replace('URI="key.key"', 'URI="key/'.$xkey.'.key"', $data);
+		header('Content-type: application/vnd.apple.mpegURL');
+		header('Content-disposition: attachment; filename='.$uri);
+		echo $data;
+	}
+
+	//秘钥
+	public function key($uri=''){
+		$keypath = sys_auth(str_replace('.key', '', $uri),1);
+		if(empty($keypath)){
+			Header("HTTP/1.1 404 Not Found");
+			exit('File address error');
+		}
+		$Mu_Path = substr(Mu_Path,0,2) == './' ? FCPATH.substr(Mu_Path,2) : Mu_Path;
+		$key_path = $Mu_Path.$keypath;
+		if(!file_exists($key_path)){
+			Header("HTTP/1.1 404 Not Found");
+			exit('404');
+		}
+		$data = file_get_contents($key_path);
+		echo $data;
+	}
+
+	//ts分片
+	public function ts($uri=''){
+		if(Mu_Tsext == ''){
+			$tspath = sys_auth(str_replace('.ts', '', $uri),1);
+		}else{
+			$tspath = sys_auth(str_replace('.'.Mu_Tsext, '', $uri),1);
+		}
+		if(empty($tspath)){
+			Header("HTTP/1.1 404 Not Found");
+			exit('File address error');
+		}
+		$Mu_Path = substr(Mu_Path,0,2) == './' ? FCPATH.substr(Mu_Path,2) : Mu_Path;
+		$ts_path = $Mu_Path.$tspath;
+		if(!file_exists($ts_path)){
+			Header("HTTP/1.1 404 Not Found");
+			exit('404');
+		}
+		$filesize = sprintf("%u", filesize($ts_path));
+		header('Content-type: video/mp2t');
+		header('Content-length: '.$filesize);
+		header('Content-disposition: attachment; filename='.$uri);
+		$data = file_get_contents($ts_path);
+		echo $data;
+	}
+
+	//图片
+	public function pic($uri=''){
+		$picpath = sys_auth(str_replace(array('.jpg','.gif'), '', $uri),1);
+		if(empty($picpath)){
+			Header("HTTP/1.1 404 Not Found");
+			exit('File address error');
+		}
+		$Mu_Path = substr(Mu_Path,0,2) == './' ? FCPATH.substr(Mu_Path,2) : Mu_Path;
+		$pic_path = $Mu_Path.$picpath;
+		if(!file_exists($pic_path)){
+			Header("HTTP/1.1 404 Not Found");
+			exit('404');
+		}
+		$image = file_get_contents($pic_path);
+		if(strpos($uri, '.jpg')){
+			header('Content-type: image/jpg');
+		}else{
+			header('Content-type: image/gif');
+		}
+		echo $image;
+	}
+}

+ 51 - 0
apps/application/controllers/Play.php

@@ -0,0 +1,51 @@
+<?php
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+class Play extends CI_Controller {
+	function __construct(){
+	    parent::__construct();
+		//防盗链
+		if(!is_referer()){
+			show_404();exit;
+		}
+	    $this->load->database();
+	}
+
+	public function index($id=''){
+		if(!is_numeric($id)){
+			$id = safe_replace($id);
+			if(empty($id)) exit('参数错误');
+			$row = $this->db->query("select * from ".CS_SqlPrefix."video where vid='".$id."'")->row();
+		}else{
+			if($id<1) exit('参数错误');
+			$row = $this->db->query("select * from ".CS_SqlPrefix."video where id=".$id)->row();
+		}
+		if(!$row) exit('该视频不存在');
+		//if($row->sid == 0) exit('视频处理中...');
+		$data['title'] = $row->name;
+		//获取视频地址,兼容其他平台转移过来的数据
+		if(!empty($row->path) && substr($row->path,-5) == '.m3u8'){
+			$parr = explode('/', $row->path);
+			$len = count($parr)-1;
+			unset($parr[$len]);
+			$path = implode('/',array_filter($parr));
+			$rowpath = $path.'/index.m3u8';
+			$picpath = $path.'/1.jpg';
+			$data['playlink'] = m3u8_link($rowpath);
+			$data['piclink'] = m3u8_link($picpath,'jpg');
+		}else{
+			if(empty($row->pic)){
+				$vodpath = m3u8_dir($row->id,$row->addtime,'m3u8',1);
+				$picpath = m3u8_dir($row->id,$row->addtime,'jpg',1);
+				$data['playlink'] = m3u8_link($vodpath);
+				$data['piclink'] = m3u8_link($picpath,'jpg');
+			}else{
+				$data['playlink'] = m3u8_link($row->m3u8);
+				$data['playlink2'] = m3u8_link($row->m3u8,'m3u8',Mu_Kbps2);
+				if(Mu_Type == 0) $data['playlink2'] = '';
+				$data['piclink'] = m3u8_link($row->pic,'jpg');
+			}
+		}
+		$this->load->view('play.html',$data);
+	}
+}

+ 240 - 0
apps/application/controllers/Setting.php

@@ -0,0 +1,240 @@
+<?php
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+class Setting extends CI_Controller {
+	function __construct(){
+	    parent::__construct();
+	    $this->load->model('User');
+	    $this->User->Login();
+	}
+	public function index($sid=1){
+
+		$str = file_get_contents(FCPATH.'packs/zimu.ass');
+		$zimu = end(explode('[Events]', $str));
+		$data['sid'] = $sid;
+		$data['zimu'] = str_replace("\r\n","",str_replace('{\b0}{\fs}{\fn}','',$zimu));
+		$this->load->view('setting.html',$data);
+	}
+	public function save(){
+
+		$Web_Path = $this->input->get_post('Web_Path',true);
+		$Web_Url = $this->input->get_post('Web_Url',true);
+		$Web_Domain = $this->input->get_post('Web_Domain',true);
+		$Web_Play = $this->input->get_post('Web_Play',true);
+		$Web_On = (int)$this->input->get_post('Web_On',true);
+		$Admin_Name = $this->input->get_post('Admin_Name',true);
+		$Admin_Code = $this->input->get_post('Admin_Code',true);
+
+		$Web_Url = str_replace('http://','',$Web_Url);
+		$Web_Domain = str_replace('http://','',$Web_Domain);
+		$Web_Play = str_replace('http://','',$Web_Play);
+		if(empty($Web_Url) || empty($Web_Domain)){
+			getjson('抱歉,域名不能留空');
+		}
+		if(empty($Admin_Name) || empty($Admin_Code)){
+			getjson('抱歉,登录名称或密码不能留空');
+		}
+
+		$Jpg_On = intval($this->input->get_post('Jpg_On',true));
+		$Jpg_Num = intval($this->input->get_post('Jpg_Num',true));
+		$Jpg_Time = intval($this->input->get_post('Jpg_Time',true));
+		$Jpg_Size = $this->input->get_post('Jpg_Size',true);
+		if($Jpg_Num == 0) $Jpg_Num = 1;
+		if($Jpg_Num > 20) getjson('抱歉,截图最多只能20张');
+
+		$Gif_On = intval($this->input->get_post('Gif_On',true));
+		$Gif_Time = intval($this->input->get_post('Gif_Time',true));
+		$Gif_Len = intval($this->input->get_post('Gif_Len',true));
+		$Gif_Size = $this->input->get_post('Gif_Size',true);
+		if(empty($Web_Path)) $Web_Path = '/';
+
+		if($Jpg_On==0){
+			$jpgarr = explode('x', $Jpg_Size);
+			if(!empty($Jpg_Size) && (sizeof($jpgarr)!=2 || intval($jpgarr[0])<1 || intval($jpgarr[1])<1)){
+				getjson('截图尺寸格式不正确');
+			}
+		}
+		if($Gif_On==0){
+			$gifarr = explode('x', $Gif_Size);
+			if(!empty($Gif_Size) && (sizeof($gifarr)!=2 || intval($gifarr[0])<1 || intval($gifarr[1])<1)){
+				getjson('GIF动图尺寸格式不正确');
+			}
+			if($Gif_Len<1) getjson('请设置GIF动图时长');
+			if($Gif_Len>10) $Gif_Len = 10;
+		}
+
+		$Mu_On = intval($this->input->get_post('Mu_On',true));
+		$Mu_Time = intval($this->input->get_post('Mu_Time',true));
+		$Mu_Type = intval($this->input->get_post('Mu_Type',true));
+		$Mu_Size = $this->input->get_post('Mu_Size',true);
+		$Mu_Path = $this->input->get_post('Mu_Path',true);
+		$Mu_M3u8_Name = $this->input->get_post('Mu_M3u8_Name',true);
+		$Mu_Pic_Name = $this->input->get_post('Mu_Pic_Name',true);
+		$Mu_Kbps = $this->input->get_post('Mu_Kbps',true);
+		$Mu_Key = $this->input->get_post('Mu_Key',true);
+		$Mu_Dir = $this->input->get_post('Mu_Dir',true);
+		$Mu_Ffpath = $this->input->get_post('Mu_Ffpath',true);
+		$Mu_PtTime = intval($this->input->get_post('Mu_PtTime',true));
+		$Mu_Tsext = $this->input->get_post('Mu_Tsext',true);
+		$Mu_Preset = $this->input->get_post('Mu_Preset',true);
+		$Mu_Size2 = $this->input->get_post('Mu_Size2',true);
+		$Mu_Kbps2 = $this->input->get_post('Mu_Kbps2',true);
+
+		if(empty($Mu_Ffpath)) getjson('ffmpeg安装路径不能留空');
+		if(empty($Mu_Path)) getjson('转码保存路径不能留空');
+		$muarr = explode('x', $Mu_Size);
+		if(!empty($Mu_Size) && (sizeof($muarr)!=2 || intval($muarr[0])<1 || intval($muarr[1])<1)){
+			getjson('转码尺寸一格式不正确');
+		}
+		$muarr = explode('x', $Mu_Size2);
+		if(!empty($Mu_Size2) && (sizeof($muarr)!=2 || intval($muarr[0])<1 || intval($muarr[1])<1)){
+			getjson('转码尺寸二格式不正确');
+		}
+		if(strpos($Mu_M3u8_Name, '[id]') === false && strpos($Mu_M3u8_Name, '[md5]') === false){
+			getjson('M3U8后缀格式不正确');
+		}
+		if(empty($Mu_Pic_Name)){
+			getjson('截图后缀不能为空');
+		}
+
+		//水印设置
+		$Wm_On = intval($this->input->get_post('Wm_On',true));
+		$Wm_Lt = $this->input->get_post('Wm_Lt',true);
+		$Wm_On2 = intval($this->input->get_post('Wm_On2',true));
+		$Wm_Lt2 = $this->input->get_post('Wm_Lt2',true);
+		$Wm_On3 = intval($this->input->get_post('Wm_On3',true));
+		$Wm_Lt3 = $this->input->get_post('Wm_Lt3',true);
+		$Wm_On4 = intval($this->input->get_post('Wm_On4',true));
+		$Wm_Lt4 = $this->input->get_post('Wm_Lt4',true);
+
+		$Wm_Zm = intval($this->input->get_post('Wm_Zm',true));
+		$Wm_ZmNei = $this->input->get_post('Wm_ZmNei');
+		if($Wm_Zm==1){
+			if(empty($Wm_ZmNei)) getjson('字幕内容不能为空');
+		}
+		if(substr($Wm_ZmNei,0,9) != 'Dialogue:'){
+			getjson('字幕内容格式错误');
+		}
+		//修改字幕内容
+		$str = file_get_contents(FCPATH.'packs/zimu.ass');
+		$zmarr = explode('Dialogue:',$Wm_ZmNei);
+		unset($zmarr[0]);
+		$Wm_ZmNei = "Dialogue:".implode("\r\n{\\b0}{\\fs}{\\fn}\r\nDialogue:",$zmarr);
+		$zimu = current(explode('[Events]', $str))."[Events]\r\n".$Wm_ZmNei;
+		write_file(FCPATH.'packs/zimu.ass', $zimu);
+
+		if($Wm_On==1){
+			$wmarr = explode(':', $Wm_Lt3);
+			if(sizeof($wmarr)<2) getjson('左上角水印间距格式不正确');
+			$Wm_Lt = intval($wmarr[0]).':'.intval($wmarr[1]);
+		}
+		if($Wm_On2==1){
+			$wmarr = explode(':', $Wm_Lt4);
+			if(sizeof($wmarr)<2) getjson('右上角水印间距格式不正确');
+			$Wm_Lt2 = intval($wmarr[0]).':'.intval($wmarr[1]);
+		}
+		if($Wm_On3==1){
+			$wmarr = explode(':', $Wm_Lt);
+			if(sizeof($wmarr)<2) getjson('左下角水印间距格式不正确');
+			$Wm_Lt3 = intval($wmarr[0]).':'.intval($wmarr[1]);
+		}
+		if($Wm_On4==1){
+			$wmarr = explode(':', $Wm_Lt2);
+			if(sizeof($wmarr)<2) getjson('右下角水印间距格式不正确');
+			$Wm_Lt4 = intval($wmarr[0]).':'.intval($wmarr[1]);
+		}
+		//保存路径
+		$Video_Path = $this->input->get_post('Video_Path',true);
+		$Video_SrcOn = intval($this->input->get_post('Video_SrcOn',true));
+		$Video_M3u8On = intval($this->input->get_post('Video_M3u8On',true));
+		if(empty($Video_Path)) getjson('上传文件路径不能为空');
+
+	
+		$Web_CrossDomain = $this->input->get_post('Web_CrossDomain',true);
+		$Web_CrossOn = intval($this->input->get_post('Web_CrossOn',true));
+		$Web_Cross = intval($this->input->get_post('Web_Cross',true));
+		$Web_M3u8On = intval($this->input->get_post('Web_M3u8On',true));
+
+	
+		$Api_On = intval($this->input->get_post('Api_On',true));
+		$Api_Url = $this->input->get_post('Api_Url',true);
+		$Api_Key = $this->input->get_post('Api_Key',true);
+		if($Api_On == 1){
+			if(empty($Api_Url) || empty($Api_Key)) getjson('同步地址和同步秘钥不能为空');
+			if(substr($Api_Url,0,7) != 'http://' && substr($Api_Url,0,8) != 'https://') getjson('同步地址格式不正确');
+		}
+		$Up_Api = intval($this->input->get_post('Up_Api',true));
+		$Up_Miao = intval($this->input->get_post('Up_Miao',true));
+		$Up_Ext = $this->input->get_post('Up_Ext',true);
+		if(empty($Up_Ext)) getjson('支持的上传格式不能为空');
+
+		//m3u8域名
+		if(substr($Mu_Path,0,2) !== './' && $Web_M3u8On == 0 && empty($Web_Play)){
+			getjson('m3u8播放域名不能留空');
+		}
+
+	
+		$strs = "<?php"."\r\n";
+        $strs .= "define('Web_Url','".$Web_Url."'); //站点域名  \r\n";
+        $strs .= "define('Web_Domain','".$Web_Domain."'); //转码域名  \r\n";
+        $strs .= "define('Web_Play','".$Web_Play."'); //m3u8域名  \r\n";
+        $strs .= "define('Web_Path','".$Web_Path."'); //站点路径  \r\n";
+        $strs .= "define('Web_On',".$Web_On."); //是否开启前台,0关闭1开启  \r\n";
+        $strs .= "define('Admin_Name','".$Admin_Name."');  //后台用户名  \r\n";
+        $strs .= "define('Admin_Code','".$Admin_Code."');  //后台验证码  \r\n";
+        $strs .= "define('Jpg_On',".$Jpg_On.");  //截图开关,0打开1关闭  \r\n";
+        $strs .= "define('Jpg_Num',".$Jpg_Num.");  //截图张数  \r\n";
+        $strs .= "define('Jpg_Time',".$Jpg_Time.");  //间隔秒数  \r\n";
+        $strs .= "define('Jpg_Size','".$Jpg_Size."');  //截图尺寸  \r\n";
+        $strs .= "define('Gif_On',".$Gif_On.");  //动图开关,0打开1关闭  \r\n";
+        $strs .= "define('Gif_Time',".$Gif_Time.");  //动图开始位置  \r\n";
+        $strs .= "define('Gif_Len',".$Gif_Len.");  //动图时长  \r\n";
+        $strs .= "define('Gif_Size','".$Gif_Size."');  //动图尺寸  \r\n";
+        $strs .= "define('Mu_On',".$Mu_On.");  //加密开关,0加密1不加密  \r\n";
+        $strs .= "define('Mu_Time',".$Mu_Time.");  //每个TS的时长  \r\n";
+        $strs .= "define('Mu_Type',".$Mu_Type.");  //多码率  \r\n";
+        $strs .= "define('Mu_Kbps','".$Mu_Kbps."');  //转码码率  \r\n";
+        $strs .= "define('Mu_Size','".$Mu_Size."');  //缩放尺寸  \r\n";
+        $strs .= "define('Mu_Kbps2','".$Mu_Kbps2."');  //转码码率2  \r\n";
+        $strs .= "define('Mu_Size2','".$Mu_Size2."');  //缩放尺寸2  \r\n";
+        $strs .= "define('Mu_Path','".$Mu_Path."');  //保存路径  \r\n";
+        $strs .= "define('Mu_Dir','".$Mu_Dir."');  //待转码目录  \r\n";
+        $strs .= "define('Mu_Key','".$Mu_Key."');  //加密密钥  \r\n";
+        $strs .= "define('Mu_Tsext','".$Mu_Tsext."');  //Ts伪装后缀  \r\n";
+        $strs .= "define('Mu_PtTime',".$Mu_PtTime.");  //跳过片头秒数  \r\n";
+        $strs .= "define('Mu_Ffpath','".$Mu_Ffpath."');  //ffmpeg安装路径\r\n";
+        $strs .= "define('Mu_M3u8_Name','".$Mu_M3u8_Name."');  //转码保存路径格式\r\n";
+        $strs .= "define('Mu_Pic_Name','".$Mu_Pic_Name."'); //截图保存路径格式\r\n";
+        $strs .= "define('Mu_Preset','".$Mu_Preset."'); //转码优先方式\r\n";
+        $strs .= "define('Wm_On',".$Wm_On.");  //左上水印开,0关闭1开启  \r\n";
+        $strs .= "define('Wm_Lt','".$Wm_Lt."');  //左上距离边界位置  \r\n";
+        $strs .= "define('Wm_On2',".$Wm_On2.");  //右上水印开,0关闭1开启  \r\n";
+        $strs .= "define('Wm_Lt2','".$Wm_Lt2."');  //右上距离边界位置  \r\n";
+        $strs .= "define('Wm_On3',".$Wm_On3.");  //左下水印开,0关闭1开启  \r\n";
+        $strs .= "define('Wm_Lt3','".$Wm_Lt3."');  //左下距离边界位置  \r\n";
+        $strs .= "define('Wm_On4',".$Wm_On4.");  //右下水印开,0关闭1开启  \r\n";
+        $strs .= "define('Wm_Lt4','".$Wm_Lt4."');  //右下距离边界位置  \r\n";
+        $strs .= "define('Wm_Zm',".$Wm_Zm.");  //是否启用字幕。0关闭,1启用  \r\n";
+        $strs .= "define('Video_Path','".$Video_Path."');  //上传文件保存路径(绝对路径)  \r\n";
+        $strs .= "define('Video_SrcOn',".$Video_SrcOn.");  //保留原文件0保留1不保留  \r\n";
+        $strs .= "define('Video_M3u8On',".$Video_M3u8On.");  //删除保留M3u8文件0保留1不保留  \r\n";
+        $strs .= "define('Web_CrossDomain','".$Web_CrossDomain."');  //防盗链域名分号隔开  \r\n";
+        $strs .= "define('Web_CrossOn',".$Web_CrossOn.");  //防盗链开关  \r\n";
+        $strs .= "define('Web_Cross',".$Web_Cross.");  //防盗链直接播放开关  \r\n";
+        $strs .= "define('Web_M3u8On',".$Web_M3u8On.");  //m3u8地址防盗开关  \r\n";
+        $strs .= "define('Api_On',".$Api_On.");  //api同步开关0关闭1开启  \r\n";
+        $strs .= "define('Api_Url','".$Api_Url."');  //api同步地址  \r\n";
+        $strs .= "define('Api_Key','".$Api_Key."');  //api同步秘钥  \r\n";
+        $strs .= "define('Up_Miao',".$Up_Miao.");  //秒传开关0关闭1开启\r\n";
+        $strs .= "define('Up_Api',".$Up_Api.");  //外部上传开关0关闭1开启\r\n";
+        $strs .= "define('Up_Ext','".$Up_Ext."');  //支持的上传格式";
+        //写文件
+        if (!write_file(FCPATH.'libraries/config.php', $strs)){
+            getjson('抱歉,无写入权限');
+        }else{
+        	$info['url'] = site_url('setting/index').'?v='.rand(1000,1999);
+            $info['msg'] = '恭喜您,写入成功';
+        	getjson($info,0);
+        }
+	}
+}

+ 171 - 0
apps/application/controllers/Ting.php

@@ -0,0 +1,171 @@
+<?php
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+class Ting extends CI_Controller {
+	function __construct(){
+	    parent::__construct();
+	}
+
+	public function index(){
+		$this->load->view('ting.html');
+	}
+
+	//执行转码
+	public function transcode(){
+		if(substr(Video_Path,0,2) == './'){
+			$Video_Path = FCPATH.substr(Video_Path,2);
+		}else{
+			$Video_Path = Video_Path;
+		}
+		$id = 0;
+		$xt = php_uname();
+		if(strpos($xt, 'Windows') !== false){
+			$jc = shell_exec('tasklist');
+		}else{
+			$jc = shell_exec('ps aux | less');
+		}
+		//获取数据库未转码
+		$this->load->database();
+		$row = $this->db->query("select * from ".CS_SqlPrefix."queue order by id asc limit 1")->row();
+		if($row){
+			$id = $row->vid;
+			$vod = $this->db->query("select id,name,m3u8,addtime from ".CS_SqlPrefix."video where id=".$id)->row();
+			//判断转码是否完毕
+			if(substr(Mu_Path,0,2) == './'){
+				$m3u8_path = FCPATH.substr(Mu_Path,2).$vod->m3u8;
+			}else{
+				$m3u8_path = Mu_Path.$vod->m3u8;
+			}
+			if(Mu_Type == 1){
+				$ext = end(explode('/', $m3u8_path));
+				$m3u8_path2 = str_replace($ext, Mu_Kbps2.'/'.$ext, $m3u8_path);
+				$is_m3u8 = (file_exists($m3u8_path) && file_exists($m3u8_path2));
+			}else{
+				$is_m3u8 = file_exists($m3u8_path);
+			}
+			if($is_m3u8){
+				//正在转码,终止执行
+				if(strpos($jc, 'ffmpeg') !== false){
+					exit('0');
+				}
+				$m3u8_neir = file_get_contents($m3u8_path);
+				if(strpos($m3u8_neir,'#EXT-X-ENDLIST') !== false){
+					//转换完毕
+					$this->db->update('video',array('sid'=>1),array('id'=>$id));
+					//发送成功API消息
+					get_api('api',array('vid'=>$vod->id,'name'=>$vod->name,'code'=>1));
+				}else{
+					//转码失败
+					$this->db->update('video',array('sid'=>2),array('id'=>$id));
+					//发送失败API消息
+					get_api('api',array('vid'=>$vod->id,'name'=>$vod->name,'code'=>2));
+				}
+				$this->db->delete('queue',array('id'=>$row->id));//删除队列
+				$id = 0;
+			}else{
+				//失败了,进程还在,需要杀掉进程继续下一个转码
+				if(strpos($jc, 'ffmpeg') !== false && $row->addtime > 0 && $row->addtime+120 < time()){
+					//杀掉进程
+					if(strpos($xt, 'Windows') !== false){
+						exec('taskkill /im ffmpeg.exe /f');
+					}else{
+						exec('ps aux | grep "ffmpeg" | cut -c 9-15 | xargs kill -9');
+					}
+					$this->db->update('video',array('sid'=>2),array('id'=>$id));
+					$this->db->delete('queue',array('id'=>$row->id));//删除队列
+					//发送失败API消息
+					get_api('api',array('vid'=>$vod->id,'name'=>$vod->name,'code'=>2));
+				}
+			}
+		}
+		if($id == 0){
+			//进入待转码目录
+			if(Mu_Dir != '' && is_dir(Mu_Dir)){
+				$dirinfo = dir_file(Mu_Dir);
+				if(!empty($dirinfo['file'])){
+					$filename = $dirinfo['file'];
+					$Mu_Dir = $dirinfo['dir'];
+					if(substr($Mu_Dir,-1) != '/') $Mu_Dir.='/';
+					//文件后缀
+					$file_ext = strtolower(trim(substr(strrchr($filename, '.'), 1)));
+					//入库名字
+					$video_name = safe_replace(str_replace('.'.$file_ext, '', iconv('gbk', 'utf-8',$filename)));
+					//定义允许上传的文件扩展名
+					$ext_arr = array_filter(explode('|', Up_Ext));
+					//判断文件后缀
+					if(in_array($file_ext, $ext_arr) == false) {
+						unlink($Mu_Dir.$filename);
+					}else{
+						$uploadStr = date('Y').'/'.date('m').'/'.date('d').'/';
+						creatdir($Video_Path.$uploadStr); //创建目录
+						$newname = date('YmdHis').rand(1111,9999).'.'.$file_ext;
+						//video
+						$data['name'] = $video_name;
+						$data['path'] = $uploadStr.$newname;
+						$data['addtime'] = time();
+						//移动目录
+						$res = rename($Mu_Dir.$filename,$Video_Path.$uploadStr.$newname);
+						if($res){
+							$this->load->library('xyz');
+							$format = $this->xyz->format($Video_Path.$uploadStr.$newname);
+							if(empty($format['size']) || empty($format['duration'])){
+								rename($Video_Path.$uploadStr.$newname,$Mu_Dir.$filename);
+								exit('ffmpeg error'); 
+							}
+							$data['duration'] = $format['duration'];
+							$data['size'] = $format['size'];
+							$data['md5'] = md5_file($Video_Path.$uploadStr.$newname);
+							//判断是否存在
+							$res = $this->db->query("select id from ".CS_SqlPrefix."video where name='".$video_name."'")->row();
+							if(!$res){
+								//判断分类是否存在
+								$carr = explode('/',str_replace(Mu_Dir,'',$Mu_Dir));
+								$cname = empty($carr[0]) ? $carr[1] : $carr[0];
+								$cname = safe_replace(iconv('gbk','utf-8',$cname));
+								if(!empty($cname)){
+									$rec = $this->db->query("select id from ".CS_SqlPrefix."class where name='".$cname."'")->row();
+									if($rec){
+										$data['cid'] = $rec->id;
+									}else{
+										$arr2['name'] = $cname;
+										$arr2['xid'] = 10;
+										$this->db->insert("class",$arr2);
+										$data['cid'] = $this->db->insert_id();
+									}
+								}
+								//新增视频入库
+								$this->db->insert("video",$data);
+								$id = $this->db->insert_id();
+								//M3U8保存目录
+								$m3u8_dir = substr(Mu_Path,0,2) == './' ? FCPATH.substr(Mu_Path,2) : Mu_Path;
+								//路径
+								$time = time();
+								$m3u8path = m3u8_dir($id,$time);
+								$jpgpath = m3u8_dir($id,$time,'jpg');
+								$gifpath = m3u8_dir($id,$time,'gif');
+		        				$this->db->update('video',array('m3u8'=>$m3u8path,'pic'=>$jpgpath),array('id'=>$id));
+								//queue
+								$queue['xu'] = 1;
+								$queue['vid'] = $id;
+						        $queue['path'] = $data['path'];
+						        $queue['jpg'] = $m3u8_dir.$jpgpath;
+						        $queue['gif'] = $m3u8_dir.$gifpath;
+						        $queue['m3u8'] = $m3u8_dir.$m3u8path;
+						        $queue['duration'] = $format['duration'];
+					        	$this->db->insert('queue',$queue);
+					        	//多码率
+					        	if(Mu_Type == 1){
+					        		$ext = end(explode('/', $m3u8path));
+					        		$queue['xu'] = 2;
+									$queue['m3u8'] = $m3u8_dir.str_replace($ext, Mu_Kbps2.'/'.$ext, $m3u8path);
+					        		$this->db->insert('queue',$queue);
+					        	}
+							}
+						}
+					}
+				}
+			}
+		}
+		echo $id;
+	}
+}

+ 33 - 0
apps/application/controllers/Tongbu.php

@@ -0,0 +1,33 @@
+<?php
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+class Tongbu extends CI_Controller{
+	function __construct(){
+	    parent::__construct();
+	    $this->load->database();
+	}
+
+	//同步
+	public function index(){
+		$id = intval($this->input->get_post('id'));
+		if($id < 1) exit('参数错误');
+		$exist = $this->db->query("select * from ".CS_SqlPrefix."video where id=".$id)->row();
+		if(!$exist) exit('视频不存在');
+		//发送api入库请求
+		$add['code'] = 0;
+		$add['vid'] = $exist->id;
+		$add['cid'] = $exist->cid;
+		$add['name'] = $exist->name;
+		$add['size'] = formatsize($exist->size);
+		$add['duration'] = formattime($exist->duration,1);
+		$add['gifurl'] = m3u8_link($exist->pic,'gif'); //$add['m3u8url'] = m3u8_link($exist->m3u8);
+		$add['gifurl'] = m3u8_link($exist->pic,'gif');
+		$add['jpgurl'] = m3u8_link($exist->pic,'jpg');
+		for($i=1;$i<Jpg_Num;$i++){
+			$n = $i+1;
+			$add['jpgurl_'.$n] = m3u8_link($picpath,'jpg',$n);
+		}
+		$add['playurl'] = 'http://'.Web_Url.links('play/'.$id);
+		echo get_api('add',$add);
+	}
+}

+ 267 - 0
apps/application/controllers/Upapi.php

@@ -0,0 +1,267 @@
+<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
+
+class Upapi extends CI_Controller {
+
+	function __construct(){
+	    parent::__construct();
+		$this->load->helper('string');
+		$this->load->database();
+		if(Up_Api == 0) getjson('上传接口关闭');
+	}
+
+	public function index(){
+		$cid = (int)$this->input->get('cid');
+		$t = time();
+		$key = md5($t.$cid.'yun');
+		$data['uploadsave'] = site_url('upapi/save/'.$cid).'?key='.$key.'&t='.$t;
+		$data['vlist'] = $this->db->query("select * from ".CS_SqlPrefix."class order by id asc")->result();
+		$data['cid'] = $cid;
+        $this->load->view('upapi.html',$data);
+	}
+
+
+	public function save($cid=0){
+		header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
+		header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
+		header("Cache-Control: no-store, no-cache, must-revalidate");
+		header("Cache-Control: post-check=0, pre-check=0", false);
+		header("Pragma: no-cache");
+		set_time_limit(0);
+		$cid = (int)$cid;
+		$key = $this->input->get('key');
+		$t = $this->input->get('t');
+		if(empty($key) || empty($t)) getjson('请求参数错误');
+		if(md5($t.$cid.'yun') != $key) getjson('非法请求');
+		if(substr(Video_Path,0,2) == './'){
+			$Video_Path = FCPATH.substr(Video_Path,2);
+		}else{
+			$Video_Path = Video_Path;
+		}
+
+		$targetDir = $Video_Path.'/temp/'.date('Ymd').'/';
+
+		$uploadDir = $Video_Path.'/'.date('Y').'/'.date('m').'/'.date('d').'/';
+		$uploadStr = date('Y').'/'.date('m').'/'.date('d').'/';
+
+		//判断MD5秒传
+		$filemd5 = $this->input->post('md5',true);
+		$filename = $this->input->post('filename',true);
+		if(!empty($filemd5) && !empty($filename)){
+			if(!preg_match("/[0-9A-Za-z]/",$filemd5)){
+				getjson('MD5不合法');
+			}elseif(Video_SrcOn == 0){
+				//判断MD5是否存在
+				$row = $this->db->query("select * from ".CS_SqlPrefix."video where md5='".$filemd5."'")->row();
+				if($row){
+					$file_ext = strtolower(trim(substr(strrchr($row->path, '.'), 1)));
+					$filename = safe_replace(str_replace('.'.$file_ext,'',$filename));
+					//判断标题是否存在
+					$res = $this->db->query("select id from ".CS_SqlPrefix."video where name='".$filename."'")->row();
+					if($res) getjson("yes");
+					//复制文件
+					$newname = date('YmdHis').time().'.'.$file_ext;
+					if(copy($row->path, $uploadDir.$newname)){
+						//video
+						$data['cid'] = $cid;
+						$data['name'] = $filename;
+						$data['path'] = $uploadDir.$newname;
+						$data['md5'] = $filemd5;
+						$data['duration'] = $row->duration;
+						$data['size'] = $row->size;
+						$data['addtime'] = time();
+						$this->db->insert("video",$data);
+						$insert_id = $this->db->insert_id();
+						//M3U8保存目录
+						$m3u8_dir = substr(Mu_Path,0,2) == './' ? FCPATH.substr(Mu_Path,2) : Mu_Path;
+						//路径
+						$time = time();
+						$m3u8path = m3u8_dir($insert_id,$time);
+						$jpgpath = m3u8_dir($insert_id,$time,'jpg');
+						$gifpath = m3u8_dir($insert_id,$time,'gif');
+        				$this->db->update('video',array('m3u8'=>$m3u8path,'pic'=>$jpgpath),array('id'=>$insert_id));
+						//queue
+						$queue['xu'] = 1;
+						$queue['vid'] = $insert_id;
+				        $queue['path'] = $data['path'];
+				        $queue['jpg'] = $m3u8_dir.$jpgpath;
+				        $queue['gif'] = $m3u8_dir.$gifpath;
+				        $queue['m3u8'] = $m3u8_dir.$m3u8path;
+				        $queue['duration'] = $row->duration;
+			        	$this->db->insert('queue',$queue);
+			        	//多码率
+			        	if(Mu_Type == 1){
+			        		$ext = end(explode('/', $m3u8path));
+			        		$queue['xu'] = 2;
+							$queue['m3u8'] = $m3u8_dir.str_replace($ext, Mu_Kbps2.'/'.$ext, $m3u8path);
+			        		$this->db->insert('queue',$queue);
+			        	}
+			        	//返回数据
+        				$ok['name'] = $filename;
+			        	$ok['m3u8'] = m3u8_link($m3u8path);
+			        	$ok['pic'] = m3u8_link($jpgpath,'jpg');
+			        	$ok['purl'] = 'http://'.Web_Url.links('play/'.$insert_id);
+			        	$ok['size'] = formatsize($row->size);
+			        	$ok['sec'] = formattime($row->duration,1);
+        				$ok['msg'] = 'ok';
+			        	getjson($ok,0,1);
+			        }
+				}
+			}
+			getjson('参数错误');
+		}
+
+		//定义允许上传的文件扩展名
+		$ext_arr = array_filter(explode('|', Up_Ext));
+		//防止外部跨站提交
+		if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
+			getjson('提交方式不正确');
+		}
+		//上传出错
+		if (!empty($_REQUEST[ 'debug' ]) ) {
+			$random = rand(0, intval($_REQUEST[ 'debug' ]) );
+			if ( $random === 0 ) {
+				header("HTTP/1.0 500 Internal Server Error"); exit;
+			}
+		}
+		//创建目录
+		if (!file_exists($targetDir)) {
+			creatdir($targetDir);
+		}
+		//创建目录
+		if (!file_exists($uploadDir)) {
+			creatdir($uploadDir);
+		}
+		//原文件名
+		if(!isset($_FILES['video']['name'])){
+			getjson('No FILES');
+		}
+		$file_name = $_FILES['video']['name'];
+		//文件后缀
+		$file_ext = strtolower(trim(substr(strrchr($file_name, '.'), 1)));
+		$newname = iconv("UTF-8","GBK//IGNORE",$file_name);
+		$video_name = safe_replace(str_replace('.'.$file_ext, '', $file_name));
+		//判断是否存在
+		$res = $this->db->query("select id from ".CS_SqlPrefix."video where name='".$video_name."'")->row();
+		if($res){
+			getjson("视频已经存在");
+		}
+		//判断文件后缀
+		if(in_array($file_ext, $ext_arr) == false) {
+			getjson("不支持的格式");
+		}
+		//根据文件名和会员ID生成一个唯一MD5
+		$fileName = md5($file_name).'.'.$file_ext;
+		$oldName = $fileName;
+		$filePath = $targetDir . DIRECTORY_SEPARATOR . $fileName;
+		//分片ID
+		$chunk = isset($_REQUEST["chunk"]) ? intval($_REQUEST["chunk"]) : 0;
+		//分片总数量
+		$chunks = isset($_REQUEST["chunks"]) ? intval($_REQUEST["chunks"]) : 1;
+		//打开临时文件
+		if (!$out = @fopen("{$filePath}_{$chunk}.parttmp", "wb")) {
+			getjson('Failed to open output stream.');
+		}
+		if (!empty($_FILES)) {
+
+			if ($_FILES["video"]["error"] || !is_uploaded_file($_FILES["video"]["tmp_name"])) {
+				getjson('Failed to move uploaded file.');
+			}
+			//读取二进制输入流并将其附加到临时文件
+			if (!$in = @fopen($_FILES["video"]["tmp_name"], "rb")) {
+				getjson('Failed to open input stream.');
+			}
+		} else {
+			if (!$in = @fopen("php://input", "rb")) {
+				getjson('Failed to open input stream.');
+			}
+		}
+		while ($buff = fread($in, 4096)) {
+			fwrite($out, $buff);
+		}
+		@fclose($out);
+		@fclose($in);
+		rename("{$filePath}_{$chunk}.parttmp", "{$filePath}_{$chunk}.part");
+		$index = 0;
+		$done = true;
+		for( $index = 0; $index < $chunks; $index++ ) {
+			if (!file_exists("{$filePath}_{$index}.part") ) {
+				$done = false;
+				break;
+			}
+		}
+		if($done){
+			$pathInfo = pathinfo($fileName);
+			$hashStr = substr(md5($pathInfo['basename']),8,5);
+			$hashName = date('YmdHis').$hashStr.'.'.$pathInfo['extension'];
+			$uploadPath = $uploadDir.$hashName;
+			$uploadStr .= $hashName;
+			if (!$out = @fopen($uploadPath, "wb")) {
+				getjson('Failed to open output stream.');
+			}
+			if ( flock($out, LOCK_EX) ) {
+				for( $index = 0; $index < $chunks; $index++ ) {
+					if (!$in = @fopen("{$filePath}_{$index}.part", "rb")) {
+						break;
+					}
+					while ($buff = fread($in, 4096)) {
+						fwrite($out, $buff);
+					}
+					@fclose($in);
+					@unlink("{$filePath}_{$index}.part");
+				}
+				flock($out, LOCK_UN);
+			}
+			@fclose($out);
+			//video
+			$data['cid'] = $cid;
+			$data['name'] = $video_name;
+			$data['path'] = $uploadPath;
+			$data['md5'] = md5_file($uploadPath);
+			$data['addtime'] = time();
+			$this->load->library('xyz');
+			$format = $this->xyz->format($uploadPath);
+			//if(empty($format['size']) || empty($format['duration'])){
+			//	unlink($uploadPath);
+			//	getjson('ffmpeg error');
+			//}
+			$data['duration'] = $format['duration'];
+			$data['size'] = $format['size'];
+			$this->db->insert("video",$data);
+			$insert_id = $this->db->insert_id();
+			//M3U8保存目录
+			$m3u8_dir = substr(Mu_Path,0,2) == './' ? FCPATH.substr(Mu_Path,2) : Mu_Path;
+			//路径
+			$time = time();
+			$m3u8path = m3u8_dir($insert_id,$time);
+			$jpgpath = m3u8_dir($insert_id,$time,'jpg');
+			$gifpath = m3u8_dir($insert_id,$time,'gif');
+			$this->db->update('video',array('m3u8'=>$m3u8path,'pic'=>$jpgpath),array('id'=>$insert_id));
+			//queue
+			$queue2['xu'] = 1;
+			$queue['vid'] = $insert_id;
+	        $queue['path'] = $uploadPath;
+	        $queue['jpg'] = $m3u8_dir.$jpgpath;
+	        $queue['gif'] = $m3u8_dir.$gifpath;
+	        $queue['m3u8'] = $m3u8_dir.$m3u8path;
+	        $queue['duration'] = $format['duration'];
+        	$this->db->insert('queue',$queue);
+        	//多码率
+        	if(Mu_Type == 1){
+        		$ext = end(explode('/', $m3u8path));
+        		$queue['xu'] = 2;
+				$queue['m3u8'] = $m3u8_dir.str_replace($ext, Mu_Kbps2.'/'.$ext, $m3u8path);
+        		$this->db->insert('queue',$queue);
+        	}
+
+        	//返回数据
+        	$ok['name'] = $video_name;
+        	$ok['m3u8'] = m3u8_link($m3u8path);
+        	$ok['pic'] = m3u8_link($jpgpath,'jpg');
+        	$ok['purl'] = 'http://'.Web_Url.links('play/'.$insert_id);
+        	$ok['size'] = formatsize($format['size']);
+        	$ok['sec'] = formattime($format['duration'],1);
+        	$ok['msg'] = 'ok';
+        	getjson($ok,0,1);
+		}
+	}
+}

+ 254 - 0
apps/application/controllers/Upload.php

@@ -0,0 +1,254 @@
+<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
+
+class Upload extends CI_Controller {
+
+	function __construct(){
+	    parent::__construct();
+		$this->load->helper('string');
+		$this->load->database();
+	    $this->load->model('User');
+	    $this->User->Login();
+	}
+
+	public function index(){
+		$cid = (int)$this->input->get('cid');
+		$data['uploadsave'] = site_url('upload/save/'.$cid);
+		$data['vlist'] = $this->db->query("select * from ".CS_SqlPrefix."class order by id asc")->result();
+		$data['cid'] = $cid;
+        $this->load->view('upload.html',$data);
+	}
+
+
+	public function save($cid=0){
+		$cid = (int)$cid;
+		header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
+		header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
+		header("Cache-Control: no-store, no-cache, must-revalidate");
+		header("Cache-Control: post-check=0, pre-check=0", false);
+		header("Pragma: no-cache");
+		set_time_limit(0);
+
+		if(substr(Video_Path,0,2) == './'){
+			$Video_Path = FCPATH.substr(Video_Path,2);
+		}else{
+			$Video_Path = Video_Path;
+		}
+
+		$targetDir = $Video_Path.'/temp/'.date('Ymd').'/';
+
+		$uploadDir = $Video_Path.'/'.date('Y').'/'.date('m').'/'.date('d').'/';
+		$uploadStr = date('Y').'/'.date('m').'/'.date('d').'/';
+
+		//判断MD5秒传
+		$filemd5 = $this->input->post('md5',true);
+		$filename = $this->input->post('filename',true);
+		if(!empty($filemd5) && !empty($filename)){
+			if(!preg_match("/[0-9A-Za-z]/",$filemd5)){
+				getjson('MD5不合法');
+			}elseif(Video_SrcOn == 1){
+				//判断MD5是否存在
+				$row = $this->db->query("select * from ".CS_SqlPrefix."video where md5='".$filemd5."'")->row();
+				if($row){
+					$file_ext = strtolower(trim(substr(strrchr($filename, '.'), 1)));
+					$filename = safe_replace(str_replace('.'.$file_ext,'',$filename));
+					//判断歌曲标题是否存在
+					$res = $this->db->query("select id from ".CS_SqlPrefix."video where name='".$filename."'")->row();
+					if($res) getjson("yes");
+					//复制文件
+					$newname = date('YmdHis').time().'.'.$file_ext;
+					if(copy($row->path, $uploadDir.$newname)){
+						//video
+						$data['cid'] = $cid;
+						$data['name'] = $filename;
+						$data['path'] = $uploadDir.$newname;
+						$data['md5'] = $filemd5;
+						$data['duration'] = $row->duration;
+						$data['size'] = $row->size;
+						$data['addtime'] = time();
+						$this->db->insert("video",$data);
+						$insert_id = $this->db->insert_id();
+						//M3U8保存目录
+						$m3u8_dir = substr(Mu_Path,0,2) == './' ? FCPATH.substr(Mu_Path,2) : Mu_Path;
+						//路径
+						$time = time();
+						$m3u8path = m3u8_dir($insert_id,$time);
+						$jpgpath = m3u8_dir($insert_id,$time,'jpg');
+						$gifpath = m3u8_dir($insert_id,$time,'gif');
+        				$this->db->update('video',array('m3u8'=>$m3u8path,'pic'=>$jpgpath),array('id'=>$insert_id));
+						//queue
+						$queue['xu'] = 1;
+						$queue['vid'] = $insert_id;
+				        $queue['path'] = $data['path'];
+				        $queue['jpg'] = $m3u8_dir.$jpgpath;
+				        $queue['gif'] = $m3u8_dir.$gifpath;
+				        $queue['m3u8'] = $m3u8_dir.$m3u8path;
+				        $queue['duration'] = $row->duration;
+			        	$this->db->insert('queue',$queue);
+			        	//多码率
+			        	if(Mu_Type == 1){
+			        		$ext = end(explode('/', $m3u8path));
+			        		$queue['xu'] = 2;
+							$queue['m3u8'] = $m3u8_dir.str_replace($ext, Mu_Kbps2.'/'.$ext, $m3u8path);
+			        		$this->db->insert('queue',$queue);
+			        	}
+			        	//返回数据
+        				$ok['name'] = $filename;
+			        	$ok['m3u8'] = m3u8_link($m3u8path);
+			        	$ok['pic'] = m3u8_link($jpgpath,'jpg');
+			        	$ok['purl'] = 'http://'.Web_Url.links('play/'.$insert_id);
+			        	$ok['size'] = formatsize($row->size);
+			        	$ok['sec'] = formattime($row->duration,1);
+        				$ok['msg'] = 'ok';
+			        	getjson($ok,0,1);
+			        }
+				}
+			}
+			getjson('no');
+		}
+
+		//定义允许上传的文件扩展名
+		$ext_arr = array_filter(explode('|', Up_Ext));
+		//防止外部跨站提交
+		if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
+			getjson('上传方式不正确');
+		}
+		//上传出错
+		if (!empty($_REQUEST[ 'debug' ]) ) {
+			$random = rand(0, intval($_REQUEST[ 'debug' ]) );
+			if ( $random === 0 ) {
+				header("HTTP/1.0 500 Internal Server Error"); exit;
+			}
+		}
+		//创建目录
+		if (!file_exists($targetDir)) {
+			creatdir($targetDir);
+		}
+		//创建目录
+		if (!file_exists($uploadDir)) {
+			creatdir($uploadDir);
+		}
+		$file_name = $_FILES['video']['name'];
+		//原文件名
+		if(!isset($_FILES['video']['name'])){
+			$file_name = rand(111111,999999).time().'.mp4';
+		}
+		//文件后缀
+		$file_ext = strtolower(trim(substr(strrchr($file_name, '.'), 1)));
+		$newname = iconv("UTF-8","GBK//IGNORE",$file_name);
+		$video_name = safe_replace(str_replace('.'.$file_ext, '', $file_name));
+		//判断是否存在
+		$res = $this->db->query("select id from ".CS_SqlPrefix."video where name='".$video_name."'")->row();
+		if($res){
+			getjson("视频已经存在");
+		}
+		//判断文件后缀
+		if(in_array($file_ext, $ext_arr) == false) {
+			getjson("不支持的格式");
+		}
+		//根据文件名和会员ID生成一个唯一MD5
+		$fileName = md5($file_name).'.'.$file_ext;
+		$oldName = $fileName;
+		$filePath = $targetDir . DIRECTORY_SEPARATOR . $fileName;
+		//分片ID
+		$chunk = isset($_REQUEST["chunk"]) ? intval($_REQUEST["chunk"]) : 0;
+		//分片总数量
+		$chunks = isset($_REQUEST["chunks"]) ? intval($_REQUEST["chunks"]) : 1;
+		//打开临时文件
+		if (!$out = @fopen("{$filePath}_{$chunk}.parttmp", "wb")) {
+			getjson('Failed to open output stream.');
+		}
+		if (!empty($_FILES)) {
+
+			if ($_FILES["video"]["error"] || !is_uploaded_file($_FILES["video"]["tmp_name"])) {
+				getjson('Failed to move uploaded file.');
+			}
+			//读取二进制输入流并将其附加到临时文件
+			if (!$in = @fopen($_FILES["video"]["tmp_name"], "rb")) {
+				getjson('Failed to open input stream.');
+			}
+		} else {
+			if (!$in = @fopen("php://input", "rb")) {
+				getjson('Failed to open input stream.');
+			}
+		}
+		while ($buff = fread($in, 4096)) {
+			fwrite($out, $buff);
+		}
+		@fclose($out);
+		@fclose($in);
+		rename("{$filePath}_{$chunk}.parttmp", "{$filePath}_{$chunk}.part");
+		$index = 0;
+		$done = true;
+		for( $index = 0; $index < $chunks; $index++ ) {
+			if (!file_exists("{$filePath}_{$index}.part") ) {
+				$done = false;
+				break;
+			}
+		}
+		if($done){
+			$pathInfo = pathinfo($fileName);
+			$hashStr = substr(md5($pathInfo['basename']),8,5);
+			$hashName = date('YmdHis').$hashStr.'.'.$pathInfo['extension'];
+			$uploadPath = $uploadDir.$hashName;
+			$uploadStr .= $hashName;
+			if (!$out = @fopen($uploadPath, "wb")) {
+				getjson('Failed to open output stream.');
+			}
+			if ( flock($out, LOCK_EX) ) {
+				for( $index = 0; $index < $chunks; $index++ ) {
+					if (!$in = @fopen("{$filePath}_{$index}.part", "rb")) {
+						break;
+					}
+					while ($buff = fread($in, 4096)) {
+						fwrite($out, $buff);
+					}
+					@fclose($in);
+					@unlink("{$filePath}_{$index}.part");
+				}
+				flock($out, LOCK_UN);
+			}
+			@fclose($out);
+			//video
+			$data['cid'] = $cid;
+			$data['name'] = $video_name;
+			$data['path'] = $uploadPath;
+			$data['md5'] = md5_file($uploadPath);
+			$data['addtime'] = time();
+			$this->load->library('xyz');
+			$format = $this->xyz->format($uploadPath);
+//			if(empty($format['size']) || empty($format['duration'])){
+//				unlink($uploadPath);
+//				getjson('ffmpeg error');
+//			}
+			$data['duration'] = $format['duration'];
+			$data['size'] = $format['size'];
+			$this->db->insert("video",$data);
+			$insert_id = $this->db->insert_id();
+			//路径
+			$time = time();
+			$m3u8path = m3u8_dir($insert_id,$time);
+			$jpgpath = m3u8_dir($insert_id,$time,'jpg');
+			$gifpath = m3u8_dir($insert_id,$time,'gif');
+			$this->db->update('video',array('m3u8'=>$m3u8path,'pic'=>$jpgpath),array('id'=>$insert_id));
+			//M3U8保存目录
+			$m3u8_dir = substr(Mu_Path,0,2) == './' ? FCPATH.substr(Mu_Path,2) : Mu_Path;
+			//queue
+			$queue2['xu'] = 1;
+			$queue['vid'] = $insert_id;
+	        $queue['path'] = $uploadPath;
+	        $queue['jpg'] = $m3u8_dir.$jpgpath;
+	        $queue['gif'] = $m3u8_dir.$gifpath;
+	        $queue['m3u8'] = $m3u8_dir.$m3u8path;
+	        $queue['duration'] = $format['duration'];
+        	$this->db->insert('queue',$queue);
+        	//多码率
+        	if(Mu_Type == 1){
+        		$ext = end(explode('/', $m3u8path));
+        		$queue['xu'] = 2;
+				$queue['m3u8'] = $m3u8_dir.str_replace($ext, Mu_Kbps2.'/'.$ext, $m3u8path);
+        		$this->db->insert('queue',$queue);
+        	}
+			getjson('ok');
+		}
+	}
+}

+ 501 - 0
apps/application/controllers/Video.php

@@ -0,0 +1,501 @@
+<?php
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+class Video extends CI_Controller {
+	function __construct(){
+	    parent::__construct();
+	    $this->load->database();
+	}
+
+	//视频列表
+	public function index(){
+	    $this->load->model('User');
+	    $this->User->Login();
+		$page = intval($this->input->get_post('page',true));
+		$page = $page<1?1:$page;
+		$per_page = 16;
+		$where = array();
+		$key = $this->input->get_post('key',true);
+		$sid = intval($this->input->get_post('sid'));
+		$cid = intval($this->input->get_post('cid'));
+		$kstime = $this->input->get_post('kstime',true);
+		$jstime = $this->input->get_post('jstime',true);
+		if(!empty($key)) $where['name'] = " like '%".$key."%'";
+		if($sid>0) $where['sid='] = $sid-1;
+		if($cid>0) $where['cid='] = $cid;
+		if(!empty($kstime)) $where['addtime>'] = strtotime($kstime)-1;
+		if(!empty($jstime)) $where['addtime<'] = strtotime($jstime)+86400;
+		$where_str = '';
+		foreach ($where as $key1 => $value) {
+			if(empty($where_str)){
+				$where_str .= 'where '.$key1.$value;
+			}else{
+				$where_str .= ' and '.$key1.$value;
+			}
+		}
+		
+		$sel_sql = "select * from ".CS_SqlPrefix."video ".$where_str." order by id desc";
+		$tol_sql = "select count(*) as count from ".CS_SqlPrefix."video ".$where_str;
+		$total_res = $this->db->query($tol_sql)->row();
+		$total = $total_res->count;
+		$totalPages = ceil($total / $per_page)?ceil($total / $per_page):1; // 总页数
+        $page = ($page>$totalPages)?$totalPages:$page;
+        $data['nums'] = $total;
+        if($total<$per_page){
+            $per_page = $total;
+        }
+        $sel_sql .= ' limit '. $per_page*($page-1) .','. $per_page;
+        $data['video'] = $this->db->query($sel_sql)->result();
+        $data['vlist'] = $this->db->query("select * from ".CS_SqlPrefix."class order by id asc")->result();
+        $base_url = site_url('video')."?key=".$key."&sid=".$sid."&cid=".$cid."&kstime=".$kstime."&jstime=".$jstime."&page=";
+        $data['page_data'] = page_data($total,$page,$totalPages); //获取分页类
+        $data['page_list'] = admin_page($base_url,$page,$totalPages); //获取分页类
+        $data['page'] = $page;
+        $data['sid'] = $sid;
+        $data['cid'] = $cid;
+        $data['key'] = $key;
+        $data['kstime'] = $kstime;
+        $data['jstime'] = $jstime;
+		$this->load->view('video.html',$data);
+	}
+
+	//视频详细
+	public function show($id){
+	    $this->load->model('User');
+	    $this->User->Login();
+		$id = intval($id);
+		if($id<1) exit('参数不正确');
+		$vod = $this->db->query("select * from ".CS_SqlPrefix."video where id=".$id)->row();
+		$data['vod'] = $vod;
+
+		//兼容ppvod平台转移过来的数据
+		if(!empty($vod->path) && substr($vod->path,-5) == '.m3u8'){
+			$parr = explode('/', $vod->path);
+			$len = count($parr)-1;
+			unset($parr[$len]);
+			$path = implode('/',array_filter($parr));
+			$vodpath = $path.'/index.m3u8';
+			$picpath = $path.'/1.jpg';
+			$gifpath = $path.'/1.gif';
+			$data['m3u8url'] = m3u8_link($vodpath);
+			$data['gifurl'] = m3u8_link($gifpath,'gif');
+			$picurl =  m3u8_link($picpath,'jpg');
+		}else{
+			if(empty($vod->pic)){
+				$vodpath = m3u8_dir($vod->id,$vod->addtime,'m3u8',1);
+				$picpath = m3u8_dir($vod->id,$vod->addtime,'jpg',1);
+				$gifpath = m3u8_dir($vod->id,$vod->addtime,'gif',1);
+				$data['m3u8url'] = m3u8_link($vodpath);
+				$data['gifurl'] = m3u8_link($gifpath,'gif');
+				$picurl =  m3u8_link($picpath,'jpg');
+				$data['pic']['jpgurl'] = m3u8_link($picpath,'jpg');
+				for($i=1;$i<Jpg_Num;$i++){
+					$data['pic']['jpgurl_'.$i] = m3u8_link($picpath,'jpg',($i+1));
+				}
+			}else{
+				$data['m3u8url'] = m3u8_link($vod->m3u8);
+				if(Mu_Type == 1){
+					$data['m3u8url2'] = m3u8_link($vod->m3u8,'m3u8',Mu_Kbps2);
+				}
+				$data['gifurl'] = m3u8_link($vod->pic,'gif');
+				$data['pic']['jpgurl'] = m3u8_link($vod->pic,'jpg');
+				for($i=1;$i<Jpg_Num;$i++){
+					$data['pic']['jpgurl_'.$i] = m3u8_link($vod->pic,'jpg',($i+1));
+				}
+			}
+		}
+		$data['playurl'] = 'http://'.Web_Url.links('play/'.$id);
+		$this->load->view('show.html',$data);
+	}
+
+	//删除视频
+	public function del(){
+	    $this->load->model('User');
+	    $this->User->Login();
+		$ids = $this->input->get_post('id');
+		if(is_array($ids)){
+			if(sizeof($ids)<1) getjson('请选择要删除的文件');
+			foreach ($ids as $key => $value) {
+				$id = intval($value);
+				if($id<1) continue;
+				$row = $this->db->query("select * from ".CS_SqlPrefix."video where id=".$id)->row();
+				if(!$row) continue;
+				$this->db->delete('video',array('id'=>$id));
+				//删除队列
+				$this->db->delete('queue',array('vid'=>$id));
+				//删除源文件
+				unlink($row->path);
+				//删除M3U8文件
+				if(Video_M3u8On == 1){
+					$m3u8path = empty($row->m3u8) ? m3u8_dir($row->id,$row->addtime,'m3u8',1) : $row->m3u8;
+					$m3u8path = dirname($m3u8path);
+					if(substr(Mu_Path,0,2) == './'){
+						deldir(FCPATH.substr(Mu_Path,2).$m3u8path);
+					}else{
+						deldir(Mu_Path.$m3u8path);
+					}
+					$picpath = empty($row->pic) ? m3u8_dir($row->id,$row->addtime,'jpg',1) : $row->pic;
+					$picpath = dirname($picpath);
+					if(substr(Mu_Path,0,2) == './'){
+						deldir(FCPATH.substr(Mu_Path,2).$picpath);
+					}else{
+						deldir(Mu_Path.$picpath);
+					}
+				}
+			}
+		}else{
+			$id = intval($ids);
+			$row = $this->db->query("select * from ".CS_SqlPrefix."video where id=".$id)->row();
+			if($row){
+				$this->db->delete('video',array('id'=>$id));
+				//删除队列
+				$this->db->delete('queue',array('vid'=>$id));
+				//删除源文件
+				unlink($row->path);
+				//删除M3U8文件
+				if(Video_M3u8On == 1){
+					$m3u8path = empty($row->m3u8) ? m3u8_dir($row->id,$row->addtime,'m3u8',1) : $row->m3u8;
+					$m3u8path = dirname($m3u8path);
+					if(substr(Mu_Path,0,2) == './'){
+						deldir(FCPATH.substr(Mu_Path,2).$m3u8path);
+					}else{
+						deldir(Mu_Path.$m3u8path);
+					}
+					$picpath = empty($row->pic) ? m3u8_dir($row->id,$row->addtime,'jpg',1) : $row->pic;
+					$picpath = dirname($picpath);
+					if(substr(Mu_Path,0,2) == './'){
+						deldir(FCPATH.substr(Mu_Path,2).$picpath);
+					}else{
+						deldir(Mu_Path.$picpath);
+					}
+				}
+			}
+		}
+		$info['url'] = site_url('video')."?v=".rand(0,999);
+		getjson($info,0);
+	}
+
+	//查看转码状态
+	/*
+	*type : 0转换百分比,1转换成功下一个,2转换失败下一个
+	*id : 当前id
+	*nxid : 下一个ID,0表示没有下一个
+	*pro: 百分比
+	*/
+	public function getpro(){
+		//M3U8保存目录
+		$m3u8_dir = substr(Mu_Path,0,2) == './' ? FCPATH.substr(Mu_Path,2) : Mu_Path;
+		$v = $this->input->get('v');
+		$row = $this->db->query("select * from ".CS_SqlPrefix."queue order by id asc limit 1")->row();
+		if(!$row){
+			//进入待转码目录
+			if($v == 'zm' && Mu_Dir != '' && is_dir(Mu_Dir)){
+				if(substr(Video_Path,0,2) == './'){
+					$Video_Path = FCPATH.substr(Video_Path,2);
+				}else{
+					$Video_Path = Video_Path;
+				}
+				$this->load->helper('file');
+				$dirinfo = dir_file(Mu_Dir);
+				if(!empty($dirinfo['file'])){
+					$filename = $dirinfo['file'];
+					$Mu_Dir = $dirinfo['dir'];
+					if(substr($Mu_Dir,-1) != '') $Mu_Dir.='/';
+					//定义允许上传的文件扩展名
+					$ext_arr = array_filter(explode('|', Up_Ext));
+					//判断文件后缀
+					$file_ext = strtolower(trim(substr(strrchr($filename, '.'), 1)));
+					if(in_array($file_ext, $ext_arr) == false) {
+						unlink($Mu_Dir.$filename);
+						exit('不支持的格式');
+					}
+					$video_name = safe_replace(str_replace('.'.$file_ext, '', iconv('gbk', 'utf-8',$filename)));
+		        	$uploadStr = date('Y').'/'.date('m').'/'.date('d').'/';
+		        	creatdir($Video_Path.$uploadStr); //创建目录
+		        	$newname = date('YmdHis').rand(1111,9999).'.'.$file_ext;
+					//video
+					$data['name'] = $video_name;
+					$data['path'] = $uploadStr.$newname;
+					$data['addtime'] = time();
+		        	//移动目录
+		        	$res = rename($Mu_Dir.$filename,$Video_Path.$uploadStr.$newname);
+		        	if($res){
+		        		$this->load->library('xyz');
+						$format = $this->xyz->format($Video_Path.$uploadStr.$newname);
+						if(empty($format['size']) || empty($format['duration'])){
+							rename($Video_Path.$uploadStr.$newname,$Mu_Dir.$filename);
+							exit('ffmpeg error'); 
+						}
+						$data['duration'] = $format['duration'];
+						$data['size'] = $format['size'];
+						$data['md5'] = md5_file($Video_Path.$uploadStr.$newname);
+						//判断歌曲是否存在
+						$res = $this->db->query("select id from ".CS_SqlPrefix."video where name='".$video_name."'")->row();
+						if(!$res){
+							//判断分类是否存在
+							$carr = explode('/',str_replace(Mu_Dir,'',$Mu_Dir));
+							$cname = empty($carr[0]) ? $carr[1] : $carr[0];
+							$cname = safe_replace(iconv('gbk','utf-8',$cname));
+							if(!empty($cname)){
+								$rec = $this->db->query("select id from ".CS_SqlPrefix."class where name='".$cname."'")->row();
+								if($rec){
+									$data['cid'] = $rec->id;
+								}else{
+									$arr2['name'] = $cname;
+									$arr2['xid'] = 10;
+									$this->db->insert("class",$arr2);
+									$data['cid'] = $this->db->insert_id();
+								}
+							}
+							//视频入库
+							$this->db->insert("video",$data);
+							$insert_id = $this->db->insert_id();
+							//路径
+							$time = time();
+							$mpath = m3u8_dir($insert_id,$time);
+							$ppath = m3u8_dir($insert_id,$time,'jpg');
+							$gpath = m3u8_dir($insert_id,$time,'gif');
+							$this->db->update('video',array('m3u8'=>$mpath,'pic'=>$ppath),array('id'=>$insert_id));
+							//queue
+							$queue['xu'] = 1;
+							$queue['vid'] = $insert_id;
+							$queue['jpg'] = $m3u8_dir.$ppath;
+							$queue['gif'] = $m3u8_dir.$gpath;
+							$queue['m3u8'] = $m3u8_dir.$mpath;
+					        $queue['path'] = $Video_Path.$uploadStr.$newname;
+					        $queue['duration'] = $format['duration'];
+				        	$this->db->insert('queue',$queue);
+				        	//多码率
+				        	if(Mu_Type == 1){
+				        		$ext = end(explode('/', $mpath));
+				        		$queue['xu'] = 2;
+								$queue['m3u8'] = $m3u8_dir.str_replace($ext, Mu_Kbps2.'/'.$ext, $mpath);
+				        		$this->db->insert('queue',$queue);
+				        		$queue['xu'] = 3;
+								$queue['m3u8'] = $m3u8_dir.str_replace($ext, Mu_Kbps3.'/'.$ext, $mpath);
+				        		$this->db->insert('queue',$queue);
+				        	}
+				        }
+		        	}
+				}
+			}
+			getjson('视频全部转码完毕');
+		}
+		//默认返回数据
+		$info['type'] = 0;
+		$info['name'] = $row->name;
+		$info['id'] = $row->id;
+		$info['xu'] = $row->xu;
+		$info['vid'] = $row->vid;
+		$info['pro'] = 0;
+		//判断是视频是否存在
+		$vod = $this->db->query("select id,name,path,size,addtime from ".CS_SqlPrefix."video where id=".$row->vid)->row();
+		//异常队列,视频不存在队列
+		if(!$vod){
+			$this->db->delete('queue',array('id'=>$row->id));//删除队列
+			getjson($info,0);
+		}
+		$row->name = $vod->name;
+		//判断转码是否完毕
+		$m3u8_path = $row->m3u8;
+		if(!file_exists($m3u8_path)){
+			//失败了,进程还在,需要杀掉进程继续下一个转码
+			if($row->addtime > 0){
+				if($row->addtime+120 < time()){
+					//杀掉进程
+					$xt = php_uname();
+					if(strpos($xt, 'Windows') !== false){
+						exec('taskkill /im ffmpeg.exe /f');
+					}else{
+						exec('ps aux | grep "ffmpeg" | cut -c 9-15 | xargs kill -9');
+					}
+					if(Mu_Type == 0 || $row->xu == 2){
+						$this->db->update('video',array('sid'=>2),array('id'=>$row->vid));
+					}
+					$this->db->delete('queue',array('id'=>$row->id));//删除队列
+					$info['type'] = 2;
+					$nxrow = $this->db->query("select id,vid from ".CS_SqlPrefix."queue order by id asc limit 1")->row();
+					if($nxrow){
+						$nxvod = $this->db->query("select name from ".CS_SqlPrefix."video where id=".$nxrow->vid)->row();
+						$info['nxid'] = $nxrow->id;
+						$info['nxvid'] = $nxrow->vid;
+						$info['name'] = $nxvod->name;
+					}else{
+						$info['nxid'] = 0;
+						$info['nxvid'] = 0;
+						$info['name'] = '';
+					}
+					//发送失败API消息
+					if(Mu_Type == 0 || $row->xu == 2) get_api('api',array('vid'=>$vod->id,'name'=>$vod->name,'code'=>2));
+				}
+			}else{
+				$info['type'] = 1;
+				$info['id'] = 0;
+				$info['vid'] = 0;
+				$info['nxid'] = $row->id;
+				$info['nxvid'] = $row->vid;
+			}	
+			getjson($info,0);
+		}
+		$m3u8_neir = file_get_contents($m3u8_path);
+		if(strpos($m3u8_neir,'#EXT-X-ENDLIST')!==false){//转换完毕
+			$this->db->update('video',array('sid'=>1),array('id'=>$row->vid));
+			$this->db->delete('queue',array('id'=>$row->id));//删除队列
+			$nxrow = $this->db->query("select id,vid from ".CS_SqlPrefix."queue order by id asc limit 1")->row();
+			if($nxrow){
+				$nxvod = $this->db->query("select name from ".CS_SqlPrefix."video where id=".$nxrow->vid)->row();
+				$info['nxid'] = $nxrow->id;
+				$info['nxvid'] = $nxrow->vid;
+				$info['name'] = $nxvod->name;
+			}else{
+				$info['nxid'] = 0;
+				$info['nxvid'] = 0;
+				$info['name'] = '';
+			}
+			$info['type'] = 1;
+			//删除源文件
+			if(Video_SrcOn == 1){
+				if(Mu_Type == 0 || $row->xu == 2){
+					unlink($row->path);
+				}
+			}
+			//发送转码完成API消息
+			get_api('api',array('vid'=>$vod->id,'name'=>$vod->name,'code'=>1));
+		}else{//转换中
+			$xt = php_uname();
+			if(strpos($xt, 'Windows') !== false){
+				$jc = shell_exec('tasklist');
+			}else{
+				$jc = shell_exec('ps aux | less');
+			}
+			//这种情况是转码中,进程被结束了
+			if(strpos($jc, 'ffmpeg') === false && $row->addtime > 0){
+				if(strpos($m3u8_neir,'.ts') !== false){ //转码没完成,追加结束符
+					$m3u8_neir .= "\r\n#EXT-X-ENDLIST";
+					write_file($m3u8_path,$m3u8_neir);
+					$type = 1;
+				}else{
+					$type = 2;
+				}
+				//改变状态
+				$this->db->update('video',array('sid'=>$type),array('id'=>$row->vid));
+				$this->db->delete('queue',array('id'=>$row->id));//删除队列
+				$info['type'] = $type;
+				$nxrow = $this->db->query("select id,vid from ".CS_SqlPrefix."queue order by id asc limit 1")->row();
+				if($nxrow){
+					$nxvod = $this->db->query("select name from ".CS_SqlPrefix."video where id=".$nxrow->vid)->row();
+					$info['nxid'] = $nxrow->id;
+					$info['nxvid'] = $nxrow->vid;
+					$info['name'] = $nxvod->name;
+				}else{
+					$info['nxid'] = 0;
+					$info['nxvid'] = 0;
+					$info['name'] = '';
+				}
+				getjson($info,0);
+			}
+			$m3u8arr = explode("\n", $m3u8_neir);
+			$oknum = intval($m3u8arr[count($m3u8arr)-2])+1;
+			$allnum = ceil($row->duration/(Mu_Time+3));
+			if($allnum<$oknum){
+				$pro = 99.99;
+			}else{
+				$pro = round($oknum/$allnum*100,2);
+			}
+			$info['pro'] = $pro;
+		}
+		getjson($info,0);
+	}
+
+	//执行转码
+	public function transcode(){
+		$id = intval($this->input->get_post('id'));
+		if($id < 1) exit('参数错误');
+		$xt = php_uname();
+		if(strpos($xt, 'Windows') !== false){
+			$jc = shell_exec('tasklist');
+		}else{
+			$jc = shell_exec('ps aux | less');
+		}
+		if(strpos($jc, 'ffmpeg') !== false){
+			exit('转码中...');
+		}
+		$exist = $this->db->query("select * from ".CS_SqlPrefix."queue where id=".$id)->row();
+		if(!$exist) exit('no exist!');
+        if(!file_exists($exist->path)){
+			$this->db->update('video',array('sid'=>2),array('id'=>$exist->vid));
+			$this->db->delete('queue',array('id'=>$id));//删除队列
+			exit('视频源文件不存在');
+		}
+		//获取视频信息
+		$vod = $this->db->query("select * from ".CS_SqlPrefix."video where id=".$exist->vid)->row();
+		if(!$vod){
+			$this->db->delete('queue',array('id'=>$id));//删除队列
+			exit('视频不存在');	
+		}
+		//发送api入库请求
+		$add['code'] = 0;
+		$add['vid'] = $vod->id;
+		$add['cid'] = $vod->cid;
+		$add['name'] = $vod->name;
+		$add['size'] = formatsize($vod->size);
+		$add['duration'] = formattime($vod->duration,1);
+		//图片和M3U8地址
+		$add['m3u8url'] = m3u8_link($exist->m3u8);
+		$add['gifurl'] = m3u8_link($exist->gif,'gif');
+		$add['jpgurl'] =  m3u8_link($exist->jpg,'jpg');
+		for($i=1;$i<Jpg_Num;$i++){
+			$n = $i+1;
+			$add['jpgurl_'.$n] = m3u8_link($exist->jpg,'jpg',$n);
+		}
+		$add['playurl'] = 'http://'.Web_Url.links('play/'.$exist->vid);
+		$res = get_api('add',$add);
+		//纪录开始转吗时间 
+		$this->db->update('queue',array('addtime'=>time()),array('id'=>$id));
+		//码率2
+		if($exist->xu == 2){
+			define('KBPS', Mu_Kbps2);
+			define('SIZE', Mu_Size2);
+			$exist->jpg = '';
+			$exist->gif = '';
+		}
+		//开始转码
+		$this->load->library('xyz');
+		$res = $this->xyz->transcode($exist->path,$exist->m3u8,$exist->jpg,$exist->gif);
+		//把原始文件进行改回原名
+		if(Video_SrcOn == 0){
+			//判断多码率
+			if(Mu_Type == 0 || $res == Mu_Kbps3){
+				$file_ext = strtolower(trim(substr(strrchr($exist->path, '.'), 1)));
+				$yname = end(explode('/',$exist->path));
+				$xpath = str_replace($yname, iconv("UTF-8","GBK//IGNORE",$vod->name).'.'.$file_ext, $exist->path);
+				rename($exist->path,$xpath);
+			}
+		}
+		echo $res;
+	}
+
+	//同步
+	public function tongbu(){
+		$id = intval($this->input->get_post('id'));
+		if($id < 1) exit('参数错误');
+		$exist = $this->db->query("select * from ".CS_SqlPrefix."video where id=".$id)->row();
+		if(!$exist) exit('视频不存在');
+		//发送api入库请求
+		$add['code'] = 0;
+		$add['vid'] = $exist->id;
+		$add['cid'] = $exist->cid;
+		$add['name'] = $exist->name;
+		$add['size'] = formatsize($exist->size);
+		$add['duration'] = formattime($exist->duration,1);
+		$add['m3u8url'] = m3u8_link($exist->m3u8);
+		$add['gifurl'] = m3u8_link($exist->pic,'gif');
+		$add['jpgurl'] = m3u8_link($exist->pic,'jpg');
+		for($i=1;$i<Jpg_Num;$i++){
+			$n = $i+1;
+			$add['jpgurl_'.$n] = m3u8_link($picpath,'jpg',$n);
+		}
+		$add['playurl'] = 'http://'.Web_Url.links('play/'.$id);
+		echo get_api('add',$add);
+	}
+}

+ 132 - 0
apps/application/controllers/Vlist.php

@@ -0,0 +1,132 @@
+<?php 
+
+
+
+
+defined('BASEPATH') or exit( 'No direct script access allowed' );
+
+class Vlist extends CI_Controller
+{
+    public function __construct()
+    {
+        parent::__construct();
+        $this->load->database();
+        $this->load->model("User");
+        $this->User->Login();
+    }
+
+    public function index()
+    {
+        $page = intval($this->input->get_post("page", true));
+        $page = ($page < 1 ? 1 : $page);
+        $per_page = 16;
+        $where = array(  );
+        $key = $this->input->get_post("key", true);
+        $where_str = "";
+        if( !empty($key) ) 
+        {
+            $where_str = " where name like '%" . $key . "%'";
+        }
+
+        $sel_sql = "select * from " . CS_SqlPrefix . "class " . $where_str . " order by id desc";
+        $tol_sql = "select count(*) as count from " . CS_SqlPrefix . "class " . $where_str;
+        $total_res = $this->db->query($tol_sql)->row();
+        $total = $total_res->count;
+        $totalPages = (ceil($total / $per_page) ? ceil($total / $per_page) : 1);
+        $page = ($totalPages < $page ? $totalPages : $page);
+        $data["nums"] = $total;
+        if( $total < $per_page ) 
+        {
+            $per_page = $total;
+        }
+
+        $sel_sql .= " limit " . $per_page * ($page - 1) . "," . $per_page;
+        $data["vlist"] = $this->db->query($sel_sql)->result();
+        $base_url = site_url("vlist") . "?key=" . $key . "&page=";
+        $data["page_data"] = page_data($total, $page, $totalPages);
+        $data["page_list"] = admin_page($base_url, $page, $totalPages);
+        $data["page"] = $page;
+        $data["key"] = $key;
+        $this->load->view("vlist.html", $data);
+    }
+
+    public function edit($id = 0)
+    {
+        $id = intval($id);
+        if( 0 < $id ) 
+        {
+            $vod = $this->db->query("select * from " . CS_SqlPrefix . "class where id=" . $id)->row();
+            $data["name"] = $vod->name;
+            $data["xid"] = $vod->xid;
+            $data["id"] = $vod->id;
+        }
+        else
+        {
+            $data["name"] = "";
+            $data["xid"] = 10;
+            $data["id"] = 0;
+        }
+
+        $this->load->view("vlist_edit.html", $data);
+    }
+
+    public function save($id = 0)
+    {
+        $id = intval($id);
+        $data["name"] = $this->input->post("name", true);
+        $data["xid"] = (int) $this->input->post("xid", true);
+        if( empty($data["name"]) ) 
+        {
+            getjson("分类名字不能为空");
+        }
+
+        if( $id == 0 ) 
+        {
+            $this->db->insert("class", $data);
+        }
+        else
+        {
+            $this->db->update("class", $data, array( "id" => $id ));
+        }
+
+        getjson($info, 0);
+    }
+
+    public function del()
+    {
+        $ids = $this->input->get_post("id");
+        if( is_array($ids) ) 
+        {
+            array_unique($ids);
+            $ids = array_merge($ids);
+            if( sizeof($ids) < 1 ) 
+            {
+                getjson("请选择要删除的文件");
+            }
+
+            foreach( $ids as $key => $value ) 
+            {
+                $id = intval($value);
+                if( $id < 1 ) 
+                {
+                    continue;
+                }
+
+                $this->db->delete("class", array( "id" => $id ));
+            }
+        }
+        else
+        {
+            $id = intval($ids);
+            $this->db->delete("class", array( "id" => $id ));
+        }
+
+        $info["url"] = site_url("class") . "?v=" . rand(0, 999);
+        getjson($info, 0);
+        return NULL;
+    }
+
+}
+
+
+

+ 11 - 0
apps/application/controllers/index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>403 Forbidden</title>
+</head>
+<body>
+
+<p>Directory access is forbidden.</p>
+
+</body>
+</html>

+ 39 - 0
apps/application/core/MY_Input.php

@@ -0,0 +1,39 @@
+<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+class MY_Input extends CI_Input{
+	
+	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)
+			? safe_replace($this->security->xss_clean($value))
+			: $value;
+	}
+} // END class MY_URI

+ 11 - 0
apps/application/core/index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>403 Forbidden</title>
+</head>
+<body>
+
+<p>Directory access is forbidden.</p>
+
+</body>
+</html>

+ 394 - 0
apps/application/helpers/common_helper.php

@@ -0,0 +1,394 @@
+<?php
+function links($path){
+	$url = site_url($path);
+	$url = str_replace(SELF,'index.php',$url);
+	if(CS_Web_Wjt == 1){
+		$url = str_replace(Web_Path.'index.php/',Web_Path,$url);
+	}
+	return $url;
+}
+//m3u8地址
+function m3u8_link($path,$ac='m3u8',$xu=1){
+	$Mu_Path = substr(Mu_Path,0,2) == './' ? substr(Mu_Path,2) : Mu_Path;
+	$webpath = CS_Web_Wjt == 1 ? Web_Path : Web_Path.'index.php/';
+	//多图模式
+	if($ac != 'm3u8'){
+		if($xu > 1 && strpos($path, '[xu]') === false){
+			$name = end(explode('/', $path));
+			$path = str_replace($name, $xu.'_'.$name, $path);
+		}else{
+			$path = str_replace('[xu]', $xu, $path);
+		}
+		if($ac == 'gif') $path = str_replace('.jpg', '.gif', $path);
+	}else{
+		if(Mu_Type == 1 && $xu != '1'){
+			$ext = end(explode('/', $path));
+			$path = str_replace($ext, $xu.'/'.$ext, $path);
+		}
+	}
+	if(Web_Play == ''){
+		if(Web_M3u8On == 1){
+			if($ac == 'm3u8'){
+				$link = 'http://'.Web_Url.$webpath.'m3u8/'.sys_auth($path).'.'.$ac;
+			}else{
+				$link = 'http://'.Web_Url.$webpath.'m3u8/pic/'.sys_auth($path).'.'.$ac;
+			}
+		}else{
+			$link = 'http://'.Web_Url.Web_Path.$Mu_Path.$path;
+		}
+	}else{
+		if(Web_M3u8On == 1){
+			if($ac == 'm3u8'){
+				$link = 'http://'.Web_Play.$webpath.'m3u8/'.sys_auth($path).'.'.$ac;
+			}else{
+				$link = 'http://'.Web_Play.$webpath.'m3u8/pic/'.sys_auth($path).'.'.$ac;
+			}
+		}else{
+			$link = 'http://'.Web_Play.'/'.$path;
+		}
+	}
+	return $link;
+}
+
+function m3u8_dir($id,$time,$ac='m3u8',$old=0){
+	if($old == 0){
+		$Mu_Pic_Name = defined('Mu_Pic_Name') ? Mu_Pic_Name : '[年]/[月]/[日]/[md5]/vod.jpg';
+		$Mu_M3u8_Name = defined('Mu_M3u8_Name') ? Mu_M3u8_Name : '[年]/[月]/[日]/[md5]/m3u8.m3u8';
+	}else{
+		$Mu_Pic_Name = '[年]/[月]/[日]/[md5]/vod.jpg';
+		$Mu_M3u8_Name = '[年]/[月]/[日]/[md5]/m3u8.m3u8';
+	}
+	$md5 = substr(md5(Mu_Key.$id.'ctcms'),0,8);
+	$yarr = array('[年]','[月]','[日]','[id]','[md5]');
+	$xarr = array(date('Y',$time),date('m',$time),date('d',$time),$id,$md5);
+	if($ac == 'gif'){
+		$dir_file = str_replace(array('.jpg','.png','jpeg'),'.gif',str_replace($yarr, $xarr, $Mu_Pic_Name));
+	}elseif ($ac == 'jpg') {
+		$dir_file = str_replace($yarr, $xarr, $Mu_Pic_Name);
+	}else{
+		$dir_file = str_replace($yarr, $xarr, $Mu_M3u8_Name);
+	}
+	return $dir_file;
+}
+//写文件
+function write_file($path, $data, $mode = FOPEN_WRITE_CREATE_DESTRUCTIVE){
+	$dir = dirname($path);
+	if(!is_dir($dir)){
+		mkdirss($dir);
+	}
+	if ( ! $fp = @fopen($path, $mode)){
+		return FALSE;
+	}
+	flock($fp, LOCK_EX);
+	fwrite($fp, $data);
+	flock($fp, LOCK_UN);
+	fclose($fp);
+	return TRUE;
+}
+//递归创建文件夹
+function mkdirss($dir) {
+    if (!$dir) {
+        return FALSE;
+    }
+    if (!is_dir($dir)) {
+        mkdirss(dirname($dir));
+        if (!file_exists($dir)) {
+            mkdir($dir, 0777);
+        }
+    }
+    return true;
+}
+//JSON输出
+function getjson($info,$error=1,$sign=0,$callback=''){
+	$msg = $info;
+	$data['error'] = $error;
+	$data['info'] = $info;
+	//兼容前台
+	$data['msg'] = $msg;
+	if($sign==1 && is_array($msg)){
+		$data = array_merge($msg);
+	}
+	$json  = json_encode($data);
+	if(!empty($callback)){
+		echo $callback."(".$json.")";
+	}else{
+		echo $json;
+	}
+	exit;
+}
+
+//视频分页
+function admin_page($url,$page,$pages){
+	$phtml = '<div class="layui-box layui-laypage layui-laypage-default" id="layui-laypage-0">';
+	if($page > 1){
+		$phtml .= '<a href="'.$url.($page-1).'" class="layui-laypage-prev" data-page="'.($page-1).'">上一页</a>';
+	}
+	if($pages<6 || $page<4){
+		if($pages < 2){
+			return '';
+		}
+		if($pages<6){
+			$len = $pages;
+		}else{
+			$len = 5;
+		}
+		for($i=1;$i<$len+1;$i++){
+			$phtml .= page_curr($url,$page,$i);
+		}
+		if($pages>5){
+			$phtml .= '<span>…</span><a href="'.$url.$pages.'" class="layui-laypage-last" title="尾页" data-page="'.$pages.'">末页</a>';
+		}
+	}else{//pages>$nums
+		if($pages<$page+2){
+			$phtml .= '<a href="'.$url.'1" class="laypage_first" data-page="1" title="首页">首页</a><span>…</span>';
+			for($i=$pages-4;$i<$pages+1;$i++){
+				$phtml .= page_curr($url,$page,$i);
+			}
+		}else{
+			$phtml .='<a href="'.$url.'1" class="laypage_first" data-page="1" title="首页">首页</a><span>…</span>';
+			for($i=$page-2;$i<$page+3;$i++){
+				$phtml .= page_curr($url,$page,$i);
+			}
+			$phtml .= '<span>…</span><a href="'.$url.$pages.'" class="layui-laypage-last" title="尾页" data-page="'.$pages.'">末页</a>';
+		}
+	}
+	if($page < $pages){
+		$phtml .= '<a href="'.$url.($page+1).'" class="layui-laypage-next" data-page="'.($page+1).'">下一页</a>';
+	}
+	$phtml .= '<span class="layui-laypage-total phide">到第 <input id="goto_page" type="number" min="1" onkeyup="this.value=this.value.replace(/\D/, \'\')" value="'.$page.'" class="layui-laypage-skip"> 页 <button type="button" onclick="goto_page(\''.$url.'\')" class="layui-laypage-btn">确定</button></span></div>';
+	return $phtml;
+}
+function page_curr($url,$page,$i){
+	$phtml = '';
+	if($page==$i){
+		$phtml .= '<span class="layui-laypage-curr"><em class="layui-laypage-em"></em><em>'.$page.'</em></span>';
+	}else{
+		$phtml .= '<a href="'.$url.$i.'" data-page="'.$i.'">'.$i.'</a>';
+	}
+	return $phtml;
+}
+function page_data($nums,$page,$pages){
+	if($pages<2){
+		return '';
+	}else{
+		return '共'.$nums.'条记录'.$pages.'页,当前显示第'.$page.'页';
+	}
+}
+
+function formatsize($size, $dec=2){
+	$a = array("B", "KB", "MB", "GB", "TB", "PB");
+	$pos = 0;
+	while ($size >= 1024) {
+		$size /= 1024;
+		$pos++;
+	}
+	return round($size,$dec)." ".$a[$pos];
+}
+
+function formattime($time,$sign=0){
+	$h = $m = $s = 0;
+	$str = '';
+	$s = floor($time%60);
+	$m = floor($time/60)%60;
+	$h = floor($time/60/60);
+	if($sign==0){
+		if($h>0)  return $str = $h."时".$m."分".$s.'秒';
+		if($m>0)  return $str = $m."分".$s.'秒';
+		return $str = $s.'秒';
+	}
+	if($sign==1){
+		if($m<10) $m = '0'.$m;
+		if($s<10) $s = '0'.$s;
+		if($h>0)  return $str = $h.":".$m.":".$s;
+		if($m>0)  return $str = $m.":".$s;
+		return $str = '00:'.$s;
+	}
+}
+
+function is_referer($m3u8=0){
+
+    if(Web_CrossDomain == '' ||
+    	Web_CrossOn == 0 ||
+    	preg_match("/(PlaySDK|ExoPlayerLib)/i", strtoupper($_SERVER['HTTP_USER_AGENT']))
+    ){
+    	return true;
+	}
+	if(empty($_SERVER['HTTP_REFERER'])){
+		if(Web_Cross == 0 || (Web_M3u8On == 1 && $m3u8 == 1)){
+			return false;
+		}else{
+			return true;
+		}
+	}
+
+	$uriarr = parse_url($_SERVER['HTTP_REFERER']);
+	$host = $uriarr['host'];
+	$ymarr = explode("|",Web_CrossDomain);
+    if($host == Web_Url || $host == Web_Play || in_array($host,$ymarr)){
+    	return true;
+    }
+    return false;
+}
+//SQL过滤
+function safe_replace($string){
+	if(is_array($string)) {
+		foreach($string as $k => $v) {
+			$string[$k] = safe_replace($v);
+		}
+	}else{
+		if(!is_numeric($string)){
+			$string = str_replace('&','&amp;',$string);
+			$string = str_replace('%20','',$string);
+			$string = str_replace('%27','',$string);
+			$string = str_replace('%2527','',$string);
+			$string = str_replace("'",'&#039;',$string);
+			$string = str_replace('"','&quot;',$string);
+			$string = str_replace(';','',$string);
+			$string = str_replace('*','',$string);
+			$string = str_replace('<','&lt;',$string);
+			$string = str_replace('>','&gt;',$string);
+			$string = str_replace("\\",'/',$string);
+			$string = str_replace('%','\%',$string);
+		    $string = str_replace('{','%7b',$string);
+		    $string = str_replace('}','%7d',$string);
+		}
+	}
+	return $string;
+}
+//删除目录和文件
+function deldir($dir,$sid=1) {
+	if(!is_dir($dir)){
+		return true;
+	}
+	//先删除目录下的文件
+	$dh=opendir($dir);
+	while ($file=readdir($dh)) {
+		if($file!="." && $file!="..") {
+			$fullpath=$dir."/".$file;
+			if(!is_dir($fullpath)) {
+				@unlink($fullpath);
+			} else {
+				deldir($fullpath,$sid);
+			}
+		}
+	}
+	closedir($dh);
+	//删除当前文件夹:
+	if($sid==1){
+		if(@rmdir($dir)) {
+			return true;
+		} else {
+			return false;
+		}
+	}else{
+		return true;
+	}
+}
+//创建目录
+function creatdir($path){
+	if(!is_dir($path)){
+		if(creatdir(dirname($path))){
+			mkdir($path,0777);
+			return true;
+		}
+	}else{
+		return true;
+	}
+}
+//CURL
+function get_curl($url,$post=''){
+	$ch = curl_init($url);
+	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+	curl_setopt($ch, CURLOPT_HEADER, false);
+	if(!empty($post)){
+		curl_setopt($ch, CURLOPT_USERAGENT, 'ctcms_yzm_api');
+		curl_setopt($ch, CURLOPT_POST, 1);
+		curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post));
+	}else{
+		curl_setopt($ch, CURLOPT_USERAGENT, "ctcms_yunzhuanma");
+	}
+	curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 8);
+	curl_setopt($ch, CURLOPT_TIMEOUT, 10);
+	$content = curl_exec($ch);
+	curl_close($ch);
+	return $content;
+}
+
+function get_api($ac,$data=array()){
+	if(Api_On==0 || Api_Url=='' || Api_Key==''){
+		return 'Api Unopened';
+	}
+	$data['ac'] = $ac;
+	$data['key'] = Api_Key;
+	$res = get_curl(Api_Url,$data);
+
+	$logfile = FCPATH.'logs/';
+	mkdirss($logfile);
+	$txt = date('Y-m-d H:i:s')."-----".json_encode($data)."-----".$ac."-----".$res."\r\n";
+	file_put_contents($logfile.date('Y-m-d').'.txt',$txt,FILE_APPEND);
+	return $res;
+}
+
+function sys_auth($string, $type = 0, $expiry = 0, $key = 'yunzhuanma') {
+	if($type == 1) $string = str_replace(array('-','_'),array('+','/'),$string);
+	$ckey_length = 4;
+	$key = md5($key);
+	$keya = md5(substr($key, 0, 16));
+	$keyb = md5(substr($key, 16, 16));
+	$keyc = $ckey_length ? ($type == 1 ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
+	$cryptkey = $keya.md5($keya.$keyc);
+	$key_length = strlen($cryptkey);
+	$string = $type == 1 ? base64_decode(substr($string, $ckey_length)) :  sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
+	$string_length = strlen($string);
+	$result = '';
+	$box = range(0, 255);
+	$rndkey = array();
+	for($i = 0; $i <= 255; $i++) {
+		$rndkey[$i] = ord($cryptkey[$i % $key_length]);
+	}
+	for($j = $i = 0; $i < 256; $i++) {
+		$j = ($j + $box[$i] + $rndkey[$i]) % 256;
+		$tmp = $box[$i];
+		$box[$i] = $box[$j];
+		$box[$j] = $tmp;
+	}
+	for($a = $j = $i = 0; $i < $string_length; $i++) {
+		$a = ($a + 1) % 256;
+		$j = ($j + $box[$a]) % 256;
+		$tmp = $box[$a];
+		$box[$a] = $box[$j];
+		$box[$j] = $tmp;
+		$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
+	}
+	if($type == 1) {
+		if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
+			return substr($result, 26);
+		} else {
+			return '';
+		}
+	} else {
+		return str_replace(array('+','/'), array('-','_'), $keyc.str_replace('=', '', base64_encode($result)));
+	}
+}
+//获取待转目录第一个文件
+function dir_file($dir){
+	if(!is_dir($dir)) return '';
+	$darr = array();
+	$dh = opendir($dir);
+	while ($file=readdir($dh)) {
+		if(empty($darr) && $file!="." && $file!="..") {
+			if(!is_dir($dir.'/'.$file)){
+				$darr['dir'] = $dir;
+				$darr['file'] = $file;
+				break;
+			} else {
+				$darr = dir_file($dir.'/'.$file);
+				if(!empty($darr)) break;
+			}
+		}
+	}
+	closedir($dh);
+	if(!empty($darr)){
+		return $darr;
+	}
+}

+ 11 - 0
apps/application/helpers/index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>403 Forbidden</title>
+</head>
+<body>
+
+<p>Directory access is forbidden.</p>
+
+</body>
+</html>

+ 11 - 0
apps/application/hooks/index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>403 Forbidden</title>
+</head>
+<body>
+
+<p>Directory access is forbidden.</p>
+
+</body>
+</html>

+ 11 - 0
apps/application/index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>403 Forbidden</title>
+</head>
+<body>
+
+<p>Directory access is forbidden.</p>
+
+</body>
+</html>

+ 11 - 0
apps/application/language/english/index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>403 Forbidden</title>
+</head>
+<body>
+
+<p>Directory access is forbidden.</p>
+
+</body>
+</html>

+ 11 - 0
apps/application/language/index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>403 Forbidden</title>
+</head>
+<body>
+
+<p>Directory access is forbidden.</p>
+
+</body>
+</html>

+ 37 - 0
apps/application/libraries/Abc.php

@@ -0,0 +1,37 @@
+<?php
+
+if( !defined('BASEPATH') ) 
+{
+    exit( 'No direct script access allowed' );
+}
+
+
+class Cookie
+{
+    public function __construct()
+    {
+        log_message('debug', 'Native Session Class Initialized');
+    }
+
+    public static function set_cookie($var, $value = "", $time = 0)
+    {
+        $time = (0 < $time ? $time : ($value == "" ? time() - 3600 : 0));
+        $s = ($_SERVER["SERVER_PORT"] == "443" ? 1 : 0);
+        $var = CT_Cookie_Prefix . $var;
+        $ips = explode(":", $_SERVER["HTTP_HOST"]);
+        $Domain = CT_Cookie_Domain;
+        setcookie($var, sys_auth($value, "E", $var . CT_Encryption_Key), $time, Web_Path, $Domain, $s);
+    }
+
+    public static function get_cookie($var, $default = "")
+    {
+        $var = CT_Cookie_Prefix . $var;
+        $value = (isset($_COOKIE[$var]) ? sys_auth($_COOKIE[$var], "D", $var . CT_Encryption_Key) : $default);
+        $value = safe_replace($value);
+        return $value;
+    }
+
+}
+
+
+

+ 159 - 0
apps/application/libraries/Def.php

@@ -0,0 +1,159 @@
+<?php 
+
+
+
+
+if( !defined('BASEPATH') ) 
+{
+    exit( 'No direct script access allowed' );
+}
+
+
+class Denglu
+{
+    public function __construct()
+    {
+        $this->redirect_uri = links("user", "open/callback");
+        $this->ci =& get_instance();
+    }
+
+    public function callback($ac, $log_state = "")
+    {
+        $mode = $ac . "_callback";
+        return $this->$mode($log_state);
+    }
+
+    public function qq_login($log_state = "")
+    {
+        if( Qq_Log == 0 ) 
+        {
+            exit( "QQ登录为关闭状态~" );
+        }
+
+        $scope = "get_user_info,add_share,list_album,add_album,upload_pic,add_topic,add_one_blog,add_weibo";
+        $login_url = "https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=" . Qq_Appid . "&redirect_uri=" . $this->redirect_uri . "&state=" . $log_state . "&scope=" . $scope;
+        header("Location:" . $login_url);
+    }
+
+    public function weixin_login($log_state = "")
+    {
+        if( Wx_Log == 0 ) 
+        {
+            exit( "微信登录为关闭状态~" );
+        }
+
+        $login_url = "https://open.weixin.qq.com/connect/qrconnect?appid=" . Wx_Appid . "&redirect_uri=" . urlencode($this->redirect_uri) . "&response_type=code&scope=snsapi_login&state=" . $log_state . "#wechat_redirect";
+        header("Location:" . $login_url);
+    }
+
+    public function qq_callback($log_state = "")
+    {
+        $state = $this->ci->input->get_post("state", true, true);
+        $code = $this->ci->input->get("code", true);
+        if( empty($state) || empty($code) ) 
+        {
+            msg_url("登录失败,返回参数错误~!", links("user", "login"));
+        }
+
+        if( $state != $log_state ) 
+        {
+            msg_url("非法登录~!", links("user", "login"));
+        }
+
+        $token_url = "https://graph.qq.com/oauth2.0/token?grant_type=authorization_code&" . "client_id=" . Qq_Appid . "&redirect_uri=" . urlencode($this->redirect_uri) . "&client_secret=" . Qq_Appkey . "&code=" . $code;
+        $response = $this->get_url_contents($token_url);
+        if( strpos($response, "callback") !== false ) 
+        {
+            msg_url("登入失败,没获取到access_token!", links("user", "login"));
+        }
+
+        $params = array(  );
+        parse_str($response, $params);
+        $data["access_token"] = $params["access_token"];
+        $data["refresh_token"] = $params["refresh_token"];
+        $data["expire_in"] = $params["expires_in"];
+        $graph_url = "https://graph.qq.com/oauth2.0/me?access_token=" . $data["access_token"];
+        $str = $this->get_url_contents($graph_url);
+        if( strpos($str, "callback") !== false ) 
+        {
+            $lpos = strpos($str, "(");
+            $rpos = strrpos($str, ")");
+            $str = substr($str, $lpos + 1, $rpos - $lpos - 1);
+        }
+
+        $user = json_decode($str);
+        if( isset($user->error) ) 
+        {
+            msg_url("获取openid失败!", links("user", "login"));
+        }
+
+        $qqid = $user->openid;
+        $get_user_info = "https://graph.qq.com/user/get_user_info?" . "access_token=" . $data["access_token"] . "&oauth_consumer_key=" . CS_Qqid . "&openid=" . $qqid . "&format=json";
+        $info = $this->get_url_contents($get_user_info);
+        $arr = json_decode($info, true);
+        $data["nichen"] = $arr["nickname"];
+        $data["pic"] = $arr["figureurl_2"];
+        $data["uid"] = $qqid;
+        return $data;
+    }
+
+    public function weixin_callback($log_state = "")
+    {
+        $state = $this->ci->input->get_post("state", true, true);
+        $code = $this->ci->input->get("code", true);
+        if( empty($state) || empty($code) ) 
+        {
+            msg_url("登录失败,返回参数错误~!", links("user", "login"));
+        }
+
+        if( $state != $log_state ) 
+        {
+            msg_url("非法登录~!", links("user", "login"));
+        }
+
+        $url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" . Wx_Appid . "&secret=" . Wx_Appkey . "&code=" . $code . "&grant_type=authorization_code";
+        $json = $this->get_url_contents($url);
+        $arr = json_decode($json, true);
+        $token = $arr["access_token"];
+        $openid = $arr["openid"];
+        if( empty($openid) ) 
+        {
+            exit( "获取用户信息失败!" );
+        }
+
+        $url = "https://api.weixin.qq.com/sns/userinfo?access_token=" . $token . "&openid=" . $openid;
+        $json = $this->get_url_contents($url);
+        $arr = json_decode($json, true);
+        $data["nichen"] = $arr["nickname"];
+        $data["pic"] = $arr["headimgurl"];
+        $data["uid"] = $openid;
+        return $data;
+    }
+
+    public function get_url_contents($url, $post = "", $type = "get")
+    {
+        $ch = curl_init();
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($ch, CURLOPT_URL, $url);
+        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
+        if( $type == "post" ) 
+        {
+            curl_setopt($ch, CURLOPT_POST, 1);
+            curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
+        }
+        else
+        {
+            curl_setopt($ch, CURLOPT_POST, 0);
+        }
+
+        curl_setopt($ch, CURLOPT_USERAGENT, "cscms");
+        $result = curl_exec($ch);
+        curl_close($ch);
+        return $result;
+    }
+
+}
+
+
+

+ 1340 - 0
apps/application/libraries/Ffm.php

@@ -0,0 +1,1340 @@
+<?php
+if (!defined('BASEPATH')) exit('No direct script access allowed');
+if (!defined('PCLZIP_READ_BLOCK_SIZE')) {
+    define('PCLZIP_READ_BLOCK_SIZE', 2048);
+}
+if (!defined('PCLZIP_SEPARATOR')) {
+    define('PCLZIP_SEPARATOR', ',');
+}
+if (!defined('PCLZIP_ERROR_EXTERNAL')) {
+    define('PCLZIP_ERROR_EXTERNAL', 0);
+}
+if (!defined('PCLZIP_TEMPORARY_DIR')) {
+    define('PCLZIP_TEMPORARY_DIR', '');
+}
+if (!defined('PCLZIP_TEMPORARY_FILE_RATIO')) {
+    define('PCLZIP_TEMPORARY_FILE_RATIO', 0.47);
+}
+$g_pclzip_version = "2.8.2";
+define('PCLZIP_ERR_USER_ABORTED', 2);
+define('PCLZIP_ERR_NO_ERROR', 0);
+define('PCLZIP_ERR_WRITE_OPEN_FAIL', -1);
+define('PCLZIP_ERR_READ_OPEN_FAIL', -2);
+define('PCLZIP_ERR_INVALID_PARAMETER', -3);
+define('PCLZIP_ERR_MISSING_FILE', -4);
+define('PCLZIP_ERR_FILENAME_TOO_LONG', -5);
+define('PCLZIP_ERR_INVALID_ZIP', -6);
+define('PCLZIP_ERR_BAD_EXTRACTED_FILE', -7);
+define('PCLZIP_ERR_DIR_CREATE_FAIL', -8);
+define('PCLZIP_ERR_BAD_EXTENSION', -9);
+define('PCLZIP_ERR_BAD_FORMAT', -10);
+define('PCLZIP_ERR_DELETE_FILE_FAIL', -11);
+define('PCLZIP_ERR_RENAME_FILE_FAIL', -12);
+define('PCLZIP_ERR_BAD_CHECKSUM', -13);
+define('PCLZIP_ERR_INVALID_ARCHIVE_ZIP', -14);
+define('PCLZIP_ERR_MISSING_OPTION_VALUE', -15);
+define('PCLZIP_ERR_INVALID_OPTION_VALUE', -16);
+define('PCLZIP_ERR_ALREADY_A_DIRECTORY', -17);
+define('PCLZIP_ERR_UNSUPPORTED_COMPRESSION', -18);
+define('PCLZIP_ERR_UNSUPPORTED_ENCRYPTION', -19);
+define('PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE', -20);
+define('PCLZIP_ERR_DIRECTORY_RESTRICTION', -21);
+define('PCLZIP_OPT_PATH', 77001);
+define('PCLZIP_OPT_ADD_PATH', 77002);
+define('PCLZIP_OPT_REMOVE_PATH', 77003);
+define('PCLZIP_OPT_REMOVE_ALL_PATH', 77004);
+define('PCLZIP_OPT_SET_CHMOD', 77005);
+define('PCLZIP_OPT_EXTRACT_AS_STRING', 77006);
+define('PCLZIP_OPT_NO_COMPRESSION', 77007);
+define('PCLZIP_OPT_BY_NAME', 77008);
+define('PCLZIP_OPT_BY_INDEX', 77009);
+define('PCLZIP_OPT_BY_EREG', 77010);
+define('PCLZIP_OPT_BY_PREG', 77011);
+define('PCLZIP_OPT_COMMENT', 77012);
+define('PCLZIP_OPT_ADD_COMMENT', 77013);
+define('PCLZIP_OPT_PREPEND_COMMENT', 77014);
+define('PCLZIP_OPT_EXTRACT_IN_OUTPUT', 77015);
+define('PCLZIP_OPT_REPLACE_NEWER', 77016);
+define('PCLZIP_OPT_STOP_ON_ERROR', 77017);
+define('PCLZIP_OPT_EXTRACT_DIR_RESTRICTION', 77019);
+define('PCLZIP_OPT_TEMP_FILE_THRESHOLD', 77020);
+define('PCLZIP_OPT_ADD_TEMP_FILE_THRESHOLD', 77020);
+define('PCLZIP_OPT_TEMP_FILE_ON', 77021);
+define('PCLZIP_OPT_ADD_TEMP_FILE_ON', 77021);
+define('PCLZIP_OPT_TEMP_FILE_OFF', 77022);
+define('PCLZIP_OPT_ADD_TEMP_FILE_OFF', 77022);
+define('PCLZIP_ATT_FILE_NAME', 79001);
+define('PCLZIP_ATT_FILE_NEW_SHORT_NAME', 79002);
+define('PCLZIP_ATT_FILE_NEW_FULL_NAME', 79003);
+define('PCLZIP_ATT_FILE_MTIME', 79004);
+define('PCLZIP_ATT_FILE_CONTENT', 79005);
+define('PCLZIP_ATT_FILE_COMMENT', 79006);
+define('PCLZIP_CB_PRE_EXTRACT', 78001);
+define('PCLZIP_CB_POST_EXTRACT', 78002);
+define('PCLZIP_CB_PRE_ADD', 78003);
+define('PCLZIP_CB_POST_ADD', 78004);
+
+class Ctzip
+{
+    var $zipname      = '';
+    var $zip_fd       = 0;
+    var $error_code   = 1;
+    var $error_string = '';
+    var $magic_quotes_status;
+
+    function PclZip($p_zipname)
+    {
+        if (!function_exists('gzopen')) {
+            die('Abort ' . basename(preg_replace('@\(.*\(.*$@', '', __FILE__)) . ' : Missing zlib extensions');
+        }
+        $this->zipname             = $p_zipname;
+        $this->zip_fd              = 0;
+        $this->magic_quotes_status = -1;
+        return;
+    }
+
+    function delete()
+    {
+        $v_result = 1;
+        $this->privErrorReset();
+        if (!$this->privCheckFormat()) {
+            return (0);
+        }
+        $v_options = array();
+        $v_size    = func_num_args();
+        if ($v_size > 0) {
+            $v_arg_list = func_get_args();
+            $v_result   = $this->privParseOptions($v_arg_list, $v_size, $v_options, array(PCLZIP_OPT_BY_NAME => 'optional', PCLZIP_OPT_BY_EREG => 'optional', PCLZIP_OPT_BY_PREG => 'optional', PCLZIP_OPT_BY_INDEX => 'optional'));
+            if ($v_result != 1) {
+                return 0;
+            }
+        }
+        $this->privDisableMagicQuotes();
+        $v_list = array();
+        if (($v_result = $this->privDeleteByRule($v_list, $v_options)) != 1) {
+            $this->privSwapBackMagicQuotes();
+            unset($v_list);
+            return (0);
+        }
+        $this->privSwapBackMagicQuotes();
+        return $v_list;
+    }
+
+    function deleteByIndex($p_index)
+    {
+        $p_list = $this->delete(PCLZIP_OPT_BY_INDEX, $p_index);
+        return $p_list;
+    }
+
+    function properties()
+    {
+        $this->privErrorReset();
+        $this->privDisableMagicQuotes();
+        if (!$this->privCheckFormat()) {
+            $this->privSwapBackMagicQuotes();
+            return (0);
+        }
+        $v_prop            = array();
+        $v_prop['comment'] = '';
+        $v_prop['nb']      = 0;
+        $v_prop['status']  = 'not_exist';
+        if (@is_file($this->zipname)) {
+            if (($this->zip_fd = @fopen($this->zipname, 'rb')) == 0) {
+                $this->privSwapBackMagicQuotes();
+                PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open archive \'' . $this->zipname . '\' in binary read mode');
+                return 0;
+            }
+            $v_central_dir = array();
+            if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1) {
+                $this->privSwapBackMagicQuotes();
+                return 0;
+            }
+            $this->privCloseFd();
+            $v_prop['comment'] = $v_central_dir['comment'];
+            $v_prop['nb']      = $v_central_dir['entries'];
+            $v_prop['status']  = 'ok';
+        }
+        $this->privSwapBackMagicQuotes();
+        return $v_prop;
+    }
+
+    function duplicate($p_archive)
+    {
+        $v_result = 1;
+        $this->privErrorReset();
+        if ((is_object($p_archive)) && (get_class($p_archive) == 'pclzip')) {
+            $v_result = $this->privDuplicate($p_archive->zipname);
+        } else if (is_string($p_archive)) {
+            if (!is_file($p_archive)) {
+                PclZip::privErrorLog(PCLZIP_ERR_MISSING_FILE, "No file with filename '" . $p_archive . "'");
+                $v_result = PCLZIP_ERR_MISSING_FILE;
+            } else {
+                $v_result = $this->privDuplicate($p_archive);
+            }
+        } else {
+            PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, 'Invalid variable type p_archive_to_add');
+            $v_result = PCLZIP_ERR_INVALID_PARAMETER;
+        }
+        return $v_result;
+    }
+
+    function merge($p_archive_to_add)
+    {
+        $v_result = 1;
+        $this->privErrorReset();
+        if (!$this->privCheckFormat()) {
+            return (0);
+        }
+        if ((is_object($p_archive_to_add)) && (get_class($p_archive_to_add) == 'pclzip')) {
+            $v_result = $this->privMerge($p_archive_to_add);
+        } else if (is_string($p_archive_to_add)) {
+            $v_object_archive = new PclZip($p_archive_to_add);
+            $v_result         = $this->privMerge($v_object_archive);
+        } else {
+            PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, 'Invalid variable type p_archive_to_add');
+            $v_result = PCLZIP_ERR_INVALID_PARAMETER;
+        }
+        return $v_result;
+    }
+
+    function errorCode()
+    {
+        if (PCLZIP_ERROR_EXTERNAL == 1) {
+            return (PclErrorCode());
+        } else {
+            return ($this->error_code);
+        }
+    }
+
+    function errorName($p_with_code = false)
+    {
+        $v_name = array(PCLZIP_ERR_NO_ERROR => 'PCLZIP_ERR_NO_ERROR', PCLZIP_ERR_WRITE_OPEN_FAIL => 'PCLZIP_ERR_WRITE_OPEN_FAIL', PCLZIP_ERR_READ_OPEN_FAIL => 'PCLZIP_ERR_READ_OPEN_FAIL', PCLZIP_ERR_INVALID_PARAMETER => 'PCLZIP_ERR_INVALID_PARAMETER', PCLZIP_ERR_MISSING_FILE => 'PCLZIP_ERR_MISSING_FILE', PCLZIP_ERR_FILENAME_TOO_LONG => 'PCLZIP_ERR_FILENAME_TOO_LONG', PCLZIP_ERR_INVALID_ZIP => 'PCLZIP_ERR_INVALID_ZIP', PCLZIP_ERR_BAD_EXTRACTED_FILE => 'PCLZIP_ERR_BAD_EXTRACTED_FILE', PCLZIP_ERR_DIR_CREATE_FAIL => 'PCLZIP_ERR_DIR_CREATE_FAIL', PCLZIP_ERR_BAD_EXTENSION => 'PCLZIP_ERR_BAD_EXTENSION', PCLZIP_ERR_BAD_FORMAT => 'PCLZIP_ERR_BAD_FORMAT', PCLZIP_ERR_DELETE_FILE_FAIL => 'PCLZIP_ERR_DELETE_FILE_FAIL', PCLZIP_ERR_RENAME_FILE_FAIL => 'PCLZIP_ERR_RENAME_FILE_FAIL', PCLZIP_ERR_BAD_CHECKSUM => 'PCLZIP_ERR_BAD_CHECKSUM', PCLZIP_ERR_INVALID_ARCHIVE_ZIP => 'PCLZIP_ERR_INVALID_ARCHIVE_ZIP', PCLZIP_ERR_MISSING_OPTION_VALUE => 'PCLZIP_ERR_MISSING_OPTION_VALUE', PCLZIP_ERR_INVALID_OPTION_VALUE => 'PCLZIP_ERR_INVALID_OPTION_VALUE', PCLZIP_ERR_UNSUPPORTED_COMPRESSION => 'PCLZIP_ERR_UNSUPPORTED_COMPRESSION', PCLZIP_ERR_UNSUPPORTED_ENCRYPTION => 'PCLZIP_ERR_UNSUPPORTED_ENCRYPTION', PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE => 'PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE', PCLZIP_ERR_DIRECTORY_RESTRICTION => 'PCLZIP_ERR_DIRECTORY_RESTRICTION');
+        if (isset($v_name[$this->error_code])) {
+            $v_value = $v_name[$this->error_code];
+        } else {
+            $v_value = 'NoName';
+        }
+        if ($p_with_code) {
+            return ($v_value . ' (' . $this->error_code . ')');
+        } else {
+            return ($v_value);
+        }
+    }
+
+    function errorInfo($p_full = false)
+    {
+        if (PCLZIP_ERROR_EXTERNAL == 1) {
+            return (PclErrorString());
+        } else {
+            if ($p_full) {
+                return ($this->errorName(true) . " : " . $this->error_string);
+            } else {
+                return ($this->error_string . " [code " . $this->error_code . "]");
+            }
+        }
+    }
+
+    function privCheckFormat($p_level = 0)
+    {
+        $v_result = true;
+        clearstatcache();
+        $this->privErrorReset();
+        if (!is_file($this->zipname)) {
+            PclZip::privErrorLog(PCLZIP_ERR_MISSING_FILE, "Missing archive file '" . $this->zipname . "'");
+            return (false);
+        }
+        if (!is_readable($this->zipname)) {
+            PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, "Unable to read archive '" . $this->zipname . "'");
+            return (false);
+        }
+        return $v_result;
+    }
+
+    function privParseOptions(&$p_options_list, $p_size, &$v_result_list, $v_requested_options = false)
+    {
+        $v_result = 1;
+        $i        = 0;
+        while ($i < $p_size) {
+            if (!isset($v_requested_options[$p_options_list[$i]])) {
+                PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid optional parameter '" . $p_options_list[$i] . "' for this method");
+                return PclZip::errorCode();
+            }
+            switch ($p_options_list[$i]) {
+                case PCLZIP_OPT_PATH :
+                case PCLZIP_OPT_REMOVE_PATH :
+                case PCLZIP_OPT_ADD_PATH :
+                    if (($i + 1) >= $p_size) {
+                        PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'");
+                        return PclZip::errorCode();
+                    }
+                    $v_result_list[$p_options_list[$i]] = PclZipUtilTranslateWinPath($p_options_list[$i + 1], FALSE);
+                    $i++;
+                    break;
+                case PCLZIP_OPT_TEMP_FILE_THRESHOLD :
+                    if (($i + 1) >= $p_size) {
+                        PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'");
+                        return PclZip::errorCode();
+                    }
+                    if (isset($v_result_list[PCLZIP_OPT_TEMP_FILE_OFF])) {
+                        PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Option '" . PclZipUtilOptionText($p_options_list[$i]) . "' can not be used with option 'PCLZIP_OPT_TEMP_FILE_OFF'");
+                        return PclZip::errorCode();
+                    }
+                    $v_value = $p_options_list[$i + 1];
+                    if ((!is_integer($v_value)) || ($v_value < 0)) {
+                        PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Integer expected for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'");
+                        return PclZip::errorCode();
+                    }
+                    $v_result_list[$p_options_list[$i]] = $v_value * 1048576;
+                    $i++;
+                    break;
+                case PCLZIP_OPT_TEMP_FILE_ON :
+                    if (isset($v_result_list[PCLZIP_OPT_TEMP_FILE_OFF])) {
+                        PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Option '" . PclZipUtilOptionText($p_options_list[$i]) . "' can not be used with option 'PCLZIP_OPT_TEMP_FILE_OFF'");
+                        return PclZip::errorCode();
+                    }
+                    $v_result_list[$p_options_list[$i]] = true;
+                    break;
+                case PCLZIP_OPT_TEMP_FILE_OFF :
+                    if (isset($v_result_list[PCLZIP_OPT_TEMP_FILE_ON])) {
+                        PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Option '" . PclZipUtilOptionText($p_options_list[$i]) . "' can not be used with option 'PCLZIP_OPT_TEMP_FILE_ON'");
+                        return PclZip::errorCode();
+                    }
+                    if (isset($v_result_list[PCLZIP_OPT_TEMP_FILE_THRESHOLD])) {
+                        PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Option '" . PclZipUtilOptionText($p_options_list[$i]) . "' can not be used with option 'PCLZIP_OPT_TEMP_FILE_THRESHOLD'");
+                        return PclZip::errorCode();
+                    }
+                    $v_result_list[$p_options_list[$i]] = true;
+                    break;
+                case PCLZIP_OPT_EXTRACT_DIR_RESTRICTION :
+                    if (($i + 1) >= $p_size) {
+                        PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'");
+                        return PclZip::errorCode();
+                    }
+                    if (is_string($p_options_list[$i + 1]) && ($p_options_list[$i + 1] != '')) {
+                        $v_result_list[$p_options_list[$i]] = PclZipUtilTranslateWinPath($p_options_list[$i + 1], FALSE);
+                        $i++;
+                    } else {
+                    }
+                    break;
+                case PCLZIP_OPT_BY_NAME :
+                    if (($i + 1) >= $p_size) {
+                        PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'");
+                        return PclZip::errorCode();
+                    }
+                    if (is_string($p_options_list[$i + 1])) {
+                        $v_result_list[$p_options_list[$i]][0] = $p_options_list[$i + 1];
+                    } else if (is_array($p_options_list[$i + 1])) {
+                        $v_result_list[$p_options_list[$i]] = $p_options_list[$i + 1];
+                    } else {
+                        PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, 'Wrong parameter value for option \'' . PclZipUtilOptionText($p_options_list[$i]) . "'");
+                        return PclZip::errorCode();
+                    }
+                    $i++;
+                    break;
+                case PCLZIP_OPT_BY_EREG :
+                    $p_options_list[$i] = PCLZIP_OPT_BY_PREG;
+                case PCLZIP_OPT_BY_PREG :
+                    if (($i + 1) >= $p_size) {
+                        PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'");
+                        return PclZip::errorCode();
+                    }
+                    if (is_string($p_options_list[$i + 1])) {
+                        $v_result_list[$p_options_list[$i]] = $p_options_list[$i + 1];
+                    } else {
+                        PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, 'Wrong parameter value for option \'' . PclZipUtilOptionText($p_options_list[$i]) . "'");
+                        return PclZip::errorCode();
+                    }
+                    $i++;
+                    break;
+                case PCLZIP_OPT_COMMENT :
+                case PCLZIP_OPT_ADD_COMMENT :
+                case PCLZIP_OPT_PREPEND_COMMENT :
+                    if (($i + 1) >= $p_size) {
+                        PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'");
+                        return PclZip::errorCode();
+                    }
+                    if (is_string($p_options_list[$i + 1])) {
+                        $v_result_list[$p_options_list[$i]] = $p_options_list[$i + 1];
+                    } else {
+                        PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, 'Wrong parameter value for option \'' . PclZipUtilOptionText($p_options_list[$i]) . "'");
+                        return PclZip::errorCode();
+                    }
+                    $i++;
+                    break;
+                case PCLZIP_OPT_BY_INDEX :
+                    if (($i + 1) >= $p_size) {
+                        PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'");
+                        return PclZip::errorCode();
+                    }
+                    $v_work_list = array();
+                    if (is_string($p_options_list[$i + 1])) {
+                        $p_options_list[$i + 1] = strtr($p_options_list[$i + 1], ' ', '');
+                        $v_work_list            = explode(",", $p_options_list[$i + 1]);
+                    } else if (is_integer($p_options_list[$i + 1])) {
+                        $v_work_list[0] = $p_options_list[$i + 1] . '-' . $p_options_list[$i + 1];
+                    } else if (is_array($p_options_list[$i + 1])) {
+                        $v_work_list = $p_options_list[$i + 1];
+                    } else {
+                        PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, 'Value must be integer, string or array for option \'' . PclZipUtilOptionText($p_options_list[$i]) . "'");
+                        return PclZip::errorCode();
+                    }
+                    $v_sort_flag  = false;
+                    $v_sort_value = 0;
+                    for ($j = 0; $j < sizeof($v_work_list); $j++) {
+                        $v_item_list      = explode("-", $v_work_list[$j]);
+                        $v_size_item_list = sizeof($v_item_list);
+                        if ($v_size_item_list == 1) {
+                            $v_result_list[$p_options_list[$i]][$j]['start'] = $v_item_list[0];
+                            $v_result_list[$p_options_list[$i]][$j]['end']   = $v_item_list[0];
+                        } elseif ($v_size_item_list == 2) {
+                            $v_result_list[$p_options_list[$i]][$j]['start'] = $v_item_list[0];
+                            $v_result_list[$p_options_list[$i]][$j]['end']   = $v_item_list[1];
+                        } else {
+                            PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, 'Too many values in index range for option \'' . PclZipUtilOptionText($p_options_list[$i]) . "'");
+                            return PclZip::errorCode();
+                        }
+                        if ($v_result_list[$p_options_list[$i]][$j]['start'] < $v_sort_value) {
+                            $v_sort_flag = true;
+                            PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, 'Invalid order of index range for option \'' . PclZipUtilOptionText($p_options_list[$i]) . "'");
+                            return PclZip::errorCode();
+                        }
+                        $v_sort_value = $v_result_list[$p_options_list[$i]][$j]['start'];
+                    }
+                    if ($v_sort_flag) {
+                    }
+                    $i++;
+                    break;
+                case PCLZIP_OPT_REMOVE_ALL_PATH :
+                case PCLZIP_OPT_EXTRACT_AS_STRING :
+                case PCLZIP_OPT_NO_COMPRESSION :
+                case PCLZIP_OPT_EXTRACT_IN_OUTPUT :
+                case PCLZIP_OPT_REPLACE_NEWER :
+                case PCLZIP_OPT_STOP_ON_ERROR :
+                    $v_result_list[$p_options_list[$i]] = true;
+                    break;
+                case PCLZIP_OPT_SET_CHMOD :
+                    if (($i + 1) >= $p_size) {
+                        PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'");
+                        return PclZip::errorCode();
+                    }
+                    $v_result_list[$p_options_list[$i]] = $p_options_list[$i + 1];
+                    $i++;
+                    break;
+                case PCLZIP_CB_PRE_EXTRACT :
+                case PCLZIP_CB_POST_EXTRACT :
+                case PCLZIP_CB_PRE_ADD :
+                case PCLZIP_CB_POST_ADD :
+                    if (($i + 1) >= $p_size) {
+                        PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'");
+                        return PclZip::errorCode();
+                    }
+                    $v_function_name = $p_options_list[$i + 1];
+                    if (!function_exists($v_function_name)) {
+                        PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Function '" . $v_function_name . "()' is not an existing function for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'");
+                        return PclZip::errorCode();
+                    }
+                    $v_result_list[$p_options_list[$i]] = $v_function_name;
+                    $i++;
+                    break;
+                default :
+                    PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, 'Unknown parameter \'' . $p_options_list[$i] . "'");
+                    return PclZip::errorCode();
+            }
+            $i++;
+        }
+        if ($v_requested_options !== false) {
+            for ($key = reset($v_requested_options); $key = key($v_requested_options); $key = next($v_requested_options)) {
+                if ($v_requested_options[$key] == 'mandatory') {
+                    if (!isset($v_result_list[$key])) {
+                        PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Missing mandatory parameter " . PclZipUtilOptionText($key) . "(" . $key . ")");
+                        return PclZip::errorCode();
+                    }
+                }
+            }
+        }
+        if (!isset($v_result_list[PCLZIP_OPT_TEMP_FILE_THRESHOLD])) {
+        }
+        return $v_result;
+    }
+
+    function privOptionDefaultThreshold(&$p_options)
+    {
+        $v_result = 1;
+        if (isset($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD]) || isset($p_options[PCLZIP_OPT_TEMP_FILE_OFF])) {
+            return $v_result;
+        }
+        $v_memory_limit = ini_get('memory_limit');
+        $v_memory_limit = trim($v_memory_limit);
+        $last           = strtolower(substr($v_memory_limit, -1));
+        if ($last == 'g') $v_memory_limit = $v_memory_limit * 1073741824;
+        if ($last == 'm') $v_memory_limit = $v_memory_limit * 1048576;
+        if ($last == 'k') $v_memory_limit = $v_memory_limit * 1024;
+        $p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD] = floor($v_memory_limit * PCLZIP_TEMPORARY_FILE_RATIO);
+        if ($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD] < 1048576) {
+            unset($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD]);
+        }
+        return $v_result;
+    }
+
+    function privFileDescrParseAtt(&$p_file_list, &$p_filedescr, $v_options, $v_requested_options = false)
+    {
+        $v_result = 1;
+        foreach ($p_file_list as $v_key => $v_value) {
+            if (!isset($v_requested_options[$v_key])) {
+                PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid file attribute '" . $v_key . "' for this file");
+                return PclZip::errorCode();
+            }
+            switch ($v_key) {
+                case PCLZIP_ATT_FILE_NAME :
+                    if (!is_string($v_value)) {
+                        PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type " . gettype($v_value) . ". String expected for attribute '" . PclZipUtilOptionText($v_key) . "'");
+                        return PclZip::errorCode();
+                    }
+                    $p_filedescr['filename'] = PclZipUtilPathReduction($v_value);
+                    if ($p_filedescr['filename'] == '') {
+                        PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid empty filename for attribute '" . PclZipUtilOptionText($v_key) . "'");
+                        return PclZip::errorCode();
+                    }
+                    break;
+                case PCLZIP_ATT_FILE_NEW_SHORT_NAME :
+                    if (!is_string($v_value)) {
+                        PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type " . gettype($v_value) . ". String expected for attribute '" . PclZipUtilOptionText($v_key) . "'");
+                        return PclZip::errorCode();
+                    }
+                    $p_filedescr['new_short_name'] = PclZipUtilPathReduction($v_value);
+                    if ($p_filedescr['new_short_name'] == '') {
+                        PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid empty short filename for attribute '" . PclZipUtilOptionText($v_key) . "'");
+                        return PclZip::errorCode();
+                    }
+                    break;
+                case PCLZIP_ATT_FILE_NEW_FULL_NAME :
+                    if (!is_string($v_value)) {
+                        PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type " . gettype($v_value) . ". String expected for attribute '" . PclZipUtilOptionText($v_key) . "'");
+                        return PclZip::errorCode();
+                    }
+                    $p_filedescr['new_full_name'] = PclZipUtilPathReduction($v_value);
+                    if ($p_filedescr['new_full_name'] == '') {
+                        PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid empty full filename for attribute '" . PclZipUtilOptionText($v_key) . "'");
+                        return PclZip::errorCode();
+                    }
+                    break;
+                case PCLZIP_ATT_FILE_COMMENT :
+                    if (!is_string($v_value)) {
+                        PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type " . gettype($v_value) . ". String expected for attribute '" . PclZipUtilOptionText($v_key) . "'");
+                        return PclZip::errorCode();
+                    }
+                    $p_filedescr['comment'] = $v_value;
+                    break;
+                case PCLZIP_ATT_FILE_MTIME :
+                    if (!is_integer($v_value)) {
+                        PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type " . gettype($v_value) . ". Integer expected for attribute '" . PclZipUtilOptionText($v_key) . "'");
+                        return PclZip::errorCode();
+                    }
+                    $p_filedescr['mtime'] = $v_value;
+                    break;
+                case PCLZIP_ATT_FILE_CONTENT :
+                    $p_filedescr['content'] = $v_value;
+                    break;
+                default :
+                    PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, 'Unknown parameter \'' . $v_key . "'");
+                    return PclZip::errorCode();
+            }
+            if ($v_requested_options !== false) {
+                for ($key = reset($v_requested_options); $key = key($v_requested_options); $key = next($v_requested_options)) {
+                    if ($v_requested_options[$key] == 'mandatory') {
+                        if (!isset($p_file_list[$key])) {
+                            PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Missing mandatory parameter " . PclZipUtilOptionText($key) . "(" . $key . ")");
+                            return PclZip::errorCode();
+                        }
+                    }
+                }
+            }
+        }
+        return $v_result;
+    }
+
+    function privFileDescrExpand(&$p_filedescr_list, &$p_options)
+    {
+        $v_result      = 1;
+        $v_result_list = array();
+        for ($i = 0; $i < sizeof($p_filedescr_list); $i++) {
+            $v_descr             = $p_filedescr_list[$i];
+            $v_descr['filename'] = PclZipUtilTranslateWinPath($v_descr['filename'], false);
+            $v_descr['filename'] = PclZipUtilPathReduction($v_descr['filename']);
+            if (file_exists($v_descr['filename'])) {
+                if (@is_file($v_descr['filename'])) {
+                    $v_descr['type'] = 'file';
+                } else if (@is_dir($v_descr['filename'])) {
+                    $v_descr['type'] = 'folder';
+                } else if (@is_link($v_descr['filename'])) {
+                    continue;
+                } else {
+                    continue;
+                }
+            } else if (isset($v_descr['content'])) {
+                $v_descr['type'] = 'virtual_file';
+            } else {
+                PclZip::privErrorLog(PCLZIP_ERR_MISSING_FILE, 'File \'' . $v_descr['filename'] . "' does not exist");
+                return PclZip::errorCode();
+            }
+            $this->privCalculateStoredFilename($v_descr, $p_options);
+            $v_result_list[sizeof($v_result_list)] = $v_descr;
+            if ($v_descr['type'] == 'folder') {
+                $v_dirlist_descr = array();
+                $v_dirlist_nb    = 0;
+                if ($v_folder_handler = @opendir($v_descr['filename'])) {
+                    while (($v_item_handler = @readdir($v_folder_handler)) !== false) {
+                        if (($v_item_handler == '.') || ($v_item_handler == '..')) {
+                            continue;
+                        }
+                        $v_dirlist_descr[$v_dirlist_nb]['filename'] = $v_descr['filename'] . '/' . $v_item_handler;
+                        if (($v_descr['stored_filename'] != $v_descr['filename']) && (!isset($p_options[PCLZIP_OPT_REMOVE_ALL_PATH]))) {
+                            if ($v_descr['stored_filename'] != '') {
+                                $v_dirlist_descr[$v_dirlist_nb]['new_full_name'] = $v_descr['stored_filename'] . '/' . $v_item_handler;
+                            } else {
+                                $v_dirlist_descr[$v_dirlist_nb]['new_full_name'] = $v_item_handler;
+                            }
+                        }
+                        $v_dirlist_nb++;
+                    }
+                    @closedir($v_folder_handler);
+                } else {
+                }
+                if ($v_dirlist_nb != 0) {
+                    if (($v_result = $this->privFileDescrExpand($v_dirlist_descr, $p_options)) != 1) {
+                        return $v_result;
+                    }
+                    $v_result_list = array_merge($v_result_list, $v_dirlist_descr);
+                } else {
+                }
+                unset($v_dirlist_descr);
+            }
+        }
+        $p_filedescr_list = $v_result_list;
+        return $v_result;
+    }
+
+    function privCreate($p_filedescr_list, &$p_result_list, &$p_options)
+    {
+        $v_result      = 1;
+        $v_list_detail = array();
+        $this->privDisableMagicQuotes();
+        if (($v_result = $this->privOpenFd('wb')) != 1) {
+            return $v_result;
+        }
+        $v_result = $this->privAddList($p_filedescr_list, $p_result_list, $p_options);
+        $this->privCloseFd();
+        $this->privSwapBackMagicQuotes();
+        return $v_result;
+    }
+
+    function privAdd($p_filedescr_list, &$p_result_list, &$p_options)
+    {
+        $v_result      = 1;
+        $v_list_detail = array();
+        if ((!is_file($this->zipname)) || (filesize($this->zipname) == 0)) {
+            $v_result = $this->privCreate($p_filedescr_list, $p_result_list, $p_options);
+            return $v_result;
+        }
+        $this->privDisableMagicQuotes();
+        if (($v_result = $this->privOpenFd('rb')) != 1) {
+            $this->privSwapBackMagicQuotes();
+            return $v_result;
+        }
+        $v_central_dir = array();
+        if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1) {
+            $this->privCloseFd();
+            $this->privSwapBackMagicQuotes();
+            return $v_result;
+        }
+        @rewind($this->zip_fd);
+        $v_zip_temp_name = PCLZIP_TEMPORARY_DIR . uniqid('pclzip-') . '.tmp';
+        if (($v_zip_temp_fd = @fopen($v_zip_temp_name, 'wb')) == 0) {
+            $this->privCloseFd();
+            $this->privSwapBackMagicQuotes();
+            PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \'' . $v_zip_temp_name . '\' in binary write mode');
+            return PclZip::errorCode();
+        }
+        $v_size = $v_central_dir['offset'];
+        while ($v_size != 0) {
+            $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE);
+            $v_buffer    = fread($this->zip_fd, $v_read_size);
+            @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size);
+            $v_size -= $v_read_size;
+        }
+        $v_swap        = $this->zip_fd;
+        $this->zip_fd  = $v_zip_temp_fd;
+        $v_zip_temp_fd = $v_swap;
+        $v_header_list = array();
+        if (($v_result = $this->privAddFileList($p_filedescr_list, $v_header_list, $p_options)) != 1) {
+            fclose($v_zip_temp_fd);
+            $this->privCloseFd();
+            @unlink($v_zip_temp_name);
+            $this->privSwapBackMagicQuotes();
+            return $v_result;
+        }
+        $v_offset = @ftell($this->zip_fd);
+        $v_size   = $v_central_dir['size'];
+        while ($v_size != 0) {
+            $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE);
+            $v_buffer    = @fread($v_zip_temp_fd, $v_read_size);
+            @fwrite($this->zip_fd, $v_buffer, $v_read_size);
+            $v_size -= $v_read_size;
+        }
+        for ($i = 0, $v_count = 0; $i < sizeof($v_header_list); $i++) {
+            if ($v_header_list[$i]['status'] == 'ok') {
+                if (($v_result = $this->privWriteCentralFileHeader($v_header_list[$i])) != 1) {
+                    fclose($v_zip_temp_fd);
+                    $this->privCloseFd();
+                    @unlink($v_zip_temp_name);
+                    $this->privSwapBackMagicQuotes();
+                    return $v_result;
+                }
+                $v_count++;
+            }
+            $this->privConvertHeader2FileInfo($v_header_list[$i], $p_result_list[$i]);
+        }
+        $v_comment = $v_central_dir['comment'];
+        if (isset($p_options[PCLZIP_OPT_COMMENT])) {
+            $v_comment = $p_options[PCLZIP_OPT_COMMENT];
+        }
+        if (isset($p_options[PCLZIP_OPT_ADD_COMMENT])) {
+            $v_comment = $v_comment . $p_options[PCLZIP_OPT_ADD_COMMENT];
+        }
+        if (isset($p_options[PCLZIP_OPT_PREPEND_COMMENT])) {
+            $v_comment = $p_options[PCLZIP_OPT_PREPEND_COMMENT] . $v_comment;
+        }
+        $v_size = @ftell($this->zip_fd) - $v_offset;
+        if (($v_result = $this->privWriteCentralHeader($v_count + $v_central_dir['entries'], $v_size, $v_offset, $v_comment)) != 1) {
+            unset($v_header_list);
+            $this->privSwapBackMagicQuotes();
+            return $v_result;
+        }
+        $v_swap        = $this->zip_fd;
+        $this->zip_fd  = $v_zip_temp_fd;
+        $v_zip_temp_fd = $v_swap;
+        $this->privCloseFd();
+        @fclose($v_zip_temp_fd);
+        $this->privSwapBackMagicQuotes();
+        @unlink($this->zipname);
+        PclZipUtilRename($v_zip_temp_name, $this->zipname);
+        return $v_result;
+    }
+
+    function privOpenFd($p_mode)
+    {
+        $v_result = 1;
+        if ($this->zip_fd != 0) {
+            PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Zip file \'' . $this->zipname . '\' already open');
+            return PclZip::errorCode();
+        }
+        if (($this->zip_fd = @fopen($this->zipname, $p_mode)) == 0) {
+            PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open archive \'' . $this->zipname . '\' in ' . $p_mode . ' mode');
+            return PclZip::errorCode();
+        }
+        return $v_result;
+    }
+
+    function privCloseFd()
+    {
+        $v_result = 1;
+        if ($this->zip_fd != 0) @fclose($this->zip_fd);
+        $this->zip_fd = 0;
+        return $v_result;
+    }
+
+    function privAddList($p_filedescr_list, &$p_result_list, &$p_options)
+    {
+        $v_result      = 1;
+        $v_header_list = array();
+        if (($v_result = $this->privAddFileList($p_filedescr_list, $v_header_list, $p_options)) != 1) {
+            return $v_result;
+        }
+        $v_offset = @ftell($this->zip_fd);
+        for ($i = 0, $v_count = 0; $i < sizeof($v_header_list); $i++) {
+            if ($v_header_list[$i]['status'] == 'ok') {
+                if (($v_result = $this->privWriteCentralFileHeader($v_header_list[$i])) != 1) {
+                    return $v_result;
+                }
+                $v_count++;
+            }
+            $this->privConvertHeader2FileInfo($v_header_list[$i], $p_result_list[$i]);
+        }
+        $v_comment = '';
+        if (isset($p_options[PCLZIP_OPT_COMMENT])) {
+            $v_comment = $p_options[PCLZIP_OPT_COMMENT];
+        }
+        $v_size = @ftell($this->zip_fd) - $v_offset;
+        if (($v_result = $this->privWriteCentralHeader($v_count, $v_size, $v_offset, $v_comment)) != 1) {
+            unset($v_header_list);
+            return $v_result;
+        }
+        return $v_result;
+    }
+
+    function privAddFileList($p_filedescr_list, &$p_result_list, &$p_options)
+    {
+        $v_result = 1;
+        $v_header = array();
+        $v_nb     = sizeof($p_result_list);
+        for ($j = 0; ($j < sizeof($p_filedescr_list)) && ($v_result == 1); $j++) {
+            $p_filedescr_list[$j]['filename'] = PclZipUtilTranslateWinPath($p_filedescr_list[$j]['filename'], false);
+            if ($p_filedescr_list[$j]['filename'] == "") {
+                continue;
+            }
+            if (($p_filedescr_list[$j]['type'] != 'virtual_file') && (!file_exists($p_filedescr_list[$j]['filename']))) {
+                PclZip::privErrorLog(PCLZIP_ERR_MISSING_FILE, "File '" . $p_filedescr_list[$j]['filename'] . "' does not exist");
+                return PclZip::errorCode();
+            }
+            if (($p_filedescr_list[$j]['type'] == 'file') || ($p_filedescr_list[$j]['type'] == 'virtual_file') || (($p_filedescr_list[$j]['type'] == 'folder') && (!isset($p_options[PCLZIP_OPT_REMOVE_ALL_PATH]) || !$p_options[PCLZIP_OPT_REMOVE_ALL_PATH]))) {
+                $v_result = $this->privAddFile($p_filedescr_list[$j], $v_header, $p_options);
+                if ($v_result != 1) {
+                    return $v_result;
+                }
+                $p_result_list[$v_nb++] = $v_header;
+            }
+        }
+        return $v_result;
+    }
+
+    function privAddFile($p_filedescr, &$p_header, &$p_options)
+    {
+        $v_result   = 1;
+        $p_filename = $p_filedescr['filename'];
+        if ($p_filename == "") {
+            PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid file list parameter (invalid or empty list)");
+            return PclZip::errorCode();
+        }
+        clearstatcache();
+        $p_header['version']           = 20;
+        $p_header['version_extracted'] = 10;
+        $p_header['flag']              = 0;
+        $p_header['compression']       = 0;
+        $p_header['crc']               = 0;
+        $p_header['compressed_size']   = 0;
+        $p_header['filename_len']      = strlen($p_filename);
+        $p_header['extra_len']         = 0;
+        $p_header['disk']              = 0;
+        $p_header['internal']          = 0;
+        $p_header['offset']            = 0;
+        $p_header['filename']          = $p_filename;
+        $p_header['stored_filename']   = $p_filedescr['stored_filename'];
+        $p_header['extra']             = '';
+        $p_header['status']            = 'ok';
+        $p_header['index']             = -1;
+        if ($p_filedescr['type'] == 'file') {
+            $p_header['external'] = 0x00000000;
+            $p_header['size']     = filesize($p_filename);
+        } else if ($p_filedescr['type'] == 'folder') {
+            $p_header['external'] = 0x00000010;
+            $p_header['mtime']    = filemtime($p_filename);
+            $p_header['size']     = filesize($p_filename);
+        } else if ($p_filedescr['type'] == 'virtual_file') {
+            $p_header['external'] = 0x00000000;
+            $p_header['size']     = strlen($p_filedescr['content']);
+        }
+        if (isset($p_filedescr['mtime'])) {
+            $p_header['mtime'] = $p_filedescr['mtime'];
+        } else if ($p_filedescr['type'] == 'virtual_file') {
+            $p_header['mtime'] = time();
+        } else {
+            $p_header['mtime'] = filemtime($p_filename);
+        }
+        if (isset($p_filedescr['comment'])) {
+            $p_header['comment_len'] = strlen($p_filedescr['comment']);
+            $p_header['comment']     = $p_filedescr['comment'];
+        } else {
+            $p_header['comment_len'] = 0;
+            $p_header['comment']     = '';
+        }
+        if (isset($p_options[PCLZIP_CB_PRE_ADD])) {
+            $v_local_header = array();
+            $this->privConvertHeader2FileInfo($p_header, $v_local_header);
+            $v_result = $p_options[PCLZIP_CB_PRE_ADD](PCLZIP_CB_PRE_ADD, $v_local_header);
+            if ($v_result == 0) {
+                $p_header['status'] = "skipped";
+                $v_result           = 1;
+            }
+            if ($p_header['stored_filename'] != $v_local_header['stored_filename']) {
+                $p_header['stored_filename'] = PclZipUtilPathReduction($v_local_header['stored_filename']);
+            }
+        }
+        if ($p_header['stored_filename'] == "") {
+            $p_header['status'] = "filtered";
+        }
+        if (strlen($p_header['stored_filename']) > 0xFF) {
+            $p_header['status'] = 'filename_too_long';
+        }
+        if ($p_header['status'] == 'ok') {
+            if ($p_filedescr['type'] == 'file') {
+                if ((!isset($p_options[PCLZIP_OPT_TEMP_FILE_OFF])) && (isset($p_options[PCLZIP_OPT_TEMP_FILE_ON]) || (isset($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD]) && ($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD] <= $p_header['size'])))) {
+                    $v_result = $this->privAddFileUsingTempFile($p_filedescr, $p_header, $p_options);
+                    if ($v_result < PCLZIP_ERR_NO_ERROR) {
+                        return $v_result;
+                    }
+                } else {
+                    if (($v_file = @fopen($p_filename, "rb")) == 0) {
+                        PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, "Unable to open file '$p_filename' in binary read mode");
+                        return PclZip::errorCode();
+                    }
+                    $v_content = @fread($v_file, $p_header['size']);
+                    @fclose($v_file);
+                    $p_header['crc'] = @crc32($v_content);
+                    if ($p_options[PCLZIP_OPT_NO_COMPRESSION]) {
+                        $p_header['compressed_size'] = $p_header['size'];
+                        $p_header['compression']     = 0;
+                    } else {
+                        $v_content                   = @gzdeflate($v_content);
+                        $p_header['compressed_size'] = strlen($v_content);
+                        $p_header['compression']     = 8;
+                    }
+                    if (($v_result = $this->privWriteFileHeader($p_header)) != 1) {
+                        @fclose($v_file);
+                        return $v_result;
+                    }
+                    @fwrite($this->zip_fd, $v_content, $p_header['compressed_size']);
+                }
+            } else if ($p_filedescr['type'] == 'virtual_file') {
+                $v_content       = $p_filedescr['content'];
+                $p_header['crc'] = @crc32($v_content);
+                if ($p_options[PCLZIP_OPT_NO_COMPRESSION]) {
+                    $p_header['compressed_size'] = $p_header['size'];
+                    $p_header['compression']     = 0;
+                } else {
+                    $v_content                   = @gzdeflate($v_content);
+                    $p_header['compressed_size'] = strlen($v_content);
+                    $p_header['compression']     = 8;
+                }
+                if (($v_result = $this->privWriteFileHeader($p_header)) != 1) {
+                    @fclose($v_file);
+                    return $v_result;
+                }
+                @fwrite($this->zip_fd, $v_content, $p_header['compressed_size']);
+            } else if ($p_filedescr['type'] == 'folder') {
+                if (@substr($p_header['stored_filename'], -1) != '/') {
+                    $p_header['stored_filename'] .= '/';
+                }
+                $p_header['size']     = 0;
+                $p_header['external'] = 0x00000010;
+                if (($v_result = $this->privWriteFileHeader($p_header)) != 1) {
+                    return $v_result;
+                }
+            }
+        }
+        if (isset($p_options[PCLZIP_CB_POST_ADD])) {
+            $v_local_header = array();
+            $this->privConvertHeader2FileInfo($p_header, $v_local_header);
+            $v_result = $p_options[PCLZIP_CB_POST_ADD](PCLZIP_CB_POST_ADD, $v_local_header);
+            if ($v_result == 0) {
+                $v_result = 1;
+            }
+        }
+        return $v_result;
+    }
+
+    function privAddFileUsingTempFile($p_filedescr, &$p_header, &$p_options)
+    {
+        $v_result   = PCLZIP_ERR_NO_ERROR;
+        $p_filename = $p_filedescr['filename'];
+        if (($v_file = @fopen($p_filename, "rb")) == 0) {
+            PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, "Unable to open file '$p_filename' in binary read mode");
+            return PclZip::errorCode();
+        }
+        $v_gzip_temp_name = PCLZIP_TEMPORARY_DIR . uniqid('pclzip-') . '.gz';
+        if (($v_file_compressed = @gzopen($v_gzip_temp_name, "wb")) == 0) {
+            fclose($v_file);
+            PclZip::privErrorLog(PCLZIP_ERR_WRITE_OPEN_FAIL, 'Unable to open temporary file \'' . $v_gzip_temp_name . '\' in binary write mode');
+            return PclZip::errorCode();
+        }
+        $v_size = filesize($p_filename);
+        while ($v_size != 0) {
+            $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE);
+            $v_buffer    = @fread($v_file, $v_read_size);
+            @gzputs($v_file_compressed, $v_buffer, $v_read_size);
+            $v_size -= $v_read_size;
+        }
+        @fclose($v_file);
+        @gzclose($v_file_compressed);
+        if (filesize($v_gzip_temp_name) < 18) {
+            PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'gzip temporary file \'' . $v_gzip_temp_name . '\' has invalid filesize - should be minimum 18 bytes');
+            return PclZip::errorCode();
+        }
+        if (($v_file_compressed = @fopen($v_gzip_temp_name, "rb")) == 0) {
+            PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \'' . $v_gzip_temp_name . '\' in binary read mode');
+            return PclZip::errorCode();
+        }
+        $v_binary_data       = @fread($v_file_compressed, 10);
+        $v_data_header       = unpack('a1id1/a1id2/a1cm/a1flag/Vmtime/a1xfl/a1os', $v_binary_data);
+        $v_data_header['os'] = bin2hex($v_data_header['os']);
+        @fseek($v_file_compressed, filesize($v_gzip_temp_name) - 8);
+        $v_binary_data               = @fread($v_file_compressed, 8);
+        $v_data_footer               = unpack('Vcrc/Vcompressed_size', $v_binary_data);
+        $p_header['compression']     = ord($v_data_header['cm']);
+        $p_header['crc']             = $v_data_footer['crc'];
+        $p_header['compressed_size'] = filesize($v_gzip_temp_name) - 18;
+        @fclose($v_file_compressed);
+        if (($v_result = $this->privWriteFileHeader($p_header)) != 1) {
+            return $v_result;
+        }
+        if (($v_file_compressed = @fopen($v_gzip_temp_name, "rb")) == 0) {
+            PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \'' . $v_gzip_temp_name . '\' in binary read mode');
+            return PclZip::errorCode();
+        }
+        fseek($v_file_compressed, 10);
+        $v_size = $p_header['compressed_size'];
+        while ($v_size != 0) {
+            $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE);
+            $v_buffer    = @fread($v_file_compressed, $v_read_size);
+            @fwrite($this->zip_fd, $v_buffer, $v_read_size);
+            $v_size -= $v_read_size;
+        }
+        @fclose($v_file_compressed);
+        @unlink($v_gzip_temp_name);
+        return $v_result;
+    }
+
+    function privCalculateStoredFilename(&$p_filedescr, &$p_options)
+    {
+        $v_result   = 1;
+        $p_filename = $p_filedescr['filename'];
+        if (isset($p_options[PCLZIP_OPT_ADD_PATH])) {
+            $p_add_dir = $p_options[PCLZIP_OPT_ADD_PATH];
+        } else {
+            $p_add_dir = '';
+        }
+        if (isset($p_options[PCLZIP_OPT_REMOVE_PATH])) {
+            $p_remove_dir = $p_options[PCLZIP_OPT_REMOVE_PATH];
+        } else {
+            $p_remove_dir = '';
+        }
+        if (isset($p_options[PCLZIP_OPT_REMOVE_ALL_PATH])) {
+            $p_remove_all_dir = $p_options[PCLZIP_OPT_REMOVE_ALL_PATH];
+        } else {
+            $p_remove_all_dir = 0;
+        }
+        if (isset($p_filedescr['new_full_name'])) {
+            $v_stored_filename = PclZipUtilTranslateWinPath($p_filedescr['new_full_name']);
+        } else {
+            if (isset($p_filedescr['new_short_name'])) {
+                $v_path_info = pathinfo($p_filename);
+                $v_dir       = '';
+                if ($v_path_info['dirname'] != '') {
+                    $v_dir = $v_path_info['dirname'] . '/';
+                }
+                $v_stored_filename = $v_dir . $p_filedescr['new_short_name'];
+            } else {
+                $v_stored_filename = $p_filename;
+            }
+            if ($p_remove_all_dir) {
+                $v_stored_filename = basename($p_filename);
+            } else if ($p_remove_dir != "") {
+                if (substr($p_remove_dir, -1) != '/') $p_remove_dir .= "/";
+                if ((substr($p_filename, 0, 2) == "./") || (substr($p_remove_dir, 0, 2) == "./")) {
+                    if ((substr($p_filename, 0, 2) == "./") && (substr($p_remove_dir, 0, 2) != "./")) {
+                        $p_remove_dir = "./" . $p_remove_dir;
+                    }
+                    if ((substr($p_filename, 0, 2) != "./") && (substr($p_remove_dir, 0, 2) == "./")) {
+                        $p_remove_dir = substr($p_remove_dir, 2);
+                    }
+                }
+                $v_compare = PclZipUtilPathInclusion($p_remove_dir, $v_stored_filename);
+                if ($v_compare > 0) {
+                    if ($v_compare == 2) {
+                        $v_stored_filename = "";
+                    } else {
+                        $v_stored_filename = substr($v_stored_filename, strlen($p_remove_dir));
+                    }
+                }
+            }
+            $v_stored_filename = PclZipUtilTranslateWinPath($v_stored_filename);
+            if ($p_add_dir != "") {
+                if (substr($p_add_dir, -1) == "/") $v_stored_filename = $p_add_dir . $v_stored_filename; else $v_stored_filename = $p_add_dir . "/" . $v_stored_filename;
+            }
+        }
+        $v_stored_filename              = PclZipUtilPathReduction($v_stored_filename);
+        $p_filedescr['stored_filename'] = $v_stored_filename;
+        return $v_result;
+    }
+
+    function privWriteFileHeader(&$p_header)
+    {
+        $v_result           = 1;
+        $p_header['offset'] = ftell($this->zip_fd);
+        $v_date             = getdate($p_header['mtime']);
+        $v_mtime            = ($v_date['hours'] << 11) + ($v_date['minutes'] << 5) + $v_date['seconds'] / 2;
+        $v_mdate            = (($v_date['year'] - 1980) << 9) + ($v_date['mon'] << 5) + $v_date['mday'];
+        $v_binary_data      = pack("VvvvvvVVVvv", 0x04034b50, $p_header['version_extracted'], $p_header['flag'], $p_header['compression'], $v_mtime, $v_mdate, $p_header['crc'], $p_header['compressed_size'], $p_header['size'], strlen($p_header['stored_filename']), $p_header['extra_len']);
+        fputs($this->zip_fd, $v_binary_data, 30);
+        if (strlen($p_header['stored_filename']) != 0) {
+            fputs($this->zip_fd, $p_header['stored_filename'], strlen($p_header['stored_filename']));
+        }
+        if ($p_header['extra_len'] != 0) {
+            fputs($this->zip_fd, $p_header['extra'], $p_header['extra_len']);
+        }
+        return $v_result;
+    }
+
+    function privWriteCentralFileHeader(&$p_header)
+    {
+        $v_result      = 1;
+        $v_date        = getdate($p_header['mtime']);
+        $v_mtime       = ($v_date['hours'] << 11) + ($v_date['minutes'] << 5) + $v_date['seconds'] / 2;
+        $v_mdate       = (($v_date['year'] - 1980) << 9) + ($v_date['mon'] << 5) + $v_date['mday'];
+        $v_binary_data = pack("VvvvvvvVVVvvvvvVV", 0x02014b50, $p_header['version'], $p_header['version_extracted'], $p_header['flag'], $p_header['compression'], $v_mtime, $v_mdate, $p_header['crc'], $p_header['compressed_size'], $p_header['size'], strlen($p_header['stored_filename']), $p_header['extra_len'], $p_header['comment_len'], $p_header['disk'], $p_header['internal'], $p_header['external'], $p_header['offset']);
+        fputs($this->zip_fd, $v_binary_data, 46);
+        if (strlen($p_header['stored_filename']) != 0) {
+            fputs($this->zip_fd, $p_header['stored_filename'], strlen($p_header['stored_filename']));
+        }
+        if ($p_header['extra_len'] != 0) {
+            fputs($this->zip_fd, $p_header['extra'], $p_header['extra_len']);
+        }
+        if ($p_header['comment_len'] != 0) {
+            fputs($this->zip_fd, $p_header['comment'], $p_header['comment_len']);
+        }
+        return $v_result;
+    }
+
+    function privWriteCentralHeader($p_nb_entries, $p_size, $p_offset, $p_comment)
+    {
+        $v_result      = 1;
+        $v_binary_data = pack("VvvvvVVv", 0x06054b50, 0, 0, $p_nb_entries, $p_nb_entries, $p_size, $p_offset, strlen($p_comment));
+        fputs($this->zip_fd, $v_binary_data, 22);
+        if (strlen($p_comment) != 0) {
+            fputs($this->zip_fd, $p_comment, strlen($p_comment));
+        }
+        return $v_result;
+    }
+
+    function privList(&$p_list)
+    {
+        $v_result = 1;
+        $this->privDisableMagicQuotes();
+        if (($this->zip_fd = @fopen($this->zipname, 'rb')) == 0) {
+            $this->privSwapBackMagicQuotes();
+            PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open archive \'' . $this->zipname . '\' in binary read mode');
+            return PclZip::errorCode();
+        }
+        $v_central_dir = array();
+        if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1) {
+            $this->privSwapBackMagicQuotes();
+            return $v_result;
+        }
+        @rewind($this->zip_fd);
+        if (@fseek($this->zip_fd, $v_central_dir['offset'])) {
+            $this->privSwapBackMagicQuotes();
+            PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size');
+            return PclZip::errorCode();
+        }
+        for ($i = 0; $i < $v_central_dir['entries']; $i++) {
+            if (($v_result = $this->privReadCentralFileHeader($v_header)) != 1) {
+                $this->privSwapBackMagicQuotes();
+                return $v_result;
+            }
+            $v_header['index'] = $i;
+            $this->privConvertHeader2FileInfo($v_header, $p_list[$i]);
+            unset($v_header);
+        }
+        $this->privCloseFd();
+        $this->privSwapBackMagicQuotes();
+        return $v_result;
+    }
+
+    function privConvertHeader2FileInfo($p_header, &$p_info)
+    {
+        $v_result                  = 1;
+        $v_temp_path               = PclZipUtilPathReduction($p_header['filename']);
+        $p_info['filename']        = $v_temp_path;
+        $v_temp_path               = PclZipUtilPathReduction($p_header['stored_filename']);
+        $p_info['stored_filename'] = $v_temp_path;
+        $p_info['size']            = $p_header['size'];
+        $p_info['compressed_size'] = $p_header['compressed_size'];
+        $p_info['mtime']           = $p_header['mtime'];
+        $p_info['comment']         = $p_header['comment'];
+        $p_info['folder']          = (($p_header['external'] & 0x00000010) == 0x00000010);
+        $p_info['index']           = $p_header['index'];
+        $p_info['status']          = $p_header['status'];
+        $p_info['crc']             = $p_header['crc'];
+        return $v_result;
+    }
+
+    function privExtractByRule(&$p_file_list, $p_path, $p_remove_path, $p_remove_all_path, &$p_options)
+    {
+        $v_result = 1;
+        $this->privDisableMagicQuotes();
+        if (($p_path == "") || ((substr($p_path, 0, 1) != "/") && (substr($p_path, 0, 3) != "../") && (substr($p_path, 1, 2) != ":/"))) $p_path = "./" . $p_path;
+        if (($p_path != "./") && ($p_path != "/")) {
+            while (substr($p_path, -1) == "/") {
+                $p_path = substr($p_path, 0, strlen($p_path) - 1);
+            }
+        }
+        if (($p_remove_path != "") && (substr($p_remove_path, -1) != '/')) {
+            $p_remove_path .= '/';
+        }
+        $p_remove_path_size = strlen($p_remove_path);
+        if (($v_result = $this->privOpenFd('rb')) != 1) {
+            $this->privSwapBackMagicQuotes();
+            return $v_result;
+        }
+        $v_central_dir = array();
+        if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1) {
+            $this->privCloseFd();
+            $this->privSwapBackMagicQuotes();
+            return $v_result;
+        }
+        $v_pos_entry = $v_central_dir['offset'];
+        $j_start     = 0;
+        for ($i = 0, $v_nb_extracted = 0; $i < $v_central_dir['entries']; $i++) {
+            @rewind($this->zip_fd);
+            if (@fseek($this->zip_fd, $v_pos_entry)) {
+                $this->privCloseFd();
+                $this->privSwapBackMagicQuotes();
+                PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size');
+                return PclZip::errorCode();
+            }
+            $v_header = array();
+            if (($v_result = $this->privReadCentralFileHeader($v_header)) != 1) {
+                $this->privCloseFd();
+                $this->privSwapBackMagicQuotes();
+                return $v_result;
+            }
+            $v_header['index'] = $i;
+            $v_pos_entry       = ftell($this->zip_fd);
+            $v_extract         = false;
+            if ((isset($p_options[PCLZIP_OPT_BY_NAME])) && ($p_options[PCLZIP_OPT_BY_NAME] != 0)) {
+                for ($j = 0; ($j < sizeof($p_options[PCLZIP_OPT_BY_NAME])) && (!$v_extract); $j++) {
+                    if (substr($p_options[PCLZIP_OPT_BY_NAME][$j], -1) == "/") {
+                        if ((strlen($v_header['stored_filename']) > strlen($p_options[PCLZIP_OPT_BY_NAME][$j])) && (substr($v_header['stored_filename'], 0, strlen($p_options[PCLZIP_OPT_BY_NAME][$j])) == $p_options[PCLZIP_OPT_BY_NAME][$j])) {
+                            $v_extract = true;
+                        }
+                    } elseif ($v_header['stored_filename'] == $p_options[PCLZIP_OPT_BY_NAME][$j]) {
+                        $v_extract = true;
+                    }
+                }
+            } else if ((isset($p_options[PCLZIP_OPT_BY_PREG])) && ($p_options[PCLZIP_OPT_BY_PREG] != "")) {
+                if (preg_match($p_options[PCLZIP_OPT_BY_PREG], $v_header['stored_filename'])) {
+                    $v_extract = true;
+                }
+            } else if ((isset($p_options[PCLZIP_OPT_BY_INDEX])) && ($p_options[PCLZIP_OPT_BY_INDEX] != 0)) {
+                for ($j = $j_start; ($j < sizeof($p_options[PCLZIP_OPT_BY_INDEX])) && (!$v_extract); $j++) {
+                    if (($i >= $p_options[PCLZIP_OPT_BY_INDEX][$j]['start']) && ($i <= $p_options[PCLZIP_OPT_BY_INDEX][$j]['end'])) {
+                        $v_extract = true;
+                    }
+                    if ($i >= $p_options[PCLZIP_OPT_BY_INDEX][$j]['end']) {
+                        $j_start = $j + 1;
+                    }
+                    if ($p_options[PCLZIP_OPT_BY_INDEX][$j]['start'] > $i) {
+                        break;
+                    }
+                }
+            } else {
+                $v_extract = true;
+            }
+            if (($v_extract) && (($v_header['compression'] != 8) && ($v_header['compression'] != 0))) {
+                $v_header['status'] = 'unsupported_compression';
+                if ((isset($p_options[PCLZIP_OPT_STOP_ON_ERROR])) && ($p_options[PCLZIP_OPT_STOP_ON_ERROR] === true)) {
+                    $this->privSwapBackMagicQuotes();
+                    PclZip::privErrorLog(PCLZIP_ERR_UNSUPPORTED_COMPRESSION, 'Filename \'' . $v_header['stored_filename'] . "' is " . "compressed by an unsupported compression " . "method (" . $v_header['compression'] . ") ");
+                    return PclZip::errorCode();
+                }
+            }
+            if (($v_extract) && (($v_header['flag'] & 1) == 1)) {
+                $v_header['status'] = 'unsupported_encryption';
+                if ((isset($p_options[PCLZIP_OPT_STOP_ON_ERROR])) && ($p_options[PCLZIP_OPT_STOP_ON_ERROR] === true)) {
+                    $this->privSwapBackMagicQuotes();
+                    PclZip::privErrorLog(PCLZIP_ERR_UNSUPPORTED_ENCRYPTION, 'Unsupported encryption for ' . ' filename \'' . $v_header['stored_filename'] . "'");
+                    return PclZip::errorCode();
+                }
+            }
+            if (($v_extract) && ($v_header['status'] != 'ok')) {
+                $v_result = $this->privConvertHeader2FileInfo($v_header, $p_file_list[$v_nb_extracted++]);
+                if ($v_result != 1) {
+                    $this->privCloseFd();
+                    $this->privSwapBackMagicQuotes();
+                    return $v_result;
+                }
+                $v_extract = false;
+            }
+            if ($v_extract) {
+                @rewind($this->zip_fd);
+                if (@fseek($this->zip_fd, $v_header['offset'])) {
+                    $this->privCloseFd();
+                    $this->privSwapBackMagicQuotes();
+                    PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size');
+                    return PclZip::errorCode();
+                }
+                if ($p_options[PCLZIP_OPT_EXTRACT_AS_STRING]) {
+                    $v_string  = '';
+                    $v_result1 = $this->privExtractFileAsString($v_header, $v_string, $p_options);
+                    if ($v_result1 < 1) {
+                        $this->privCloseFd();
+                        $this->privSwapBackMagicQuotes();
+                        return $v_result1;
+                    }
+                    if (($v_result = $this->privConvertHeader2FileInfo($v_header, $p_file_list[$v_nb_extracted])) != 1) {
+                        $this->privCloseFd();
+                        $this->privSwapBackMagicQuotes();
+                        return $v_result;
+                    }
+                    $p_file_list[$v_nb_extracted]['content'] = $v_string;
+                    $v_nb_extracted++;
+                    if ($v_result1 == 2) {
+                        break;
+                    }
+                } elseif ((isset($p_options[PCLZIP_OPT_EXTRACT_IN_OUTPUT])) && ($p_options[PCLZIP_OPT_EXTRACT_IN_OUTPUT])) {
+                    $v_result1 = $this->privExtractFileInOutput($v_header, $p_options);
+                    if ($v_result1 < 1) {
+                        $this->privCloseFd();
+                        $this->privSwapBackMagicQuotes();
+                        return $v_result1;
+                    }
+                    if (($v_result = $this->privConvertHeader2FileInfo($v_header, $p_file_list[$v_nb_extracted++])) != 1) {
+                        $this->privCloseFd();
+                        $this->privSwapBackMagicQuotes();
+                        return $v_result;
+                    }
+                    if ($v_result1 == 2) {
+                        break;
+                    }
+                } else {
+                    $v_result1 = $this->privExtractFile($v_header, $p_path, $p_remove_path, $p_remove_all_path, $p_options);
+                    if ($v_result1 < 1) {
+                        $this->privCloseFd();
+                        $this->privSwapBackMagicQuotes();
+                        return $v_result1;
+                    }
+                    if (($v_result = $this->privConvertHeader2FileInfo($v_header, $p_file_list[$v_nb_extracted++])) != 1) {
+                        $this->privCloseFd();
+                        $this->privSwapBackMagicQuotes();
+                        return $v_result;
+                    }
+                    if ($v_result1 == 2) {
+                        break;
+                    }
+                }
+            }
+        }
+        $this->privCloseFd();
+        $this->privSwapBackMagicQuotes();
+        return $v_result;
+    }
+
+    function privExtractFile(&$p_entry, $p_path, $p_remove_path, $p_remove_all_path, &$p_options)
+    {
+        $v_result = 1;
+        if (($v_result = $this->privReadFileHeader($v_header)) != 1) {
+            return $v_result;
+        }
+        if ($this->privCheckFileHeaders($v_header, $p_entry) != 1) {
+        }
+        function PclZipUtilTranslateWinPath($p_path, $p_remove_disk_letter = true)
+        {
+            if (stristr(php_uname(), 'windows')) {
+                if (($p_remove_disk_letter) && (($v_position = strpos($p_path, ':')) != false)) {
+                    $p_path = substr($p_path, $v_position + 1);
+                }
+                if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0, 1) == '\\')) {
+                    $p_path = strtr($p_path, '\\', '/');
+                }
+            }
+            return $p_path;
+        }

+ 112 - 0
apps/application/libraries/Hke.php

@@ -0,0 +1,112 @@
+<?php
+
+
+
+
+
+if( !defined('BASEPATH') ) 
+{
+    exit( 'No direct script access allowed' );
+}
+
+
+class Ftpup
+{
+    public function __construct()
+    {
+        set_time_limit(0);
+    }
+
+    public function up($file_path, $file_name)
+    {
+        if( Ftp_Is == 2 ) 
+        {
+            $data["Token"] = Ftp_Token;
+            $data["file"] = "@" . $file_path;
+            $data["deadline"] = time() + 60;
+            $data["aid"] = Ftp_Pid;
+            $data["from"] = "file";
+            $json = $this->uppost("http://up.tietuku.com/", $data);
+            $arr = json_decode($json, true);
+            if( !empty($arr["linkurl"]) ) 
+            {
+                unlink($file_path);
+                return $arr["linkurl"] . "#ttk";
+            }
+
+            return false;
+        }
+
+        $ci =& get_instance();
+        $ci->load->library("ftp");
+        if( $ci->ftp->connect(array( "port" => Ftp_Port, "debug" => false, "passive" => Ftp_Ive, "hostname" => Ftp_Server, "username" => Ftp_User, "password" => Ftp_Pass )) ) 
+        {
+            $Dirs = Ftp_Dir;
+            if( substr($Dirs, -1) == "/" ) 
+            {
+                $Dirs = substr($Dirs, 0, -1);
+            }
+
+            $dir = $Dirs . "/" . date("Ymd") . "/";
+            $ci->ftp->mkdir($dir);
+            if( $ci->ftp->upload($file_path, $dir . $file_name, SITE_ATTACH_MODE, 509) ) 
+            {
+                unlink($file_path);
+                $ci->ftp->close();
+                return str_replace($Dirs, "", $dir) . $file_name;
+            }
+
+            return false;
+        }
+
+        return false;
+    }
+
+    public function del($file_path)
+    {
+        $ci =& get_instance();
+        $ci->load->library("ftp");
+        if( substr($file_path, 0, 1) == "/" ) 
+        {
+            $file_path = substr($file_path, 1);
+        }
+
+        if( $ci->ftp->connect(array( "port" => Ftp_Port, "debug" => false, "passive" => Ftp_Ive, "hostname" => Ftp_Server, "username" => Ftp_User, "password" => Ftp_Pass )) ) 
+        {
+            $Dirs = Ftp_Dir;
+            if( substr($Dirs, -1) == "/" ) 
+            {
+                $Dirs = substr($Dirs, 0, -1);
+            }
+
+            $path = $Dirs . "/" . $file_path;
+            $res = $ci->ftp->delete_file("." . $path);
+            $ci->ftp->close();
+            if( !$res ) 
+            {
+                return false;
+            }
+
+            return true;
+        }
+
+        return NULL;
+    }
+
+    public function uppost($url, $post_data)
+    {
+        $ch = curl_init();
+        curl_setopt($ch, CURLOPT_URL, $url);
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
+        curl_setopt($ch, CURLOPT_POST, 1);
+        curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
+        $output = curl_exec($ch);
+        curl_close($ch);
+        return $output;
+    }
+
+}
+
+
+

+ 82 - 0
apps/application/libraries/Pdx.php

@@ -0,0 +1,82 @@
+<?php
+
+
+
+
+
+
+if( !defined('BASEPATH') ) 
+{
+    exit( 'No direct script access allowed' );
+}
+
+
+class Pays_app
+{
+    public function __construct()
+    {
+        if( CT_Pay == 0 ) 
+        {
+            msg_url("在线支付功能已关闭~!", "javascript:history.back();");
+        }
+
+        $this->api_url = "http://";
+        if( Web_Mode == 2 ) 
+        {
+            $this->return_url = "http://" . Web_Url . Web_Path . "index.php?d=app&c=pay&m=return_url";
+            $this->notify_url = "http://" . Web_Url . Web_Path . "index.php?d=app&c=pay&m=notify_url";
+        }
+        else
+        {
+            $this->return_url = "http://" . Web_Url . Web_Path . "index.php/app/pay/return_url";
+            $this->notify_url = "http://" . Web_Url . Web_Path . "index.php/app/pay/notify_url";
+        }
+
+    }
+
+    public function to($row = array(  ), $mid = 0)
+    {
+        $body = ($row["cid"] == 0 ? "会员在线购买金币" : "会员在线购买Vip");
+        $sign_arr = array( "pay_sid" => $row["sid"] + 1, "out_trade_no" => $row["dingdan"], "total_fee" => (double) $row["rmb"], "partner" => CT_Pay_ID, "return_url" => $this->return_url, "notify_url" => $this->notify_url );
+        $sign = $this->md5_sign($sign_arr, CT_Pay_Key);
+        $parameter = $sign_arr;
+        $parameter["charset"] = "utf-8";
+        $parameter["body"] = urlencode($body);
+        $parameter["sign"] = $sign;
+        $parameter["mid"] = $mid;
+        return $this->api_url . "?" . $this->arr_url($parameter);
+    }
+
+    public function md5_sign($arr, $skey)
+    {
+        $arr_filter = array(  );
+        foreach( $arr as $key => $val ) 
+        {
+            if( $key == "sign" || $val == "" ) 
+            {
+                continue;
+            }
+
+            $arr_filter[$key] = $arr[$key];
+        }
+        ksort($arr_filter);
+        reset($arr_filter);
+        $arg = "";
+        foreach( $arr_filter as $key => $val ) 
+        {
+            $arg .= $key . "=" . urlencode($val) . "&";
+        }
+        $arg = substr($arg, 0, count($arg) - 2);
+        if( get_magic_quotes_gpc() ) 
+        {
+            $arg = stripslashes($arg);
+        }
+
+        $sign = strtoupper(md5($arg . $skey));
+        return $sign;
+    }
+
+}
+
+
+

+ 238 - 0
apps/application/libraries/Xyz.php

@@ -0,0 +1,238 @@
+<?php
+if (!defined('BASEPATH')) exit('No direct script access allowed');
+class Xyz {
+    function __construct() {
+        set_time_limit(0);
+        if(substr(Mu_Ffpath,0,2) == './'){
+            $Mu_Ffpath = FCPATH.substr(Mu_Ffpath,2);
+        }else{
+            $Mu_Ffpath = Mu_Ffpath;
+        }
+        if(substr($Mu_Ffpath,-1) != '/') $Mu_Ffpath .= '/';
+        $xt = php_uname();
+        if(strpos($xt, 'Windows') !== false){
+        	$this->zmpath = 'packs/zimu.ass';
+        	$this->sypath = 'packs/logo';
+            $this->ffmpeg = $Mu_Ffpath.'ffmpeg.exe';
+            $this->ffprobe = $Mu_Ffpath.'ffprobe.exe';
+        }else{
+        	$this->zmpath = FCPATH.'packs/zimu.ass';
+        	$this->sypath = FCPATH.'packs/logo';
+            $this->ffmpeg = $Mu_Ffpath.'ffmpeg';
+            $this->ffprobe = $Mu_Ffpath.'ffprobe';
+        }
+        $ip = '/' == DIRECTORY_SEPARATOR ? $_SERVER['SERVER_ADDR'] : gethostbyname($_SERVER['SERVER_NAME']);
+        $json = get_curl('http://api.m3u8.ctcms.cn/api?ip='.$ip);
+        $arr = json_decode($json,1);
+    // if(empty($arr['msg']) || $arr['msg'] == 'no') exit('授权已到期');
+    }
+
+    public function transcode($src_path,$m3u8_path='',$jpg_path='',$gif_path='') {
+		$src_path = str_replace("//","/",str_replace("\\","/",$src_path));
+		$m3u8_path = str_replace("//","/",str_replace("\\","/",$m3u8_path));
+        $obj_path = dirname($m3u8_path);
+        mkdirss($obj_path);
+        $m3u8_time = Mu_Time;       //ts时长
+        //jpg + gif
+        if(!empty($jpg_path)) $jpg = $this->vodtojpg($src_path,$jpg_path);
+        if(!empty($gif_path)) $gif = $this->vodtogif($src_path,$gif_path);
+        //获取格式命令
+        $format = $this->format($src_path);
+        //字幕+水印
+        $watermark = $this->watermark_zm();
+        //加密
+        $aes = $this->m3u8aes($obj_path);
+        //缩放
+		$Mu_Size = defined('SIZE') ? SIZE : Mu_Size;
+        $change = !empty($Mu_Size) ? '-s '.$Mu_Size : '';
+        $Mu_Kbps = defined('KBPS') ? (int)KBPS : (int)Mu_Kbps;
+        $bit_rate = (int)$format['bit_rate'];
+        $kbps = '';
+        if($bit_rate > 0){
+        	$bit = $bit_rate / 1000;
+        	if($Mu_Kbps > 0 && $bit > $Mu_Kbps){
+                $kbps = '-b:v '.$Mu_Kbps.'k';
+        	}
+        }
+        //跳过片头
+        $pttime = Mu_PtTime == 0 ? '' : '-ss '.formattime(Mu_PtTime,1);
+        //速度
+        $prearr = array('ultrafast','superfast','veryfast','faster','fast','medium','slow','slower');
+        $Mu_Preset = defined('Mu_Preset') ? Mu_Preset : 'fast';
+        if(!in_array($Mu_Preset, $prearr) || $Mu_Preset == 'medium') $Mu_Preset = '';
+        $preset = !empty($Mu_Preset) ? '-preset:v '.$Mu_Preset : '';
+        //执行转换
+        $extarr = explode(',', $format['ext']);
+        if(in_array('mp4', $extarr) && empty($watermark)){
+            $make_command = $this->ffmpeg.' '.$pttime.' -y -i '.$src_path.' '.$change.' '.$kbps.' '.$preset.' -codec copy -vbsf h264_mp4toannexb -map 0 -f segment -segment_list '.$m3u8_path.' -segment_time '.$m3u8_time.' '.$obj_path.'/%04d.ts';
+        }elseif($format['audio']=='aac' && $format['video'] == 'h264'){
+            $make_command = $this->ffmpeg.' '.$pttime.' -y -i '.$src_path.' '.$watermark.' '.$change.' '.$kbps.' '.$preset.' -hls_time '.$m3u8_time.' '.$aes.' -hls_segment_filename '.$obj_path.'/%04d.ts -hls_list_size 0 '.$m3u8_path;
+        }else{
+            $make_command = $this->ffmpeg.' '.$pttime.' -y -i '.$src_path.' '.$watermark.' '.$change.' '.$kbps.' '.$preset.' -c:v libx264 -c:a aac -strict -2 -f hls -hls_list_size 0 -hls_time '.$m3u8_time.' '.$aes.' -hls_segment_filename '.$obj_path.'/%04d.ts '.$m3u8_path;
+        }
+        $result = exec($make_command,$arr,$log);
+        if($log == 0){
+            return defined('KBPS') ? KBPS : 'm3u8ok';
+        }else{
+            return '';
+        }
+    }
+    //获取视频格式信息
+    public function format($src_path){
+
+		$src_path = str_replace("//","/",str_replace("\\","/",$src_path));
+        $arr = array(
+            'video' => '',
+            'audio' => '',
+            'duration' => 0,
+            'width' => 0,
+            'height' => 0,
+            'dis_ratio' => '',
+            'size' => 0,
+            'bit_rate' => 0
+        );
+        if(empty($src_path)) return $arr;
+        $format_command = $this->ffprobe.' -v quiet -print_format json -show_format -show_streams '.$src_path;
+        $format = shell_exec($format_command);
+        $json = json_decode($format);
+        $audio = '';$video = '';
+        foreach($json->streams as $row){
+            if($row->codec_type=='video'){
+                $arr['video'] = $row->codec_name;
+                $arr['duration'] = $row->duration;
+                $arr['width'] = $row->width;
+                $arr['height'] = $row->height;
+                $arr['dis_ratio']= $row->display_aspect_ratio;
+            }
+            if($row->codec_type=='audio'){
+                $arr['audio'] = $row->codec_name;
+            }
+        }
+		if(empty($arr['duration'])) $arr['duration'] = $json->format->duration;
+        $arr['size'] = $json->format->size;
+        $arr['bit_rate'] = $json->format->bit_rate;
+        $arr['ext'] = $json->format->format_name;
+        return $arr;
+    }
+    //视频截图JPG
+    function vodtojpg($src_path,$obj_path){
+        if(Jpg_On==1) return 'no';
+        mkdirss(dirname($obj_path));
+		$Jpg_Size = Jpg_Size;
+        $size = !empty($Jpg_Size)?'-s '.Jpg_Size:'';
+        $filename = end(explode('/', $obj_path));
+        if(strpos($filename,'[xu]') !== false){
+            $jpg_path = str_replace('[xu]', '1', $obj_path);
+        }else{
+            $jpg_path = $obj_path;
+        }
+        $jpg_command = $this->ffmpeg.' -y -i '.$src_path.' -y -f image2 -ss '.Jpg_Time.' '.$size.' -t 0.001 '.$jpg_path;
+        $jpg = exec($jpg_command,$arr,$log);
+        //多张图
+        if(Jpg_Num > 1){
+            for($i=1;$i<Jpg_Num;$i++){
+				$n = $i+1;
+                if(strpos($obj_path, '[xu]') === false){
+                    $jpg_path2 = str_replace($filename, $n.'_'.$filename, $obj_path);
+                }else{
+                    $jpg_path2 = str_replace('[xu]', $n, $obj_path);
+                }
+                $jpg_pos = Jpg_Time*$n;//截图时间点
+                $jpg_command = $this->ffmpeg.' -y -i '.$src_path.' -y -f image2 -ss '.$jpg_pos.' '.$size.' -t 0.001 '.$jpg_path2;
+                $jpg = exec($jpg_command,$arr,$log);
+            }
+        }
+        if($log==0){
+            return 'ok';
+        }else{
+            return 'no';
+        }
+    }
+    //截取GIF
+    function vodtogif($src_path,$obj_path){
+        if(Gif_On==1) return 'no';
+        mkdirss(dirname($obj_path));
+        $gif_pos = Gif_Time;//开始位置
+        $gif_len = Gif_Len;//截取时长
+		$Gif_Size = Gif_Size;
+        $size = !empty($Gif_Size)?'-s '.Gif_Size:'';
+        if(strpos($obj_path,'[xu]') !== false){
+            $obj_path = str_replace('[xu]', '1', $obj_path);
+        }
+        $gif_command = $this->ffmpeg.' -y -ss '.$gif_pos.' -t '.$gif_len.' -i '.$src_path.' '.$size.' -f gif '.$obj_path;
+        $gif = exec($gif_command,$arr,$log);
+        if($log==0){
+            return 'ok';
+        }else{
+            return $gif_command;
+        }
+    }
+    //水印OR字幕
+    function watermark_zm(){
+    	$cmd = '';
+        //左上
+        if(Wm_On == 1){
+            $cmd = '-vf "movie='.$this->sypath.'.png[wm1];[in][wm1]overlay='.Wm_Lt.'[out]"';
+        }
+        //右上
+        if(Wm_On2 == 1){
+            $mar_arr = explode(':', Wm_Lt2);
+            $mar1 = intval($mar_arr[0]);
+            $mar2 = intval($mar_arr[1]);
+            $wm = 'overlay=main_w-overlay_w-'.$mar1.':'.$mar2;
+            if(empty($cmd)){
+                $cmd = '-vf "movie='.$this->sypath.'2.png[wm2];[in][wm2]'.$wm.'[out]"';
+            }else{
+                $cmd = str_replace('[in]', 'movie='.$this->sypath.'2.png[wm2];[in][wm2]'.$wm.'[a];[a]', $cmd);
+            }
+        }
+        //左下
+        if(Wm_On3 == 1){
+            $mar_arr = explode(':', Wm_Lt3);
+            $mar1 = intval($mar_arr[0]);
+            $mar2 = intval($mar_arr[1]);
+            $wm = 'overlay='.$mar1.':main_h-overlay_h-'.$mar2;
+            if(empty($cmd)){
+                $cmd = '-vf "movie='.$this->sypath.'3.png[wm3];[in][wm3]'.$wm.'[out]"';
+            }else{
+                $cmd = str_replace('[in]', 'movie='.$this->sypath.'3.png[wm3];[in][wm3]'.$wm.'[b];[b]', $cmd);
+            }
+        }
+        //右下
+        if(Wm_On4 == 1){
+            $mar_arr = explode(':', Wm_Lt4);
+            $mar1 = intval($mar_arr[0]);
+            $mar2 = intval($mar_arr[1]);
+            $wm = 'overlay=main_w-overlay_w-'.$mar1.':main_h-overlay_h-'.$mar2;
+            if(empty($cmd)){
+                $cmd = '-vf "movie='.$this->sypath.'4.png[wm4];[in][wm4]'.$wm.'[out]"';
+            }else{
+                $cmd = str_replace('[in]', 'movie='.$this->sypath.'4.png[wm4];[in][wm4]'.$wm.'[c];[c]', $cmd);
+            }
+        }
+        if(Wm_Zm == 1){
+            if(empty($cmd)){
+                $cmd = '-vf "ass='.$this->zmpath.'"';
+            }else{
+                $cmd = str_replace('[in]', 'ass='.$this->zmpath.'[asub];[asub]', $cmd);
+            }
+        }
+        return $cmd;
+    }
+    //m3u8加密
+    function m3u8aes($path){
+		if(Mu_On == 1) return '';
+        $src_str = '0123456789abcdefghijklmnopqrstuvwxyz';
+        $aes_key = substr(str_shuffle($src_str), 0, 16);
+        if(substr($path, -1)!='/') $path .= '/';
+        if(!is_dir($path)) mkdir($path,0777,true);
+        $fp =fopen($path.'key.key', 'w');
+        fwrite($fp, $aes_key);
+        fclose($fp);
+        $keyinfo = "key.key\n".$path."key.key";
+        $fp = fopen($path.'key_info', 'w');
+        fwrite($fp, $keyinfo);
+        fclose($fp);
+        return '-hls_key_info_file '.$path.'key_info';
+    }
+}

+ 137 - 0
apps/application/libraries/Zm.php

@@ -0,0 +1,137 @@
+<?php
+
+
+
+
+
+
+
+if( !defined('BASEPATH') ) 
+{
+    exit( 'No direct script access allowed' );
+}
+
+
+class Pays_zfb
+{
+    public function __construct()
+    {
+        $this->partner = CT_Pay_ID;
+        $this->SKey = CT_Pay_Key;
+        $this->return_url = "http://" . Web_Url . links("pay", "return_url/alipay");
+        $this->notify_url = "http://" . Web_Url . links("pay", "notify_url/alipay");
+    }
+
+    public function get_link($dingdan, $body, $total_fee)
+    {
+        $params = array( "service" => "create_direct_pay_by_user", "partner" => $this->partner, "seller_id" => $this->partner, "payment_type" => 1, "notify_url" => $this->notify_url . "/md5", "return_url" => $this->return_url . "/md5", "out_trade_no" => $dingdan, "subject" => $body, "total_fee" => $total_fee, "body" => $body, "_input_charset" => "utf-8" );
+        $params["sign"] = $this->md5_sign($params);
+        $params["sign_type"] = "MD5";
+        $url = "https://mapi.alipay.com/gateway.do?";
+        foreach( $params as $k => $v ) 
+        {
+            $url .= $k . "=" . urlencode($v) . "&";
+        }
+        $url = substr($url, 0, -1);
+        header('location:' . $url);
+        exit();
+    }
+
+    public function md5_sign($para)
+    {
+        $para_filter = array(  );
+        foreach( $para as $key => $val ) 
+        {
+            if( $key == "sign" || $key == "sign_type" || $val == "" ) 
+            {
+                continue;
+            }
+
+            $para_filter[$key] = $para[$key];
+        }
+        ksort($para_filter);
+        reset($para_filter);
+        $prestr = $this->createLinkstring($para_filter) . $this->SKey;
+        return md5($prestr);
+    }
+
+    public function is_sign()
+    {
+        $para = (isset($_POST["sign"]) ? $_POST : $_GET);
+        $sign = $para["sign"];
+        $mysgin = $this->md5_sign($para);
+        if( $mysgin == $sign ) 
+        {
+            if( $para["trade_status"] == "TRADE_SUCCESS" ) 
+            {
+                return $para["out_trade_no"];
+            }
+
+        }
+
+        return false;
+    }
+
+    public function getsignstr($params, $n = 0)
+    {
+        ksort($params);
+        $stringToBeSigned = "";
+        $i = 0;
+        foreach( $params as $k => $v ) 
+        {
+            if( !empty($v) && "@" != substr($v, 0, 1) ) 
+            {
+                $v = $this->characet($v);
+                if( $i == 0 ) 
+                {
+                    $stringToBeSigned .= (string) $k . "=" . (string) $v;
+                }
+                else
+                {
+                    $stringToBeSigned .= "&" . (string) $k . "=" . (string) $v;
+                }
+
+                $i++;
+            }
+
+        }
+        unset($k);
+        unset($v);
+        return $stringToBeSigned;
+    }
+
+    public function characet($data, $targetCharset = "utf-8")
+    {
+        if( !empty($data) ) 
+        {
+            $fileType = "utf-8";
+            if( strcasecmp($fileType, $targetCharset) != 0 ) 
+            {
+                $data = mb_convert_encoding($data, $targetCharset, $fileType);
+            }
+
+        }
+
+        return $data;
+    }
+
+    public function createLinkstring($para)
+    {
+        $arg = "";
+        while( list($key, $val) = each($para) ) 
+        {
+            $arg .= $key . "=" . $val . "&";
+        }
+        $arg = substr($arg, 0, count($arg) - 2);
+        if( get_magic_quotes_gpc() ) 
+        {
+            $arg = stripslashes($arg);
+        }
+
+        return $arg;
+    }
+
+}
+
+
+

+ 11 - 0
apps/application/libraries/index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>403 Forbidden</title>
+</head>
+<body>
+
+<p>Directory access is forbidden.</p>
+
+</body>
+</html>

+ 11 - 0
apps/application/logs/index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>403 Forbidden</title>
+</head>
+<body>
+
+<p>Directory access is forbidden.</p>
+
+</body>
+</html>

+ 50 - 0
apps/application/models/User.php

@@ -0,0 +1,50 @@
+<?php 
+
+
+if( !defined('BASEPATH') ) 
+{
+    exit( 'No direct script access allowed' );
+}
+
+
+class User extends CI_Model
+{
+    public function __construct()
+    {
+        parent::__construct();
+        if( !defined('IS_ADMIN') ) 
+        {
+            show_404();
+            exit();
+        }
+
+        return NULL;
+    }
+
+    public function Login($cid = 0)
+    {
+        $user_name = get_cookie("user_name");
+        $user_login = get_cookie("user_login");
+        $login = false;
+        if( !empty($user_name) && !empty($user_login) ) 
+        {
+            if( md5(Admin_Name . Admin_Code . "zhw") == $user_login && $user_name == Admin_Name ) 
+            {
+                $login = true;
+            }
+
+        }
+
+        if( $cid == 0 && $login == false ) 
+        {
+            header("Location:" . site_url("login/index"));
+            exit();
+        }
+
+        return $login;
+    }
+
+}
+
+
+

+ 11 - 0
apps/application/models/index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>403 Forbidden</title>
+</head>
+<body>
+
+<p>Directory access is forbidden.</p>
+
+</body>
+</html>

+ 11 - 0
apps/application/third_party/index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>403 Forbidden</title>
+</head>
+<body>
+
+<p>Directory access is forbidden.</p>
+
+</body>
+</html>

+ 11 - 0
apps/application/views/index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>403 Forbidden</title>
+</head>
+<body>
+
+<p>Directory access is forbidden.</p>
+
+</body>
+</html>

+ 6 - 0
apps/system/.htaccess

@@ -0,0 +1,6 @@
+<IfModule authz_core_module>
+	Require all denied
+</IfModule>
+<IfModule !authz_core_module>
+	Deny from all
+</IfModule>

+ 133 - 0
apps/system/core/Benchmark.php

@@ -0,0 +1,133 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 1.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * Benchmark Class
+ *
+ * This class enables you to mark points and calculate the time difference
+ * between them. Memory consumption can also be displayed.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Libraries
+ * @category	Libraries
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/libraries/benchmark.html
+ */
+class CI_Benchmark {
+
+	/**
+	 * List of all benchmark markers
+	 *
+	 * @var	array
+	 */
+	public $marker = array();
+
+	/**
+	 * Set a benchmark marker
+	 *
+	 * Multiple calls to this function can be made so that several
+	 * execution points can be timed.
+	 *
+	 * @param	string	$name	Marker name
+	 * @return	void
+	 */
+	public function mark($name)
+	{
+		$this->marker[$name] = microtime(TRUE);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Elapsed time
+	 *
+	 * Calculates the time difference between two marked points.
+	 *
+	 * If the first parameter is empty this function instead returns the
+	 * {elapsed_time} pseudo-variable. This permits the full system
+	 * execution time to be shown in a template. The output class will
+	 * swap the real value for this variable.
+	 *
+	 * @param	string	$point1		A particular marked point
+	 * @param	string	$point2		A particular marked point
+	 * @param	int	$decimals	Number of decimal places
+	 *
+	 * @return	string	Calculated elapsed time on success,
+	 *			an '{elapsed_string}' if $point1 is empty
+	 *			or an empty string if $point1 is not found.
+	 */
+	public function elapsed_time($point1 = '', $point2 = '', $decimals = 4)
+	{
+		if ($point1 === '')
+		{
+			return '{elapsed_time}';
+		}
+
+		if ( ! isset($this->marker[$point1]))
+		{
+			return '';
+		}
+
+		if ( ! isset($this->marker[$point2]))
+		{
+			$this->marker[$point2] = microtime(TRUE);
+		}
+
+		return number_format($this->marker[$point2] - $this->marker[$point1], $decimals);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Memory Usage
+	 *
+	 * Simply returns the {memory_usage} marker.
+	 *
+	 * This permits it to be put it anywhere in a template
+	 * without the memory being calculated until the end.
+	 * The output class will swap the real value for this variable.
+	 *
+	 * @return	string	'{memory_usage}'
+	 */
+	public function memory_usage()
+	{
+		return '{memory_usage}';
+	}
+
+}

+ 559 - 0
apps/system/core/CodeIgniter.php

@@ -0,0 +1,559 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 1.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * System Initialization File
+ *
+ * Loads the base classes and executes the request.
+ *
+ * @package		CodeIgniter
+ * @subpackage	CodeIgniter
+ * @category	Front-controller
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/
+ */
+
+/**
+ * CodeIgniter Version
+ *
+ * @var	string
+ *
+ */
+	const CI_VERSION = '3.1.7';
+
+/*
+ * ------------------------------------------------------
+ *  Load the framework constants
+ * ------------------------------------------------------
+ */
+	if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/constants.php'))
+	{
+		require_once(APPPATH.'config/'.ENVIRONMENT.'/constants.php');
+	}
+
+	if (file_exists(APPPATH.'config/constants.php'))
+	{
+		require_once(APPPATH.'config/constants.php');
+	}
+
+/*
+ * ------------------------------------------------------
+ *  Load the global functions
+ * ------------------------------------------------------
+ */
+	require_once(BASEPATH.'core/Common.php');
+
+
+/*
+ * ------------------------------------------------------
+ * Security procedures
+ * ------------------------------------------------------
+ */
+
+if ( ! is_php('5.4'))
+{
+	ini_set('magic_quotes_runtime', 0);
+
+	if ((bool) ini_get('register_globals'))
+	{
+		$_protected = array(
+			'_SERVER',
+			'_GET',
+			'_POST',
+			'_FILES',
+			'_REQUEST',
+			'_SESSION',
+			'_ENV',
+			'_COOKIE',
+			'GLOBALS',
+			'HTTP_RAW_POST_DATA',
+			'system_path',
+			'application_folder',
+			'view_folder',
+			'_protected',
+			'_registered'
+		);
+
+		$_registered = ini_get('variables_order');
+		foreach (array('E' => '_ENV', 'G' => '_GET', 'P' => '_POST', 'C' => '_COOKIE', 'S' => '_SERVER') as $key => $superglobal)
+		{
+			if (strpos($_registered, $key) === FALSE)
+			{
+				continue;
+			}
+
+			foreach (array_keys($$superglobal) as $var)
+			{
+				if (isset($GLOBALS[$var]) && ! in_array($var, $_protected, TRUE))
+				{
+					$GLOBALS[$var] = NULL;
+				}
+			}
+		}
+	}
+}
+
+
+/*
+ * ------------------------------------------------------
+ *  Define a custom error handler so we can log PHP errors
+ * ------------------------------------------------------
+ */
+	set_error_handler('_error_handler');
+	set_exception_handler('_exception_handler');
+	register_shutdown_function('_shutdown_handler');
+
+/*
+ * ------------------------------------------------------
+ *  Set the subclass_prefix
+ * ------------------------------------------------------
+ *
+ * Normally the "subclass_prefix" is set in the config file.
+ * The subclass prefix allows CI to know if a core class is
+ * being extended via a library in the local application
+ * "libraries" folder. Since CI allows config items to be
+ * overridden via data set in the main index.php file,
+ * before proceeding we need to know if a subclass_prefix
+ * override exists. If so, we will set this value now,
+ * before any classes are loaded
+ * Note: Since the config file data is cached it doesn't
+ * hurt to load it here.
+ */
+	if ( ! empty($assign_to_config['subclass_prefix']))
+	{
+		get_config(array('subclass_prefix' => $assign_to_config['subclass_prefix']));
+	}
+
+/*
+ * ------------------------------------------------------
+ *  Should we use a Composer autoloader?
+ * ------------------------------------------------------
+ */
+	if ($composer_autoload = config_item('composer_autoload'))
+	{
+		if ($composer_autoload === TRUE)
+		{
+			file_exists(APPPATH.'vendor/autoload.php')
+				? require_once(APPPATH.'vendor/autoload.php')
+				: log_message('error', '$config[\'composer_autoload\'] is set to TRUE but '.APPPATH.'vendor/autoload.php was not found.');
+		}
+		elseif (file_exists($composer_autoload))
+		{
+			require_once($composer_autoload);
+		}
+		else
+		{
+			log_message('error', 'Could not find the specified $config[\'composer_autoload\'] path: '.$composer_autoload);
+		}
+	}
+
+/*
+ * ------------------------------------------------------
+ *  Start the timer... tick tock tick tock...
+ * ------------------------------------------------------
+ */
+	$BM =& load_class('Benchmark', 'core');
+	$BM->mark('total_execution_time_start');
+	$BM->mark('loading_time:_base_classes_start');
+
+/*
+ * ------------------------------------------------------
+ *  Instantiate the hooks class
+ * ------------------------------------------------------
+ */
+	$EXT =& load_class('Hooks', 'core');
+
+/*
+ * ------------------------------------------------------
+ *  Is there a "pre_system" hook?
+ * ------------------------------------------------------
+ */
+	$EXT->call_hook('pre_system');
+
+/*
+ * ------------------------------------------------------
+ *  Instantiate the config class
+ * ------------------------------------------------------
+ *
+ * Note: It is important that Config is loaded first as
+ * most other classes depend on it either directly or by
+ * depending on another class that uses it.
+ *
+ */
+	$CFG =& load_class('Config', 'core');
+
+	// Do we have any manually set config items in the index.php file?
+	if (isset($assign_to_config) && is_array($assign_to_config))
+	{
+		foreach ($assign_to_config as $key => $value)
+		{
+			$CFG->set_item($key, $value);
+		}
+	}
+
+/*
+ * ------------------------------------------------------
+ * Important charset-related stuff
+ * ------------------------------------------------------
+ *
+ * Configure mbstring and/or iconv if they are enabled
+ * and set MB_ENABLED and ICONV_ENABLED constants, so
+ * that we don't repeatedly do extension_loaded() or
+ * function_exists() calls.
+ *
+ * Note: UTF-8 class depends on this. It used to be done
+ * in it's constructor, but it's _not_ class-specific.
+ *
+ */
+	$charset = strtoupper(config_item('charset'));
+	ini_set('default_charset', $charset);
+
+	if (extension_loaded('mbstring'))
+	{
+		define('MB_ENABLED', TRUE);
+		// mbstring.internal_encoding is deprecated starting with PHP 5.6
+		// and it's usage triggers E_DEPRECATED messages.
+		@ini_set('mbstring.internal_encoding', $charset);
+		// This is required for mb_convert_encoding() to strip invalid characters.
+		// That's utilized by CI_Utf8, but it's also done for consistency with iconv.
+		mb_substitute_character('none');
+	}
+	else
+	{
+		define('MB_ENABLED', FALSE);
+	}
+
+	// There's an ICONV_IMPL constant, but the PHP manual says that using
+	// iconv's predefined constants is "strongly discouraged".
+	if (extension_loaded('iconv'))
+	{
+		define('ICONV_ENABLED', TRUE);
+		// iconv.internal_encoding is deprecated starting with PHP 5.6
+		// and it's usage triggers E_DEPRECATED messages.
+		@ini_set('iconv.internal_encoding', $charset);
+	}
+	else
+	{
+		define('ICONV_ENABLED', FALSE);
+	}
+
+	if (is_php('5.6'))
+	{
+		ini_set('php.internal_encoding', $charset);
+	}
+
+/*
+ * ------------------------------------------------------
+ *  Load compatibility features
+ * ------------------------------------------------------
+ */
+
+	require_once(BASEPATH.'core/compat/mbstring.php');
+	require_once(BASEPATH.'core/compat/hash.php');
+	require_once(BASEPATH.'core/compat/password.php');
+	require_once(BASEPATH.'core/compat/standard.php');
+
+/*
+ * ------------------------------------------------------
+ *  Instantiate the UTF-8 class
+ * ------------------------------------------------------
+ */
+	$UNI =& load_class('Utf8', 'core');
+
+/*
+ * ------------------------------------------------------
+ *  Instantiate the URI class
+ * ------------------------------------------------------
+ */
+	$URI =& load_class('URI', 'core');
+
+/*
+ * ------------------------------------------------------
+ *  Instantiate the routing class and set the routing
+ * ------------------------------------------------------
+ */
+	$RTR =& load_class('Router', 'core', isset($routing) ? $routing : NULL);
+
+/*
+ * ------------------------------------------------------
+ *  Instantiate the output class
+ * ------------------------------------------------------
+ */
+	$OUT =& load_class('Output', 'core');
+
+/*
+ * ------------------------------------------------------
+ *	Is there a valid cache file? If so, we're done...
+ * ------------------------------------------------------
+ */
+	if ($EXT->call_hook('cache_override') === FALSE && $OUT->_display_cache($CFG, $URI) === TRUE)
+	{
+		exit;
+	}
+
+/*
+ * -----------------------------------------------------
+ * Load the security class for xss and csrf support
+ * -----------------------------------------------------
+ */
+	$SEC =& load_class('Security', 'core');
+
+/*
+ * ------------------------------------------------------
+ *  Load the Input class and sanitize globals
+ * ------------------------------------------------------
+ */
+	$IN	=& load_class('Input', 'core');
+
+/*
+ * ------------------------------------------------------
+ *  Load the Language class
+ * ------------------------------------------------------
+ */
+	$LANG =& load_class('Lang', 'core');
+
+/*
+ * ------------------------------------------------------
+ *  Load the app controller and local controller
+ * ------------------------------------------------------
+ *
+ */
+	// Load the base controller class
+	require_once BASEPATH.'core/Controller.php';
+
+	/**
+	 * Reference to the CI_Controller method.
+	 *
+	 * Returns current CI instance object
+	 *
+	 * @return CI_Controller
+	 */
+	function &get_instance()
+	{
+		return CI_Controller::get_instance();
+	}
+
+	if (file_exists(APPPATH.'core/'.$CFG->config['subclass_prefix'].'Controller.php'))
+	{
+		require_once APPPATH.'core/'.$CFG->config['subclass_prefix'].'Controller.php';
+	}
+
+	// Set a mark point for benchmarking
+	$BM->mark('loading_time:_base_classes_end');
+
+/*
+ * ------------------------------------------------------
+ *  Sanity checks
+ * ------------------------------------------------------
+ *
+ *  The Router class has already validated the request,
+ *  leaving us with 3 options here:
+ *
+ *	1) an empty class name, if we reached the default
+ *	   controller, but it didn't exist;
+ *	2) a query string which doesn't go through a
+ *	   file_exists() check
+ *	3) a regular request for a non-existing page
+ *
+ *  We handle all of these as a 404 error.
+ *
+ *  Furthermore, none of the methods in the app controller
+ *  or the loader class can be called via the URI, nor can
+ *  controller methods that begin with an underscore.
+ */
+
+	$e404 = FALSE;
+	$class = ucfirst($RTR->class);
+	$method = $RTR->method;
+
+	if (empty($class) OR ! file_exists(APPPATH.'controllers/'.$RTR->directory.$class.'.php'))
+	{
+		$e404 = TRUE;
+	}
+	else
+	{
+		require_once(APPPATH.'controllers/'.$RTR->directory.$class.'.php');
+
+		if ( ! class_exists($class, FALSE) OR $method[0] === '_' OR method_exists('CI_Controller', $method))
+		{
+			$e404 = TRUE;
+		}
+		elseif (method_exists($class, '_remap'))
+		{
+			$params = array($method, array_slice($URI->rsegments, 2));
+			$method = '_remap';
+		}
+		elseif ( ! method_exists($class, $method))
+		{
+			$e404 = TRUE;
+		}
+		/**
+		 * DO NOT CHANGE THIS, NOTHING ELSE WORKS!
+		 *
+		 * - method_exists() returns true for non-public methods, which passes the previous elseif
+		 * - is_callable() returns false for PHP 4-style constructors, even if there's a __construct()
+		 * - method_exists($class, '__construct') won't work because CI_Controller::__construct() is inherited
+		 * - People will only complain if this doesn't work, even though it is documented that it shouldn't.
+		 *
+		 * ReflectionMethod::isConstructor() is the ONLY reliable check,
+		 * knowing which method will be executed as a constructor.
+		 */
+		elseif ( ! is_callable(array($class, $method)))
+		{
+			$reflection = new ReflectionMethod($class, $method);
+			if ( ! $reflection->isPublic() OR $reflection->isConstructor())
+			{
+				$e404 = TRUE;
+			}
+		}
+	}
+
+	if ($e404)
+	{
+		if ( ! empty($RTR->routes['404_override']))
+		{
+			if (sscanf($RTR->routes['404_override'], '%[^/]/%s', $error_class, $error_method) !== 2)
+			{
+				$error_method = 'index';
+			}
+
+			$error_class = ucfirst($error_class);
+
+			if ( ! class_exists($error_class, FALSE))
+			{
+				if (file_exists(APPPATH.'controllers/'.$RTR->directory.$error_class.'.php'))
+				{
+					require_once(APPPATH.'controllers/'.$RTR->directory.$error_class.'.php');
+					$e404 = ! class_exists($error_class, FALSE);
+				}
+				// Were we in a directory? If so, check for a global override
+				elseif ( ! empty($RTR->directory) && file_exists(APPPATH.'controllers/'.$error_class.'.php'))
+				{
+					require_once(APPPATH.'controllers/'.$error_class.'.php');
+					if (($e404 = ! class_exists($error_class, FALSE)) === FALSE)
+					{
+						$RTR->directory = '';
+					}
+				}
+			}
+			else
+			{
+				$e404 = FALSE;
+			}
+		}
+
+		// Did we reset the $e404 flag? If so, set the rsegments, starting from index 1
+		if ( ! $e404)
+		{
+			$class = $error_class;
+			$method = $error_method;
+
+			$URI->rsegments = array(
+				1 => $class,
+				2 => $method
+			);
+		}
+		else
+		{
+			show_404($RTR->directory.$class.'/'.$method);
+		}
+	}
+
+	if ($method !== '_remap')
+	{
+		$params = array_slice($URI->rsegments, 2);
+	}
+
+/*
+ * ------------------------------------------------------
+ *  Is there a "pre_controller" hook?
+ * ------------------------------------------------------
+ */
+	$EXT->call_hook('pre_controller');
+
+/*
+ * ------------------------------------------------------
+ *  Instantiate the requested controller
+ * ------------------------------------------------------
+ */
+	// Mark a start point so we can benchmark the controller
+	$BM->mark('controller_execution_time_( '.$class.' / '.$method.' )_start');
+
+	$CI = new $class();
+
+/*
+ * ------------------------------------------------------
+ *  Is there a "post_controller_constructor" hook?
+ * ------------------------------------------------------
+ */
+	$EXT->call_hook('post_controller_constructor');
+
+/*
+ * ------------------------------------------------------
+ *  Call the requested method
+ * ------------------------------------------------------
+ */
+	call_user_func_array(array(&$CI, $method), $params);
+
+	// Mark a benchmark end point
+	$BM->mark('controller_execution_time_( '.$class.' / '.$method.' )_end');
+
+/*
+ * ------------------------------------------------------
+ *  Is there a "post_controller" hook?
+ * ------------------------------------------------------
+ */
+	$EXT->call_hook('post_controller');
+
+/*
+ * ------------------------------------------------------
+ *  Send the final rendered output to the browser
+ * ------------------------------------------------------
+ */
+	if ($EXT->call_hook('display_override') === FALSE)
+	{
+		$OUT->_display();
+	}
+
+/*
+ * ------------------------------------------------------
+ *  Is there a "post_system" hook?
+ * ------------------------------------------------------
+ */
+	$EXT->call_hook('post_system');

+ 849 - 0
apps/system/core/Common.php

@@ -0,0 +1,849 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 1.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * Common Functions
+ *
+ * Loads the base classes and executes the request.
+ *
+ * @package		CodeIgniter
+ * @subpackage	CodeIgniter
+ * @category	Common Functions
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/
+ */
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('is_php'))
+{
+	/**
+	 * Determines if the current version of PHP is equal to or greater than the supplied value
+	 *
+	 * @param	string
+	 * @return	bool	TRUE if the current version is $version or higher
+	 */
+	function is_php($version)
+	{
+		static $_is_php;
+		$version = (string) $version;
+
+		if ( ! isset($_is_php[$version]))
+		{
+			$_is_php[$version] = version_compare(PHP_VERSION, $version, '>=');
+		}
+
+		return $_is_php[$version];
+	}
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('is_really_writable'))
+{
+	/**
+	 * Tests for file writability
+	 *
+	 * is_writable() returns TRUE on Windows servers when you really can't write to
+	 * the file, based on the read-only attribute. is_writable() is also unreliable
+	 * on Unix servers if safe_mode is on.
+	 *
+	 * @link	https://bugs.php.net/bug.php?id=54709
+	 * @param	string
+	 * @return	bool
+	 */
+	function is_really_writable($file)
+	{
+		// If we're on a Unix server with safe_mode off we call is_writable
+		if (DIRECTORY_SEPARATOR === '/' && (is_php('5.4') OR ! ini_get('safe_mode')))
+		{
+			return is_writable($file);
+		}
+
+		/* For Windows servers and safe_mode "on" installations we'll actually
+		 * write a file then read it. Bah...
+		 */
+		if (is_dir($file))
+		{
+			$file = rtrim($file, '/').'/'.md5(mt_rand());
+			if (($fp = @fopen($file, 'ab')) === FALSE)
+			{
+				return FALSE;
+			}
+
+			fclose($fp);
+			@chmod($file, 0777);
+			@unlink($file);
+			return TRUE;
+		}
+		elseif ( ! is_file($file) OR ($fp = @fopen($file, 'ab')) === FALSE)
+		{
+			return FALSE;
+		}
+
+		fclose($fp);
+		return TRUE;
+	}
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('load_class'))
+{
+	/**
+	 * Class registry
+	 *
+	 * This function acts as a singleton. If the requested class does not
+	 * exist it is instantiated and set to a static variable. If it has
+	 * previously been instantiated the variable is returned.
+	 *
+	 * @param	string	the class name being requested
+	 * @param	string	the directory where the class should be found
+	 * @param	mixed	an optional argument to pass to the class constructor
+	 * @return	object
+	 */
+	function &load_class($class, $directory = 'libraries', $param = NULL)
+	{
+		static $_classes = array();
+
+		// Does the class exist? If so, we're done...
+		if (isset($_classes[$class]))
+		{
+			return $_classes[$class];
+		}
+
+		$name = FALSE;
+
+		// Look for the class first in the local application/libraries folder
+		// then in the native system/libraries folder
+		foreach (array(APPPATH, BASEPATH) as $path)
+		{
+			if (file_exists($path.$directory.'/'.$class.'.php'))
+			{
+				$name = 'CI_'.$class;
+
+				if (class_exists($name, FALSE) === FALSE)
+				{
+					require_once($path.$directory.'/'.$class.'.php');
+				}
+
+				break;
+			}
+		}
+
+		// Is the request a class extension? If so we load it too
+		if (file_exists(APPPATH.$directory.'/'.config_item('subclass_prefix').$class.'.php'))
+		{
+			$name = config_item('subclass_prefix').$class;
+
+			if (class_exists($name, FALSE) === FALSE)
+			{
+				require_once(APPPATH.$directory.'/'.$name.'.php');
+			}
+		}
+
+		// Did we find the class?
+		if ($name === FALSE)
+		{
+			// Note: We use exit() rather than show_error() in order to avoid a
+			// self-referencing loop with the Exceptions class
+			set_status_header(503);
+			echo 'Unable to locate the specified class: '.$class.'.php';
+			exit(5); // EXIT_UNK_CLASS
+		}
+
+		// Keep track of what we just loaded
+		is_loaded($class);
+
+		$_classes[$class] = isset($param)
+			? new $name($param)
+			: new $name();
+		return $_classes[$class];
+	}
+}
+
+// --------------------------------------------------------------------
+
+if ( ! function_exists('is_loaded'))
+{
+	/**
+	 * Keeps track of which libraries have been loaded. This function is
+	 * called by the load_class() function above
+	 *
+	 * @param	string
+	 * @return	array
+	 */
+	function &is_loaded($class = '')
+	{
+		static $_is_loaded = array();
+
+		if ($class !== '')
+		{
+			$_is_loaded[strtolower($class)] = $class;
+		}
+
+		return $_is_loaded;
+	}
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('get_config'))
+{
+	/**
+	 * Loads the main config.php file
+	 *
+	 * This function lets us grab the config file even if the Config class
+	 * hasn't been instantiated yet
+	 *
+	 * @param	array
+	 * @return	array
+	 */
+	function &get_config(Array $replace = array())
+	{
+		static $config;
+
+		if (empty($config))
+		{
+			$file_path = APPPATH.'config/config.php';
+			$found = FALSE;
+			if (file_exists($file_path))
+			{
+				$found = TRUE;
+				require($file_path);
+			}
+
+			// Is the config file in the environment folder?
+			if (file_exists($file_path = APPPATH.'config/'.ENVIRONMENT.'/config.php'))
+			{
+				require($file_path);
+			}
+			elseif ( ! $found)
+			{
+				set_status_header(503);
+				echo 'The configuration file does not exist.';
+				exit(3); // EXIT_CONFIG
+			}
+
+			// Does the $config array exist in the file?
+			if ( ! isset($config) OR ! is_array($config))
+			{
+				set_status_header(503);
+				echo 'Your config file does not appear to be formatted correctly.';
+				exit(3); // EXIT_CONFIG
+			}
+		}
+
+		// Are any values being dynamically added or replaced?
+		foreach ($replace as $key => $val)
+		{
+			$config[$key] = $val;
+		}
+
+		return $config;
+	}
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('config_item'))
+{
+	/**
+	 * Returns the specified config item
+	 *
+	 * @param	string
+	 * @return	mixed
+	 */
+	function config_item($item)
+	{
+		static $_config;
+
+		if (empty($_config))
+		{
+			// references cannot be directly assigned to static variables, so we use an array
+			$_config[0] =& get_config();
+		}
+
+		return isset($_config[0][$item]) ? $_config[0][$item] : NULL;
+	}
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('get_mimes'))
+{
+	/**
+	 * Returns the MIME types array from config/mimes.php
+	 *
+	 * @return	array
+	 */
+	function &get_mimes()
+	{
+		static $_mimes;
+
+		if (empty($_mimes))
+		{
+			$_mimes = file_exists(APPPATH.'config/mimes.php')
+				? include(APPPATH.'config/mimes.php')
+				: array();
+
+			if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/mimes.php'))
+			{
+				$_mimes = array_merge($_mimes, include(APPPATH.'config/'.ENVIRONMENT.'/mimes.php'));
+			}
+		}
+
+		return $_mimes;
+	}
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('is_https'))
+{
+	/**
+	 * Is HTTPS?
+	 *
+	 * Determines if the application is accessed via an encrypted
+	 * (HTTPS) connection.
+	 *
+	 * @return	bool
+	 */
+	function is_https()
+	{
+		if ( ! empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off')
+		{
+			return TRUE;
+		}
+		elseif (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https')
+		{
+			return TRUE;
+		}
+		elseif ( ! empty($_SERVER['HTTP_FRONT_END_HTTPS']) && strtolower($_SERVER['HTTP_FRONT_END_HTTPS']) !== 'off')
+		{
+			return TRUE;
+		}
+
+		return FALSE;
+	}
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('is_cli'))
+{
+
+	/**
+	 * Is CLI?
+	 *
+	 * Test to see if a request was made from the command line.
+	 *
+	 * @return 	bool
+	 */
+	function is_cli()
+	{
+		return (PHP_SAPI === 'cli' OR defined('STDIN'));
+	}
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('show_error'))
+{
+	/**
+	 * Error Handler
+	 *
+	 * This function lets us invoke the exception class and
+	 * display errors using the standard error template located
+	 * in application/views/errors/error_general.php
+	 * This function will send the error page directly to the
+	 * browser and exit.
+	 *
+	 * @param	string
+	 * @param	int
+	 * @param	string
+	 * @return	void
+	 */
+	function show_error($message, $status_code = 500, $heading = 'An Error Was Encountered')
+	{
+		$status_code = abs($status_code);
+		if ($status_code < 100)
+		{
+			$exit_status = $status_code + 9; // 9 is EXIT__AUTO_MIN
+			$status_code = 500;
+		}
+		else
+		{
+			$exit_status = 1; // EXIT_ERROR
+		}
+
+		$_error =& load_class('Exceptions', 'core');
+		echo $_error->show_error($heading, $message, 'error_general', $status_code);
+		exit($exit_status);
+	}
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('show_404'))
+{
+	/**
+	 * 404 Page Handler
+	 *
+	 * This function is similar to the show_error() function above
+	 * However, instead of the standard error template it displays
+	 * 404 errors.
+	 *
+	 * @param	string
+	 * @param	bool
+	 * @return	void
+	 */
+	function show_404($page = '', $log_error = TRUE)
+	{
+		$_error =& load_class('Exceptions', 'core');
+		$_error->show_404($page, $log_error);
+		exit(4); // EXIT_UNKNOWN_FILE
+	}
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('log_message'))
+{
+	/**
+	 * Error Logging Interface
+	 *
+	 * We use this as a simple mechanism to access the logging
+	 * class and send messages to be logged.
+	 *
+	 * @param	string	the error level: 'error', 'debug' or 'info'
+	 * @param	string	the error message
+	 * @return	void
+	 */
+	function log_message($level, $message)
+	{
+		static $_log;
+
+		if ($_log === NULL)
+		{
+			// references cannot be directly assigned to static variables, so we use an array
+			$_log[0] =& load_class('Log', 'core');
+		}
+
+		$_log[0]->write_log($level, $message);
+	}
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('set_status_header'))
+{
+	/**
+	 * Set HTTP Status Header
+	 *
+	 * @param	int	the status code
+	 * @param	string
+	 * @return	void
+	 */
+	function set_status_header($code = 200, $text = '')
+	{
+		if (is_cli())
+		{
+			return;
+		}
+
+		if (empty($code) OR ! is_numeric($code))
+		{
+			show_error('Status codes must be numeric', 500);
+		}
+
+		if (empty($text))
+		{
+			is_int($code) OR $code = (int) $code;
+			$stati = array(
+				100	=> 'Continue',
+				101	=> 'Switching Protocols',
+
+				200	=> 'OK',
+				201	=> 'Created',
+				202	=> 'Accepted',
+				203	=> 'Non-Authoritative Information',
+				204	=> 'No Content',
+				205	=> 'Reset Content',
+				206	=> 'Partial Content',
+
+				300	=> 'Multiple Choices',
+				301	=> 'Moved Permanently',
+				302	=> 'Found',
+				303	=> 'See Other',
+				304	=> 'Not Modified',
+				305	=> 'Use Proxy',
+				307	=> 'Temporary Redirect',
+
+				400	=> 'Bad Request',
+				401	=> 'Unauthorized',
+				402	=> 'Payment Required',
+				403	=> 'Forbidden',
+				404	=> 'Not Found',
+				405	=> 'Method Not Allowed',
+				406	=> 'Not Acceptable',
+				407	=> 'Proxy Authentication Required',
+				408	=> 'Request Timeout',
+				409	=> 'Conflict',
+				410	=> 'Gone',
+				411	=> 'Length Required',
+				412	=> 'Precondition Failed',
+				413	=> 'Request Entity Too Large',
+				414	=> 'Request-URI Too Long',
+				415	=> 'Unsupported Media Type',
+				416	=> 'Requested Range Not Satisfiable',
+				417	=> 'Expectation Failed',
+				422	=> 'Unprocessable Entity',
+				426	=> 'Upgrade Required',
+				428	=> 'Precondition Required',
+				429	=> 'Too Many Requests',
+				431	=> 'Request Header Fields Too Large',
+
+				500	=> 'Internal Server Error',
+				501	=> 'Not Implemented',
+				502	=> 'Bad Gateway',
+				503	=> 'Service Unavailable',
+				504	=> 'Gateway Timeout',
+				505	=> 'HTTP Version Not Supported',
+				511	=> 'Network Authentication Required',
+			);
+
+			if (isset($stati[$code]))
+			{
+				$text = $stati[$code];
+			}
+			else
+			{
+				show_error('No status text available. Please check your status code number or supply your own message text.', 500);
+			}
+		}
+
+		if (strpos(PHP_SAPI, 'cgi') === 0)
+		{
+			header('Status: '.$code.' '.$text, TRUE);
+			return;
+		}
+
+		$server_protocol = (isset($_SERVER['SERVER_PROTOCOL']) && in_array($_SERVER['SERVER_PROTOCOL'], array('HTTP/1.0', 'HTTP/1.1', 'HTTP/2'), TRUE))
+			? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1';
+		header($server_protocol.' '.$code.' '.$text, TRUE, $code);
+	}
+}
+
+// --------------------------------------------------------------------
+
+if ( ! function_exists('_error_handler'))
+{
+	/**
+	 * Error Handler
+	 *
+	 * This is the custom error handler that is declared at the (relative)
+	 * top of CodeIgniter.php. The main reason we use this is to permit
+	 * PHP errors to be logged in our own log files since the user may
+	 * not have access to server logs. Since this function effectively
+	 * intercepts PHP errors, however, we also need to display errors
+	 * based on the current error_reporting level.
+	 * We do that with the use of a PHP error template.
+	 *
+	 * @param	int	$severity
+	 * @param	string	$message
+	 * @param	string	$filepath
+	 * @param	int	$line
+	 * @return	void
+	 */
+	function _error_handler($severity, $message, $filepath, $line)
+	{
+		$is_error = (((E_ERROR | E_PARSE | E_COMPILE_ERROR | E_CORE_ERROR | E_USER_ERROR) & $severity) === $severity);
+
+		// When an error occurred, set the status header to '500 Internal Server Error'
+		// to indicate to the client something went wrong.
+		// This can't be done within the $_error->show_php_error method because
+		// it is only called when the display_errors flag is set (which isn't usually
+		// the case in a production environment) or when errors are ignored because
+		// they are above the error_reporting threshold.
+		if ($is_error)
+		{
+			set_status_header(500);
+		}
+
+		// Should we ignore the error? We'll get the current error_reporting
+		// level and add its bits with the severity bits to find out.
+		if (($severity & error_reporting()) !== $severity)
+		{
+			return;
+		}
+
+		$_error =& load_class('Exceptions', 'core');
+		$_error->log_exception($severity, $message, $filepath, $line);
+
+		// Should we display the error?
+		if (str_ireplace(array('off', 'none', 'no', 'false', 'null'), '', ini_get('display_errors')))
+		{
+			$_error->show_php_error($severity, $message, $filepath, $line);
+		}
+
+		// If the error is fatal, the execution of the script should be stopped because
+		// errors can't be recovered from. Halting the script conforms with PHP's
+		// default error handling. See http://www.php.net/manual/en/errorfunc.constants.php
+		if ($is_error)
+		{
+			exit(1); // EXIT_ERROR
+		}
+	}
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('_exception_handler'))
+{
+	/**
+	 * Exception Handler
+	 *
+	 * Sends uncaught exceptions to the logger and displays them
+	 * only if display_errors is On so that they don't show up in
+	 * production environments.
+	 *
+	 * @param	Exception	$exception
+	 * @return	void
+	 */
+	function _exception_handler($exception)
+	{
+		$_error =& load_class('Exceptions', 'core');
+		$_error->log_exception('error', 'Exception: '.$exception->getMessage(), $exception->getFile(), $exception->getLine());
+
+		is_cli() OR set_status_header(500);
+		// Should we display the error?
+		if (str_ireplace(array('off', 'none', 'no', 'false', 'null'), '', ini_get('display_errors')))
+		{
+			$_error->show_exception($exception);
+		}
+
+		exit(1); // EXIT_ERROR
+	}
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('_shutdown_handler'))
+{
+	/**
+	 * Shutdown Handler
+	 *
+	 * This is the shutdown handler that is declared at the top
+	 * of CodeIgniter.php. The main reason we use this is to simulate
+	 * a complete custom exception handler.
+	 *
+	 * E_STRICT is purposively neglected because such events may have
+	 * been caught. Duplication or none? None is preferred for now.
+	 *
+	 * @link	http://insomanic.me.uk/post/229851073/php-trick-catching-fatal-errors-e-error-with-a
+	 * @return	void
+	 */
+	function _shutdown_handler()
+	{
+		$last_error = error_get_last();
+		if (isset($last_error) &&
+			($last_error['type'] & (E_ERROR | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING)))
+		{
+			_error_handler($last_error['type'], $last_error['message'], $last_error['file'], $last_error['line']);
+		}
+	}
+}
+
+// --------------------------------------------------------------------
+
+if ( ! function_exists('remove_invisible_characters'))
+{
+	/**
+	 * Remove Invisible Characters
+	 *
+	 * This prevents sandwiching null characters
+	 * between ascii characters, like Java\0script.
+	 *
+	 * @param	string
+	 * @param	bool
+	 * @return	string
+	 */
+	function remove_invisible_characters($str, $url_encoded = TRUE)
+	{
+		$non_displayables = array();
+
+		// every control character except newline (dec 10),
+		// carriage return (dec 13) and horizontal tab (dec 09)
+		if ($url_encoded)
+		{
+			$non_displayables[] = '/%0[0-8bcef]/i';	// url encoded 00-08, 11, 12, 14, 15
+			$non_displayables[] = '/%1[0-9a-f]/i';	// url encoded 16-31
+			$non_displayables[] = '/%7f/i';	// url encoded 127
+		}
+
+		$non_displayables[] = '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S';	// 00-08, 11, 12, 14-31, 127
+
+		do
+		{
+			$str = preg_replace($non_displayables, '', $str, -1, $count);
+		}
+		while ($count);
+
+		return $str;
+	}
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('html_escape'))
+{
+	/**
+	 * Returns HTML escaped variable.
+	 *
+	 * @param	mixed	$var		The input string or array of strings to be escaped.
+	 * @param	bool	$double_encode	$double_encode set to FALSE prevents escaping twice.
+	 * @return	mixed			The escaped string or array of strings as a result.
+	 */
+	function html_escape($var, $double_encode = TRUE)
+	{
+		if (empty($var))
+		{
+			return $var;
+		}
+
+		if (is_array($var))
+		{
+			foreach (array_keys($var) as $key)
+			{
+				$var[$key] = html_escape($var[$key], $double_encode);
+			}
+
+			return $var;
+		}
+
+		return htmlspecialchars($var, ENT_QUOTES, config_item('charset'), $double_encode);
+	}
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('_stringify_attributes'))
+{
+	/**
+	 * Stringify attributes for use in HTML tags.
+	 *
+	 * Helper function used to convert a string, array, or object
+	 * of attributes to a string.
+	 *
+	 * @param	mixed	string, array, object
+	 * @param	bool
+	 * @return	string
+	 */
+	function _stringify_attributes($attributes, $js = FALSE)
+	{
+		$atts = NULL;
+
+		if (empty($attributes))
+		{
+			return $atts;
+		}
+
+		if (is_string($attributes))
+		{
+			return ' '.$attributes;
+		}
+
+		$attributes = (array) $attributes;
+
+		foreach ($attributes as $key => $val)
+		{
+			$atts .= ($js) ? $key.'='.$val.',' : ' '.$key.'="'.$val.'"';
+		}
+
+		return rtrim($atts, ',');
+	}
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('function_usable'))
+{
+	/**
+	 * Function usable
+	 *
+	 * Executes a function_exists() check, and if the Suhosin PHP
+	 * extension is loaded - checks whether the function that is
+	 * checked might be disabled in there as well.
+	 *
+	 * This is useful as function_exists() will return FALSE for
+	 * functions disabled via the *disable_functions* php.ini
+	 * setting, but not for *suhosin.executor.func.blacklist* and
+	 * *suhosin.executor.disable_eval*. These settings will just
+	 * terminate script execution if a disabled function is executed.
+	 *
+	 * The above described behavior turned out to be a bug in Suhosin,
+	 * but even though a fix was committed for 0.9.34 on 2012-02-12,
+	 * that version is yet to be released. This function will therefore
+	 * be just temporary, but would probably be kept for a few years.
+	 *
+	 * @link	http://www.hardened-php.net/suhosin/
+	 * @param	string	$function_name	Function to check for
+	 * @return	bool	TRUE if the function exists and is safe to call,
+	 *			FALSE otherwise.
+	 */
+	function function_usable($function_name)
+	{
+		static $_suhosin_func_blacklist;
+
+		if (function_exists($function_name))
+		{
+			if ( ! isset($_suhosin_func_blacklist))
+			{
+				$_suhosin_func_blacklist = extension_loaded('suhosin')
+					? explode(',', trim(ini_get('suhosin.executor.func.blacklist')))
+					: array();
+			}
+
+			return ! in_array($function_name, $_suhosin_func_blacklist, TRUE);
+		}
+
+		return FALSE;
+	}
+}

+ 379 - 0
apps/system/core/Config.php

@@ -0,0 +1,379 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 1.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * Config Class
+ *
+ * This class contains functions that enable config files to be managed
+ *
+ * @package		CodeIgniter
+ * @subpackage	Libraries
+ * @category	Libraries
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/libraries/config.html
+ */
+class CI_Config {
+
+	/**
+	 * List of all loaded config values
+	 *
+	 * @var	array
+	 */
+	public $config = array();
+
+	/**
+	 * List of all loaded config files
+	 *
+	 * @var	array
+	 */
+	public $is_loaded =	array();
+
+	/**
+	 * List of paths to search when trying to load a config file.
+	 *
+	 * @used-by	CI_Loader
+	 * @var		array
+	 */
+	public $_config_paths =	array(APPPATH);
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * Sets the $config data from the primary config.php file as a class variable.
+	 *
+	 * @return	void
+	 */
+	public function __construct()
+	{
+		$this->config =& get_config();
+
+		// Set the base_url automatically if none was provided
+		if (empty($this->config['base_url']))
+		{
+			if (isset($_SERVER['SERVER_ADDR']))
+			{
+				if (strpos($_SERVER['SERVER_ADDR'], ':') !== FALSE)
+				{
+					$server_addr = '['.$_SERVER['SERVER_ADDR'].']';
+				}
+				else
+				{
+					$server_addr = $_SERVER['SERVER_ADDR'];
+				}
+
+				$base_url = (is_https() ? 'https' : 'http').'://'.$server_addr
+					.substr($_SERVER['SCRIPT_NAME'], 0, strpos($_SERVER['SCRIPT_NAME'], basename($_SERVER['SCRIPT_FILENAME'])));
+			}
+			else
+			{
+				$base_url = 'http://localhost/';
+			}
+
+			$this->set_item('base_url', $base_url);
+		}
+
+		log_message('info', 'Config Class Initialized');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Load Config File
+	 *
+	 * @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 load($file = '', $use_sections = FALSE, $fail_gracefully = FALSE)
+	{
+		$file = ($file === '') ? 'config' : str_replace('.php', '', $file);
+		$loaded = FALSE;
+
+		foreach ($this->_config_paths as $path)
+		{
+			foreach (array($file, ENVIRONMENT.DIRECTORY_SEPARATOR.$file) as $location)
+			{
+				$file_path = $path.'config/'.$location.'.php';
+				if (in_array($file_path, $this->is_loaded, TRUE))
+				{
+					return TRUE;
+				}
+
+				if ( ! file_exists($file_path))
+				{
+					continue;
+				}
+
+				include($file_path);
+
+				if ( ! isset($config) OR ! is_array($config))
+				{
+					if ($fail_gracefully === TRUE)
+					{
+						return FALSE;
+					}
+
+					show_error('Your '.$file_path.' file does not appear to contain a valid configuration array.');
+				}
+
+				if ($use_sections === TRUE)
+				{
+					$this->config[$file] = isset($this->config[$file])
+						? array_merge($this->config[$file], $config)
+						: $config;
+				}
+				else
+				{
+					$this->config = array_merge($this->config, $config);
+				}
+
+				$this->is_loaded[] = $file_path;
+				$config = NULL;
+				$loaded = TRUE;
+				log_message('debug', 'Config file loaded: '.$file_path);
+			}
+		}
+
+		if ($loaded === TRUE)
+		{
+			return TRUE;
+		}
+		elseif ($fail_gracefully === TRUE)
+		{
+			return FALSE;
+		}
+
+		show_error('The configuration file '.$file.'.php does not exist.');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Fetch a config file item
+	 *
+	 * @param	string	$item	Config item name
+	 * @param	string	$index	Index name
+	 * @return	string|null	The configuration item or NULL if the item doesn't exist
+	 */
+	public function item($item, $index = '')
+	{
+		if ($index == '')
+		{
+			return isset($this->config[$item]) ? $this->config[$item] : NULL;
+		}
+
+		return isset($this->config[$index], $this->config[$index][$item]) ? $this->config[$index][$item] : NULL;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Fetch a config file item with slash appended (if not empty)
+	 *
+	 * @param	string		$item	Config item name
+	 * @return	string|null	The configuration item or NULL if the item doesn't exist
+	 */
+	public function slash_item($item)
+	{
+		if ( ! isset($this->config[$item]))
+		{
+			return NULL;
+		}
+		elseif (trim($this->config[$item]) === '')
+		{
+			return '';
+		}
+
+		return rtrim($this->config[$item], '/').'/';
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Site URL
+	 *
+	 * Returns base_url . index_page [. uri_string]
+	 *
+	 * @uses	CI_Config::_uri_string()
+	 *
+	 * @param	string|string[]	$uri	URI string or an array of segments
+	 * @param	string	$protocol
+	 * @return	string
+	 */
+	public function site_url($uri = '', $protocol = NULL)
+	{
+		$base_url = $this->slash_item('base_url');
+
+		if (isset($protocol))
+		{
+			// For protocol-relative links
+			if ($protocol === '')
+			{
+				$base_url = substr($base_url, strpos($base_url, '//'));
+			}
+			else
+			{
+				$base_url = $protocol.substr($base_url, strpos($base_url, '://'));
+			}
+		}
+
+		if (empty($uri))
+		{
+			return $base_url.$this->item('index_page');
+		}
+
+		$uri = $this->_uri_string($uri);
+
+		if ($this->item('enable_query_strings') === FALSE)
+		{
+			$suffix = isset($this->config['url_suffix']) ? $this->config['url_suffix'] : '';
+
+			if ($suffix !== '')
+			{
+				if (($offset = strpos($uri, '?')) !== FALSE)
+				{
+					$uri = substr($uri, 0, $offset).$suffix.substr($uri, $offset);
+				}
+				else
+				{
+					$uri .= $suffix;
+				}
+			}
+
+			return $base_url.$this->slash_item('index_page').$uri;
+		}
+		elseif (strpos($uri, '?') === FALSE)
+		{
+			$uri = '?'.$uri;
+		}
+
+		return $base_url.$this->item('index_page').$uri;
+	}
+
+	// -------------------------------------------------------------
+
+	/**
+	 * Base URL
+	 *
+	 * Returns base_url [. uri_string]
+	 *
+	 * @uses	CI_Config::_uri_string()
+	 *
+	 * @param	string|string[]	$uri	URI string or an array of segments
+	 * @param	string	$protocol
+	 * @return	string
+	 */
+	public function base_url($uri = '', $protocol = NULL)
+	{
+		$base_url = $this->slash_item('base_url');
+
+		if (isset($protocol))
+		{
+			// For protocol-relative links
+			if ($protocol === '')
+			{
+				$base_url = substr($base_url, strpos($base_url, '//'));
+			}
+			else
+			{
+				$base_url = $protocol.substr($base_url, strpos($base_url, '://'));
+			}
+		}
+
+		return $base_url.$this->_uri_string($uri);
+	}
+
+	// -------------------------------------------------------------
+
+	/**
+	 * Build URI string
+	 *
+	 * @used-by	CI_Config::site_url()
+	 * @used-by	CI_Config::base_url()
+	 *
+	 * @param	string|string[]	$uri	URI string or an array of segments
+	 * @return	string
+	 */
+	protected function _uri_string($uri)
+	{
+		if ($this->item('enable_query_strings') === FALSE)
+		{
+			is_array($uri) && $uri = implode('/', $uri);
+			return ltrim($uri, '/');
+		}
+		elseif (is_array($uri))
+		{
+			return http_build_query($uri);
+		}
+
+		return $uri;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * System URL
+	 *
+	 * @deprecated	3.0.0	Encourages insecure practices
+	 * @return	string
+	 */
+	public function system_url()
+	{
+		$x = explode('/', preg_replace('|/*(.+?)/*$|', '\\1', BASEPATH));
+		return $this->slash_item('base_url').end($x).'/';
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Set a config file item
+	 *
+	 * @param	string	$item	Config item key
+	 * @param	string	$value	Config item value
+	 * @return	void
+	 */
+	public function set_item($item, $value)
+	{
+		$this->config[$item] = $value;
+	}
+
+}

+ 96 - 0
apps/system/core/Controller.php

@@ -0,0 +1,96 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 1.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * Application Controller Class
+ *
+ * This class object is the super class that every library in
+ * CodeIgniter will be assigned to.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Libraries
+ * @category	Libraries
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/general/controllers.html
+ */
+class CI_Controller {
+
+	/**
+	 * Reference to the CI singleton
+	 *
+	 * @var	object
+	 */
+	private static $instance;
+
+	/**
+	 * Class constructor
+	 *
+	 * @return	void
+	 */
+	public function __construct()
+	{
+		self::$instance =& $this;
+
+		// Assign all the class objects that were instantiated by the
+		// bootstrap file (CodeIgniter.php) to local class variables
+		// so that CI can run as one big super object.
+		foreach (is_loaded() as $var => $class)
+		{
+			$this->$var =& load_class($class);
+		}
+
+		$this->load =& load_class('Loader', 'core');
+		$this->load->initialize();
+		log_message('info', 'Controller Class Initialized');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Get the CI singleton
+	 *
+	 * @static
+	 * @return	object
+	 */
+	public static function &get_instance()
+	{
+		return self::$instance;
+	}
+
+}

+ 274 - 0
apps/system/core/Exceptions.php

@@ -0,0 +1,274 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 1.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * Exceptions Class
+ *
+ * @package		CodeIgniter
+ * @subpackage	Libraries
+ * @category	Exceptions
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/libraries/exceptions.html
+ */
+class CI_Exceptions {
+
+	/**
+	 * Nesting level of the output buffering mechanism
+	 *
+	 * @var	int
+	 */
+	public $ob_level;
+
+	/**
+	 * List of available error levels
+	 *
+	 * @var	array
+	 */
+	public $levels = array(
+		E_ERROR			=>	'Error',
+		E_WARNING		=>	'Warning',
+		E_PARSE			=>	'Parsing Error',
+		E_NOTICE		=>	'Notice',
+		E_CORE_ERROR		=>	'Core Error',
+		E_CORE_WARNING		=>	'Core Warning',
+		E_COMPILE_ERROR		=>	'Compile Error',
+		E_COMPILE_WARNING	=>	'Compile Warning',
+		E_USER_ERROR		=>	'User Error',
+		E_USER_WARNING		=>	'User Warning',
+		E_USER_NOTICE		=>	'User Notice',
+		E_STRICT		=>	'Runtime Notice'
+	);
+
+	/**
+	 * Class constructor
+	 *
+	 * @return	void
+	 */
+	public function __construct()
+	{
+		$this->ob_level = ob_get_level();
+		// Note: Do not log messages from this constructor.
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Exception Logger
+	 *
+	 * Logs PHP generated error messages
+	 *
+	 * @param	int	$severity	Log level
+	 * @param	string	$message	Error message
+	 * @param	string	$filepath	File path
+	 * @param	int	$line		Line number
+	 * @return	void
+	 */
+	public function log_exception($severity, $message, $filepath, $line)
+	{
+		$severity = isset($this->levels[$severity]) ? $this->levels[$severity] : $severity;
+		log_message('error', 'Severity: '.$severity.' --> '.$message.' '.$filepath.' '.$line);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * 404 Error Handler
+	 *
+	 * @uses	CI_Exceptions::show_error()
+	 *
+	 * @param	string	$page		Page URI
+	 * @param 	bool	$log_error	Whether to log the error
+	 * @return	void
+	 */
+	public function show_404($page = '', $log_error = TRUE)
+	{
+		if (is_cli())
+		{
+			$heading = 'Not Found';
+			$message = 'The controller/method pair you requested was not found.';
+		}
+		else
+		{
+			$heading = '404 Page Not Found';
+			$message = 'The page you requested was not found.';
+		}
+
+		// By default we log this, but allow a dev to skip it
+		if ($log_error)
+		{
+			log_message('error', $heading.': '.$page);
+		}
+
+		echo $this->show_error($heading, $message, 'error_404', 404);
+		exit(4); // EXIT_UNKNOWN_FILE
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * General Error Page
+	 *
+	 * Takes an error message as input (either as a string or an array)
+	 * and displays it using the specified template.
+	 *
+	 * @param	string		$heading	Page heading
+	 * @param	string|string[]	$message	Error message
+	 * @param	string		$template	Template name
+	 * @param 	int		$status_code	(default: 500)
+	 *
+	 * @return	string	Error page output
+	 */
+	public function show_error($heading, $message, $template = 'error_general', $status_code = 500)
+	{
+		$templates_path = config_item('error_views_path');
+		if (empty($templates_path))
+		{
+			$templates_path = VIEWPATH.'errors'.DIRECTORY_SEPARATOR;
+		}
+
+		if (is_cli())
+		{
+			$message = "\t".(is_array($message) ? implode("\n\t", $message) : $message);
+			$template = 'cli'.DIRECTORY_SEPARATOR.$template;
+		}
+		else
+		{
+			set_status_header($status_code);
+			$message = '<p>'.(is_array($message) ? implode('</p><p>', $message) : $message).'</p>';
+			$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;
+	}
+
+}

+ 266 - 0
apps/system/core/Hooks.php

@@ -0,0 +1,266 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 1.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * Hooks Class
+ *
+ * Provides a mechanism to extend the base system without hacking.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Libraries
+ * @category	Libraries
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/general/hooks.html
+ */
+class CI_Hooks {
+
+	/**
+	 * Determines whether hooks are enabled
+	 *
+	 * @var	bool
+	 */
+	public $enabled = FALSE;
+
+	/**
+	 * List of all hooks set in config/hooks.php
+	 *
+	 * @var	array
+	 */
+	public $hooks =	array();
+
+	/**
+	 * Array with class objects to use hooks methods
+	 *
+	 * @var array
+	 */
+	protected $_objects = array();
+
+	/**
+	 * In progress flag
+	 *
+	 * Determines whether hook is in progress, used to prevent infinte loops
+	 *
+	 * @var	bool
+	 */
+	protected $_in_progress = FALSE;
+
+	/**
+	 * Class constructor
+	 *
+	 * @return	void
+	 */
+	public function __construct()
+	{
+		$CFG =& load_class('Config', 'core');
+		log_message('info', 'Hooks Class Initialized');
+
+		// If hooks are not enabled in the config file
+		// there is nothing else to do
+		if ($CFG->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;
+	}
+
+}

+ 895 - 0
apps/system/core/Input.php

@@ -0,0 +1,895 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 1.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * Input Class
+ *
+ * Pre-processes global input data for security
+ *
+ * @package		CodeIgniter
+ * @subpackage	Libraries
+ * @category	Input
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/libraries/input.html
+ */
+class CI_Input {
+
+	/**
+	 * IP address of the current user
+	 *
+	 * @var	string
+	 */
+	protected $ip_address = FALSE;
+
+	/**
+	 * Allow GET array flag
+	 *
+	 * If set to FALSE, then $_GET will be set to an empty array.
+	 *
+	 * @var	bool
+	 */
+	protected $_allow_get_array = TRUE;
+
+	/**
+	 * Standardize new lines flag
+	 *
+	 * If set to TRUE, then newlines are standardized.
+	 *
+	 * @var	bool
+	 */
+	protected $_standardize_newlines;
+
+	/**
+	 * Enable XSS flag
+	 *
+	 * Determines whether the XSS filter is always active when
+	 * GET, POST or COOKIE data is encountered.
+	 * Set automatically based on config setting.
+	 *
+	 * @var	bool
+	 */
+	protected $_enable_xss = FALSE;
+
+	/**
+	 * Enable CSRF flag
+	 *
+	 * Enables a CSRF cookie token to be set.
+	 * Set automatically based on config setting.
+	 *
+	 * @var	bool
+	 */
+	protected $_enable_csrf = FALSE;
+
+	/**
+	 * List of all HTTP request headers
+	 *
+	 * @var array
+	 */
+	protected $headers = array();
+
+	/**
+	 * Raw input stream data
+	 *
+	 * Holds a cache of php://input contents
+	 *
+	 * @var	string
+	 */
+	protected $_raw_input_stream;
+
+	/**
+	 * Parsed input stream data
+	 *
+	 * Parsed from php://input at runtime
+	 *
+	 * @see	CI_Input::input_stream()
+	 * @var	array
+	 */
+	protected $_input_stream;
+
+	protected $security;
+	protected $uni;
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * Determines whether to globally enable the XSS processing
+	 * and whether to allow the $_GET array.
+	 *
+	 * @return	void
+	 */
+	public function __construct()
+	{
+		$this->_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;
+		}
+	}
+
+}

+ 203 - 0
apps/system/core/Lang.php

@@ -0,0 +1,203 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 1.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * Language Class
+ *
+ * @package		CodeIgniter
+ * @subpackage	Libraries
+ * @category	Language
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/libraries/language.html
+ */
+class CI_Lang {
+
+	/**
+	 * List of translations
+	 *
+	 * @var	array
+	 */
+	public $language =	array();
+
+	/**
+	 * List of loaded language files
+	 *
+	 * @var	array
+	 */
+	public $is_loaded =	array();
+
+	/**
+	 * Class constructor
+	 *
+	 * @return	void
+	 */
+	public function __construct()
+	{
+		log_message('info', 'Language Class Initialized');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Load a language file
+	 *
+	 * @param	mixed	$langfile	Language file name
+	 * @param	string	$idiom		Language name (english, etc.)
+	 * @param	bool	$return		Whether to return the loaded array of translations
+	 * @param 	bool	$add_suffix	Whether to add suffix to $langfile
+	 * @param 	string	$alt_path	Alternative path to look for the language file
+	 *
+	 * @return	void|string[]	Array containing translations, if $return is set to TRUE
+	 */
+	public function load($langfile, $idiom = '', $return = FALSE, $add_suffix = TRUE, $alt_path = '')
+	{
+		if (is_array($langfile))
+		{
+			foreach ($langfile as $value)
+			{
+				$this->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;
+	}
+
+}

+ 1415 - 0
apps/system/core/Loader.php

@@ -0,0 +1,1415 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 1.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * Loader Class
+ *
+ * Loads framework components.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Libraries
+ * @category	Loader
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/libraries/loader.html
+ */
+class CI_Loader {
+
+	// All these are set automatically. Don't mess with them.
+	/**
+	 * Nesting level of the output buffering mechanism
+	 *
+	 * @var	int
+	 */
+	protected $_ci_ob_level;
+
+	/**
+	 * List of paths to load views from
+	 *
+	 * @var	array
+	 */
+	protected $_ci_view_paths =	array(VIEWPATH	=> 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('<?=', '<?php echo ', file_get_contents($_ci_path))));
+		}
+		else
+		{
+			include($_ci_path); // include() vs include_once() allows for multiple views with the same name
+		}
+
+		log_message('info', 'File loaded: '.$_ci_path);
+
+		// Return the file data if requested
+		if ($_ci_return === TRUE)
+		{
+			$buffer = ob_get_contents();
+			@ob_end_clean();
+			return $buffer;
+		}
+
+		/*
+		 * Flush the buffer... or buff the flusher?
+		 *
+		 * In order to permit views to be nested within
+		 * other views, we need to flush the content back out whenever
+		 * we are beyond the first level of output buffering so that
+		 * it can be seen and included properly by the first included
+		 * template and any subsequent ones. Oy!
+		 */
+		if (ob_get_level() > $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;
+	}
+}

+ 296 - 0
apps/system/core/Log.php

@@ -0,0 +1,296 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 1.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * Logging Class
+ *
+ * @package		CodeIgniter
+ * @subpackage	Libraries
+ * @category	Logging
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/general/errors.html
+ */
+class CI_Log {
+
+	/**
+	 * Path to save log files
+	 *
+	 * @var string
+	 */
+	protected $_log_path;
+
+	/**
+	 * File permissions
+	 *
+	 * @var	int
+	 */
+	protected $_file_permissions = 0644;
+
+	/**
+	 * Level of logging
+	 *
+	 * @var int
+	 */
+	protected $_threshold = 1;
+
+	/**
+	 * Array of threshold levels to log
+	 *
+	 * @var array
+	 */
+	protected $_threshold_array = array();
+
+	/**
+	 * Format of timestamp for log files
+	 *
+	 * @var string
+	 */
+	protected $_date_fmt = 'Y-m-d H:i:s';
+
+	/**
+	 * Filename extension
+	 *
+	 * @var	string
+	 */
+	protected $_file_ext;
+
+	/**
+	 * Whether or not the logger can write to the log files
+	 *
+	 * @var bool
+	 */
+	protected $_enabled = TRUE;
+
+	/**
+	 * Predefined logging levels
+	 *
+	 * @var array
+	 */
+	protected $_levels = array('ERROR' => 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 .= "<?php defined('BASEPATH') OR exit('No direct script access allowed'); ?>\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);
+	}
+}

+ 76 - 0
apps/system/core/Model.php

@@ -0,0 +1,76 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 1.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * Model Class
+ *
+ * @package		CodeIgniter
+ * @subpackage	Libraries
+ * @category	Libraries
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/libraries/config.html
+ */
+class CI_Model {
+
+	/**
+	 * Class constructor
+	 *
+	 * @link	https://github.com/bcit-ci/CodeIgniter/issues/5332
+	 * @return	void
+	 */
+	public function __construct() {}
+
+	/**
+	 * __get magic
+	 *
+	 * Allows models to access CI's loaded classes using the same
+	 * syntax as controllers.
+	 *
+	 * @param	string	$key
+	 */
+	public function __get($key)
+	{
+		// Debugging note:
+		//	If you're here because you're getting an error message
+		//	saying 'Undefined Property: system/core/Model.php', it's
+		//	most likely a typo in your model code.
+		return get_instance()->$key;
+	}
+
+}

+ 842 - 0
apps/system/core/Output.php

@@ -0,0 +1,842 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 1.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * Output Class
+ *
+ * Responsible for sending final output to the browser.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Libraries
+ * @category	Output
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/libraries/output.html
+ */
+class CI_Output {
+
+	/**
+	 * Final output string
+	 *
+	 * @var	string
+	 */
+	public $final_output;
+
+	/**
+	 * Cache expiration time
+	 *
+	 * @var	int
+	 */
+	public $cache_expiration = 0;
+
+	/**
+	 * List of server headers
+	 *
+	 * @var	array
+	 */
+	public $headers = array();
+
+	/**
+	 * List of mime types
+	 *
+	 * @var	array
+	 */
+	public $mimes =	array();
+
+	/**
+	 * Mime-type for the current page
+	 *
+	 * @var	string
+	 */
+	protected $mime_type = 'text/html';
+
+	/**
+	 * Enable Profiler flag
+	 *
+	 * @var	bool
+	 */
+	public $enable_profiler = FALSE;
+
+	/**
+	 * php.ini zlib.output_compression flag
+	 *
+	 * @var	bool
+	 */
+	protected $_zlib_oc = FALSE;
+
+	/**
+	 * CI output compression flag
+	 *
+	 * @var	bool
+	 */
+	protected $_compress_output = FALSE;
+
+	/**
+	 * List of profiler sections
+	 *
+	 * @var	array
+	 */
+	protected $_profiler_sections =	array();
+
+	/**
+	 * Parse markers flag
+	 *
+	 * Whether or not to parse variables like {elapsed_time} and {memory_usage}.
+	 *
+	 * @var	bool
+	 */
+	public $parse_exec_vars = TRUE;
+
+	/**
+	 * mbstring.func_overload flag
+	 *
+	 * @var	bool
+	 */
+	protected static $func_overload;
+
+	/**
+	 * Class constructor
+	 *
+	 * Determines whether zLib output compression will be used.
+	 *
+	 * @return	void
+	 */
+	public function __construct()
+	{
+		$this->_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 </body> and </html> tags
+			// we will remove them and add them back after we insert the profile data
+			$output = preg_replace('|</body>.*?</html>|is', '', $output, -1, $count).$CI->profiler->run();
+			if ($count > 0)
+			{
+				$output .= '</body></html>';
+			}
+		}
+
+		// Does the controller contain a function named _output()?
+		// If so send the output there.  Otherwise, echo it.
+		if (method_exists($CI, '_output'))
+		{
+			$CI->_output($output);
+		}
+		else
+		{
+			echo $output; // Send it to the browser!
+		}
+
+		log_message('info', 'Final output sent to browser');
+		log_message('debug', 'Total execution time: '.$elapsed);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Write Cache
+	 *
+	 * @param	string	$output	Output data to cache
+	 * @return	void
+	 */
+	public function _write_cache($output)
+	{
+		$CI =& get_instance();
+		$path = $CI->config->item('cache_path');
+		$cache_path = ($path === '') ? APPPATH.'cache/' : $path;
+
+		if ( ! is_dir($cache_path) OR ! is_really_writable($cache_path))
+		{
+			log_message('error', 'Unable to write cache file: '.$cache_path);
+			return;
+		}
+
+		$uri = $CI->config->item('base_url')
+			.$CI->config->item('index_page')
+			.$CI->uri->uri_string();
+
+		if (($cache_query_string = $CI->config->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
+		{
+			if (is_array($cache_query_string))
+			{
+				$uri .= '?'.http_build_query(array_intersect_key($_GET, array_flip($cache_query_string)));
+			}
+			else
+			{
+				$uri .= '?'.$_SERVER['QUERY_STRING'];
+			}
+		}
+
+		$cache_path .= md5($uri);
+
+		if ( ! $fp = @fopen($cache_path, 'w+b'))
+		{
+			log_message('error', 'Unable to write cache file: '.$cache_path);
+			return;
+		}
+
+		if ( ! flock($fp, LOCK_EX))
+		{
+			log_message('error', 'Unable to secure a file lock for file at: '.$cache_path);
+			fclose($fp);
+			return;
+		}
+
+		// If output compression is enabled, compress the cache
+		// itself, so that we don't have to do that each time
+		// we're serving it
+		if ($this->_compress_output === TRUE)
+		{
+			$output = gzencode($output);
+
+			if ($this->get_header('content-type') === NULL)
+			{
+				$this->set_content_type($this->mime_type);
+			}
+		}
+
+		$expire = time() + ($this->cache_expiration * 60);
+
+		// Put together our serialized info.
+		$cache_info = serialize(array(
+			'expire'	=> $expire,
+			'headers'	=> $this->headers
+		));
+
+		$output = $cache_info.'ENDCI--->'.$output;
+
+		for ($written = 0, $length = self::strlen($output); $written < $length; $written += $result)
+		{
+			if (($result = fwrite($fp, self::substr($output, $written))) === FALSE)
+			{
+				break;
+			}
+		}
+
+		flock($fp, LOCK_UN);
+		fclose($fp);
+
+		if ( ! is_int($result))
+		{
+			@unlink($cache_path);
+			log_message('error', 'Unable to write the complete cache content at: '.$cache_path);
+			return;
+		}
+
+		chmod($cache_path, 0640);
+		log_message('debug', 'Cache file written: '.$cache_path);
+
+		// Send HTTP cache-control headers to browser to match file cache settings.
+		$this->set_cache_header($_SERVER['REQUEST_TIME'], $expire);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Update/serve cached output
+	 *
+	 * @uses	CI_Config
+	 * @uses	CI_URI
+	 *
+	 * @param	object	&$CFG	CI_Config class instance
+	 * @param	object	&$URI	CI_URI class instance
+	 * @return	bool	TRUE on success or FALSE on failure
+	 */
+	public function _display_cache(&$CFG, &$URI)
+	{
+		$cache_path = ($CFG->item('cache_path') === '') ? APPPATH.'cache/' : $CFG->item('cache_path');
+
+		// Build the file path. The file name is an MD5 hash of the full URI
+		$uri = $CFG->item('base_url').$CFG->item('index_page').$URI->uri_string;
+
+		if (($cache_query_string = $CFG->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
+		{
+			if (is_array($cache_query_string))
+			{
+				$uri .= '?'.http_build_query(array_intersect_key($_GET, array_flip($cache_query_string)));
+			}
+			else
+			{
+				$uri .= '?'.$_SERVER['QUERY_STRING'];
+			}
+		}
+
+		$filepath = $cache_path.md5($uri);
+
+		if ( ! file_exists($filepath) OR ! $fp = @fopen($filepath, 'rb'))
+		{
+			return FALSE;
+		}
+
+		flock($fp, LOCK_SH);
+
+		$cache = (filesize($filepath) > 0) ? fread($fp, filesize($filepath)) : '';
+
+		flock($fp, LOCK_UN);
+		fclose($fp);
+
+		// Look for embedded serialized file info.
+		if ( ! preg_match('/^(.*)ENDCI--->/', $cache, $match))
+		{
+			return FALSE;
+		}
+
+		$cache_info = unserialize($match[1]);
+		$expire = $cache_info['expire'];
+
+		$last_modified = filemtime($filepath);
+
+		// Has the file expired?
+		if ($_SERVER['REQUEST_TIME'] >= $expire && is_really_writable($cache_path))
+		{
+			// If so we'll delete it.
+			@unlink($filepath);
+			log_message('debug', 'Cache file has expired. File deleted.');
+			return FALSE;
+		}
+
+		// Send the HTTP cache control headers
+		$this->set_cache_header($last_modified, $expire);
+
+		// Add headers from cache file.
+		foreach ($cache_info['headers'] as $header)
+		{
+			$this->set_header($header[0], $header[1]);
+		}
+
+		// Display the cache
+		$this->_display(self::substr($cache, self::strlen($match[0])));
+		log_message('debug', 'Cache file is current. Sending it to browser.');
+		return TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Delete cache
+	 *
+	 * @param	string	$uri	URI string
+	 * @return	bool
+	 */
+	public function delete_cache($uri = '')
+	{
+		$CI =& get_instance();
+		$cache_path = $CI->config->item('cache_path');
+		if ($cache_path === '')
+		{
+			$cache_path = APPPATH.'cache/';
+		}
+
+		if ( ! is_dir($cache_path))
+		{
+			log_message('error', 'Unable to find cache path: '.$cache_path);
+			return FALSE;
+		}
+
+		if (empty($uri))
+		{
+			$uri = $CI->uri->uri_string();
+
+			if (($cache_query_string = $CI->config->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
+			{
+				if (is_array($cache_query_string))
+				{
+					$uri .= '?'.http_build_query(array_intersect_key($_GET, array_flip($cache_query_string)));
+				}
+				else
+				{
+					$uri .= '?'.$_SERVER['QUERY_STRING'];
+				}
+			}
+		}
+
+		$cache_path .= md5($CI->config->item('base_url').$CI->config->item('index_page').ltrim($uri, '/'));
+
+		if ( ! @unlink($cache_path))
+		{
+			log_message('error', 'Unable to delete cache file for '.$uri);
+			return FALSE;
+		}
+
+		return TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Set Cache Header
+	 *
+	 * Set the HTTP headers to match the server-side file cache settings
+	 * in order to reduce bandwidth.
+	 *
+	 * @param	int	$last_modified	Timestamp of when the page was last modified
+	 * @param	int	$expiration	Timestamp of when should the requested page expire from cache
+	 * @return	void
+	 */
+	public function set_cache_header($last_modified, $expiration)
+	{
+		$max_age = $expiration - $_SERVER['REQUEST_TIME'];
+
+		if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $last_modified <= strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']))
+		{
+			$this->set_status_header(304);
+			exit;
+		}
+
+		header('Pragma: public');
+		header('Cache-Control: max-age='.$max_age.', public');
+		header('Expires: '.gmdate('D, d M Y H:i:s', $expiration).' GMT');
+		header('Last-modified: '.gmdate('D, d M Y H:i:s', $last_modified).' GMT');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * 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);
+	}
+}

+ 511 - 0
apps/system/core/Router.php

@@ -0,0 +1,511 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 1.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * Router Class
+ *
+ * Parses URIs and determines routing
+ *
+ * @package		CodeIgniter
+ * @subpackage	Libraries
+ * @category	Libraries
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/general/routing.html
+ */
+class CI_Router {
+
+	/**
+	 * CI_Config class object
+	 *
+	 * @var	object
+	 */
+	public $config;
+
+	/**
+	 * List of routes
+	 *
+	 * @var	array
+	 */
+	public $routes =	array();
+
+	/**
+	 * Current class name
+	 *
+	 * @var	string
+	 */
+	public $class =		'';
+
+	/**
+	 * Current method name
+	 *
+	 * @var	string
+	 */
+	public $method =	'index';
+
+	/**
+	 * Sub-directory that contains the requested controller class
+	 *
+	 * @var	string
+	 */
+	public $directory;
+
+	/**
+	 * Default controller (and method if specific)
+	 *
+	 * @var	string
+	 */
+	public $default_controller;
+
+	/**
+	 * Translate URI dashes
+	 *
+	 * Determines whether dashes in controller & method segments
+	 * should be automatically replaced by underscores.
+	 *
+	 * @var	bool
+	 */
+	public $translate_uri_dashes = FALSE;
+
+	/**
+	 * Enable query strings flag
+	 *
+	 * Determines whether to use GET parameters or segment URIs
+	 *
+	 * @var	bool
+	 */
+	public $enable_query_strings = FALSE;
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * Runs the route mapping function.
+	 *
+	 * @param	array	$routing
+	 * @return	void
+	 */
+	public function __construct($routing = NULL)
+	{
+		$this->config =& load_class('Config', 'core');
+		$this->uri =& load_class('URI', 'core');
+
+		$this->enable_query_strings = ( ! is_cli() && $this->config->item('enable_query_strings') === TRUE);
+
+		// If a directory override is configured, it has to be set before any dynamic routing logic
+		is_array($routing) && isset($routing['directory']) && $this->set_directory($routing['directory']);
+		$this->_set_routing();
+		// Set any routing overrides that may exist in the main index file
+		if (is_array($routing))
+		{
+			empty($routing['controller']) OR $this->set_class($routing['controller']);
+			empty($routing['function'])   OR $this->set_method($routing['function']);
+		}
+		log_message('info', 'Router Class Initialized');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Set route mapping
+	 *
+	 * Determines what should be served based on the URI request,
+	 * as well as any "routes" that have been set in the routing config file.
+	 *
+	 * @return	void
+	 */
+	protected function _set_routing()
+	{
+		// Load the routes.php file. It would be great if we could
+		// skip this for enable_query_strings = TRUE, but then
+		// default_controller would be empty ...
+		if (file_exists(APPPATH.'config/routes.php'))
+		{
+			include(APPPATH.'config/routes.php');
+		}
+
+		if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/routes.php'))
+		{
+			include(APPPATH.'config/'.ENVIRONMENT.'/routes.php');
+		}
+		// Validate & get reserved routes
+		if (isset($route) && is_array($route))
+		{
+			isset($route['default_controller']) && $this->default_controller = $route['default_controller'];
+			isset($route['translate_uri_dashes']) && $this->translate_uri_dashes = $route['translate_uri_dashes'];
+			unset($route['default_controller'], $route['translate_uri_dashes']);
+			$this->routes = $route;
+		}
+		// Are query strings enabled in the config file? Normally CI doesn't utilize query strings
+		// since URI segments are more search-engine friendly, but they can optionally be used.
+		// If this feature is enabled, we will gather the directory/class/method a little differently
+		if ($this->enable_query_strings)
+		{
+			// If the directory is set at this time, it means an override exists, so skip the checks
+			if ( ! isset($this->directory))
+			{
+				$_d = $this->config->item('directory_trigger');
+				$_d = isset($_GET[$_d]) ? trim($_GET[$_d], " \t\n\r\0\x0B/") : '';
+
+				if ($_d !== '')
+				{
+					$this->uri->filter_uri($_d);
+					$this->set_directory($_d);
+				}
+			}
+
+			$_c = trim($this->config->item('controller_trigger'));
+			if ( ! empty($_GET[$_c]))
+			{
+				$this->uri->filter_uri($_GET[$_c]);
+				$this->set_class($_GET[$_c]);
+
+				$_f = trim($this->config->item('function_trigger'));
+				if ( ! empty($_GET[$_f]))
+				{
+					$this->uri->filter_uri($_GET[$_f]);
+					$this->set_method($_GET[$_f]);
+				}
+
+				$this->uri->rsegments = array(
+					1 => $this->class,
+					2 => $this->method
+				);
+			}
+			else
+			{
+				$this->_set_default_controller();
+			}
+
+			// Routing rules don't apply to query strings and we don't need to detect
+			// directories, so we're done here
+			return;
+		}
+
+		// Is there anything to parse?
+		if ($this->uri->uri_string !== '')
+		{
+			$this->_parse_routes();
+		}
+		else
+		{
+			$this->_set_default_controller();
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Set request route
+	 *
+	 * Takes an array of URI segments as input and sets the class/method
+	 * to be called.
+	 *
+	 * @used-by	CI_Router::_parse_routes()
+	 * @param	array	$segments	URI segments
+	 * @return	void
+	 */
+	protected function _set_request($segments = array())
+	{
+		$segments = $this->_validate_request($segments);
+		// If we don't have any segments left - try the default controller;
+		// WARNING: Directories get shifted out of the segments array!
+		if (empty($segments))
+		{
+			$this->_set_default_controller();
+			return;
+		}
+
+		if ($this->translate_uri_dashes === TRUE)
+		{
+			$segments[0] = str_replace('-', '_', $segments[0]);
+			if (isset($segments[1]))
+			{
+				$segments[1] = str_replace('-', '_', $segments[1]);
+			}
+		}
+
+		$this->set_class($segments[0]);
+		if (isset($segments[1]))
+		{
+			$this->set_method($segments[1]);
+		}
+		else
+		{
+			$segments[1] = 'index';
+		}
+
+		array_unshift($segments, NULL);
+		unset($segments[0]);
+		$this->uri->rsegments = $segments;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Set default controller
+	 *
+	 * @return	void
+	 */
+	protected function _set_default_controller()
+	{
+		if (empty($this->default_controller))
+		{
+			show_error('Unable to determine what should be displayed. A default route has not been specified in the routing file.');
+		}
+
+		// Is the method being specified?
+		if (sscanf($this->default_controller, '%[^/]/%s', $class, $method) !== 2)
+		{
+			$method = 'index';
+		}
+
+		if ( ! file_exists(APPPATH.'controllers/'.$this->directory.ucfirst($class).'.php'))
+		{
+			// This will trigger 404 later
+			return;
+		}
+
+		$this->set_class($class);
+		$this->set_method($method);
+
+		// Assign routed segments, index starting from 1
+		$this->uri->rsegments = array(
+			1 => $class,
+			2 => $method
+		);
+
+		log_message('debug', 'No URI present. Default controller set.');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Validate request
+	 *
+	 * Attempts validate the URI request and determine the controller path.
+	 *
+	 * @used-by	CI_Router::_set_request()
+	 * @param	array	$segments	URI segments
+	 * @return	mixed	URI segments
+	 */
+	protected function _validate_request($segments)
+	{
+		$c = count($segments);
+		$directory_override = isset($this->directory);
+
+		// Loop through our segments and return as soon as a controller
+		// is found or when such a directory doesn't exist
+		while ($c-- > 0)
+		{
+			$test = $this->directory
+				.ucfirst($this->translate_uri_dashes === TRUE ? str_replace('-', '_', $segments[0]) : $segments[0]);
+
+			if ( ! file_exists(APPPATH.'controllers/'.$test.'.php')
+				&& $directory_override === FALSE
+				&& is_dir(APPPATH.'controllers/'.$this->directory.$segments[0])
+			)
+			{
+				$this->set_directory(array_shift($segments), TRUE);
+				continue;
+			}
+
+			return $segments;
+		}
+
+		// This means that all segments were actually directories
+		return $segments;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Parse Routes
+	 *
+	 * Matches any routes that may exist in the config/routes.php file
+	 * against the URI to determine if the class/method need to be remapped.
+	 *
+	 * @return	void
+	 */
+	protected function _parse_routes()
+	{
+		// Turn the segment array into a URI string
+		$uri = implode('/', $this->uri->segments);
+
+		// Get HTTP verb
+		$http_verb = isset($_SERVER['REQUEST_METHOD']) ? strtolower($_SERVER['REQUEST_METHOD']) : 'cli';
+
+		// Loop through the route array looking for wildcards
+		foreach ($this->routes as $key => $val)
+		{
+			// Check if route format is using HTTP verbs
+			if (is_array($val))
+			{
+				$val = array_change_key_case($val, CASE_LOWER);
+				if (isset($val[$http_verb]))
+				{
+					$val = $val[$http_verb];
+				}
+				else
+				{
+					continue;
+				}
+			}
+
+			// Convert wildcards to RegEx
+			$key = str_replace(array(':any', ':num'), array('[^/]+', '[0-9]+'), $key);
+
+			// Does the RegEx match?
+			if (preg_match('#^'.$key.'$#', $uri, $matches))
+			{
+				// Are we using callbacks to process back-references?
+				if ( ! is_string($val) && is_callable($val))
+				{
+					// Remove the original string from the matches array.
+					array_shift($matches);
+
+					// Execute the callback using the values in matches as its parameters.
+					$val = call_user_func_array($val, $matches);
+				}
+				// Are we using the default routing method for back-references?
+				elseif (strpos($val, '$') !== FALSE && strpos($key, '(') !== FALSE)
+				{
+					$val = preg_replace('#^'.$key.'$#', $val, $uri);
+				}
+
+				$this->_set_request(explode('/', $val));
+				return;
+			}
+		}
+
+		// If we got this far it means we didn't encounter a
+		// matching route so we'll set the site default route
+		$this->_set_request(array_values($this->uri->segments));
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Set class name
+	 *
+	 * @param	string	$class	Class name
+	 * @return	void
+	 */
+	public function set_class($class)
+	{
+		$this->class = str_replace(array('/', '.'), '', $class);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Fetch the current class
+	 *
+	 * @deprecated	3.0.0	Read the 'class' property instead
+	 * @return	string
+	 */
+	public function fetch_class()
+	{
+		return $this->class;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Set method name
+	 *
+	 * @param	string	$method	Method name
+	 * @return	void
+	 */
+	public function set_method($method)
+	{
+		$this->method = $method;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Fetch the current method
+	 *
+	 * @deprecated	3.0.0	Read the 'method' property instead
+	 * @return	string
+	 */
+	public function fetch_method()
+	{
+		return $this->method;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Set directory name
+	 *
+	 * @param	string	$dir	Directory name
+	 * @param	bool	$append	Whether we're appending rather than setting the full value
+	 * @return	void
+	 */
+	public function set_directory($dir, $append = FALSE)
+	{
+		if ($append !== TRUE OR empty($this->directory))
+		{
+			$this->directory = str_replace('.', '', trim($dir, '/')).'/';
+		}
+		else
+		{
+			$this->directory .= str_replace('.', '', trim($dir, '/')).'/';
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Fetch directory
+	 *
+	 * Feches the sub-directory (if any) that contains the requested
+	 * controller class.
+	 *
+	 * @deprecated	3.0.0	Read the 'directory' property instead
+	 * @return	string
+	 */
+	public function fetch_directory()
+	{
+		return $this->directory;
+	}
+
+}

+ 1080 - 0
apps/system/core/Security.php

@@ -0,0 +1,1080 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 1.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * Security Class
+ *
+ * @package		CodeIgniter
+ * @subpackage	Libraries
+ * @category	Security
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/libraries/security.html
+ */
+class CI_Security {
+
+	/**
+	 * List of sanitize filename strings
+	 *
+	 * @var	array
+	 */
+	public $filename_bad_chars =	array(
+		'../', '<!--', '-->', '<', '>',
+		"'", '"', '&', '$', '#',
+		'{', '}', '[', ']', '=',
+		';', '?', '%20', '%22',
+		'%3c',		// <
+		'%253c',	// <
+		'%3e',		// >
+		'%0e',		// >
+		'%28',		// (
+		'%29',		// )
+		'%2528',	// (
+		'%26',		// &
+		'%24',		// $
+		'%3f',		// ?
+		'%3b',		// ;
+		'%3d'		// =
+	);
+
+	/**
+	 * Character set
+	 *
+	 * Will be overridden by the constructor.
+	 *
+	 * @var	string
+	 */
+	public $charset = 'UTF-8';
+
+	/**
+	 * XSS Hash
+	 *
+	 * Random Hash for protecting URLs.
+	 *
+	 * @var	string
+	 */
+	protected $_xss_hash;
+
+	/**
+	 * CSRF Hash
+	 *
+	 * Random hash for Cross Site Request Forgery protection cookie
+	 *
+	 * @var	string
+	 */
+	protected $_csrf_hash;
+
+	/**
+	 * CSRF Expire time
+	 *
+	 * Expiration time for Cross Site Request Forgery protection cookie.
+	 * Defaults to two hours (in seconds).
+	 *
+	 * @var	int
+	 */
+	protected $_csrf_expire =	7200;
+
+	/**
+	 * CSRF Token name
+	 *
+	 * Token name for Cross Site Request Forgery protection cookie.
+	 *
+	 * @var	string
+	 */
+	protected $_csrf_token_name =	'ci_csrf_token';
+
+	/**
+	 * CSRF Cookie name
+	 *
+	 * Cookie name for Cross Site Request Forgery protection cookie.
+	 *
+	 * @var	string
+	 */
+	protected $_csrf_cookie_name =	'ci_csrf_token';
+
+	/**
+	 * List of never allowed strings
+	 *
+	 * @var	array
+	 */
+	protected $_never_allowed_str =	array(
+		'document.cookie' => '[removed]',
+		'document.write'  => '[removed]',
+		'.parentNode'     => '[removed]',
+		'.innerHTML'      => '[removed]',
+		'-moz-binding'    => '[removed]',
+		'<!--'            => '&lt;!--',
+		'-->'             => '--&gt;',
+		'<![CDATA['       => '&lt;![CDATA[',
+		'<comment>'	  => '&lt;comment&gt;',
+		'<%'              => '&lt;&#37;'
+	);
+
+	/**
+	 * List of never allowed regex replacements
+	 *
+	 * @var	array
+	 */
+	protected $_never_allowed_regex = array(
+		'javascript\s*:',
+		'(document|(document\.)?window)\.(location|on\w*)',
+		'expression\s*(\(|&\#40;)', // CSS and IE
+		'vbscript\s*:', // IE, surprise!
+		'wscript\s*:', // IE
+		'jscript\s*:', // IE
+		'vbs\s*:', // IE
+		'Redirect\s+30\d',
+		"([\"'])?data\s*:[^\\1]*?base64[^\\1]*?,[^\\1]*?\\1?"
+	);
+
+	/**
+	 * Class constructor
+	 *
+	 * @return	void
+	 */
+	public function __construct()
+	{
+		// Is CSRF protection enabled?
+		if (config_item('csrf_protection'))
+		{
+			// CSRF config
+			foreach (array('csrf_expire', 'csrf_token_name', 'csrf_cookie_name') as $key)
+			{
+				if (NULL !== ($val = config_item($key)))
+				{
+					$this->{'_'.$key} = $val;
+				}
+			}
+
+			// Append application specific cookie prefix
+			if ($cookie_prefix = config_item('cookie_prefix'))
+			{
+				$this->_csrf_cookie_name = $cookie_prefix.$this->_csrf_cookie_name;
+			}
+
+			// Set the CSRF hash
+			$this->_csrf_set_hash();
+		}
+
+		$this->charset = strtoupper(config_item('charset'));
+
+		log_message('info', 'Security Class Initialized');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * CSRF Verify
+	 *
+	 * @return	CI_Security
+	 */
+	public function csrf_verify()
+	{
+		// If it's not a POST request we will set the CSRF cookie
+		if (strtoupper($_SERVER['REQUEST_METHOD']) !== 'POST')
+		{
+			return $this->csrf_set_cookie();
+		}
+
+		// Check if URI has been whitelisted from CSRF checks
+		if ($exclude_uris = config_item('csrf_exclude_uris'))
+		{
+			$uri = load_class('URI', 'core');
+			foreach ($exclude_uris as $excluded)
+			{
+				if (preg_match('#^'.$excluded.'$#i'.(UTF8_ENABLED ? 'u' : ''), $uri->uri_string()))
+				{
+					return $this;
+				}
+			}
+		}
+
+		// Check CSRF token validity, but don't error on mismatch just yet - we'll want to regenerate
+		$valid = isset($_POST[$this->_csrf_token_name], $_COOKIE[$this->_csrf_cookie_name])
+			&& hash_equals($_POST[$this->_csrf_token_name], $_COOKIE[$this->_csrf_cookie_name]);
+
+		// We kill this since we're done and we don't want to pollute the _POST array
+		unset($_POST[$this->_csrf_token_name]);
+
+		// Regenerate on every submission?
+		if (config_item('csrf_regenerate'))
+		{
+			// Nothing should last forever
+			unset($_COOKIE[$this->_csrf_cookie_name]);
+			$this->_csrf_hash = NULL;
+		}
+
+		$this->_csrf_set_hash();
+		$this->csrf_set_cookie();
+
+		if ($valid !== TRUE)
+		{
+			$this->csrf_show_error();
+		}
+
+		log_message('info', 'CSRF token verified');
+		return $this;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * CSRF Set Cookie
+	 *
+	 * @codeCoverageIgnore
+	 * @return	CI_Security
+	 */
+	public function csrf_set_cookie()
+	{
+		$expire = time() + $this->_csrf_expire;
+		$secure_cookie = (bool) config_item('cookie_secure');
+
+		if ($secure_cookie && ! is_https())
+		{
+			return FALSE;
+		}
+
+		setcookie(
+			$this->_csrf_cookie_name,
+			$this->_csrf_hash,
+			$expire,
+			config_item('cookie_path'),
+			config_item('cookie_domain'),
+			$secure_cookie,
+			config_item('cookie_httponly')
+		);
+		log_message('info', 'CSRF cookie sent');
+
+		return $this;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Show CSRF Error
+	 *
+	 * @return	void
+	 */
+	public function csrf_show_error()
+	{
+		show_error('The action you have requested is not allowed.', 403);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Get CSRF Hash
+	 *
+	 * @see		CI_Security::$_csrf_hash
+	 * @return 	string	CSRF hash
+	 */
+	public function get_csrf_hash()
+	{
+		return $this->_csrf_hash;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Get CSRF Token Name
+	 *
+	 * @see		CI_Security::$_csrf_token_name
+	 * @return	string	CSRF token name
+	 */
+	public function get_csrf_token_name()
+	{
+		return $this->_csrf_token_name;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * XSS Clean
+	 *
+	 * Sanitizes data so that Cross Site Scripting Hacks can be
+	 * prevented.  This method does a fair amount of work but
+	 * it is extremely thorough, designed to prevent even the
+	 * most obscure XSS attempts.  Nothing is ever 100% foolproof,
+	 * of course, but I haven't been able to get anything passed
+	 * the filter.
+	 *
+	 * Note: Should only be used to deal with data upon submission.
+	 *	 It's not something that should be used for general
+	 *	 runtime processing.
+	 *
+	 * @link	http://channel.bitflux.ch/wiki/XSS_Prevention
+	 * 		Based in part on some code and ideas from Bitflux.
+	 *
+	 * @link	http://ha.ckers.org/xss.html
+	 * 		To help develop this script I used this great list of
+	 *		vulnerabilities along with a few other hacks I've
+	 *		harvested from examining vulnerabilities in other programs.
+	 *
+	 * @param	string|string[]	$str		Input data
+	 * @param 	bool		$is_image	Whether the input is an image
+	 * @return	string
+	 */
+	public function xss_clean($str, $is_image = FALSE)
+	{
+		// Is the string an array?
+		if (is_array($str))
+		{
+			foreach ($str as $key => &$value)
+			{
+				$str[$key] = $this->xss_clean($value);
+			}
+
+			return $str;
+		}
+
+		// Remove Invisible Characters
+		$str = remove_invisible_characters($str);
+
+		/*
+		 * URL Decode
+		 *
+		 * Just in case stuff like this is submitted:
+		 *
+		 * <a href="http://%77%77%77%2E%67%6F%6F%67%6C%65%2E%63%6F%6D">Google</a>
+		 *
+		 * Note: Use rawurldecode() so it does not remove plus signs
+		 */
+		if (stripos($str, '%') !== false)
+		{
+			do
+			{
+				$oldstr = $str;
+				$str = rawurldecode($str);
+				$str = preg_replace_callback('#%(?:\s*[0-9a-f]){2,}#i', array($this, '_urldecodespaces'), $str);
+			}
+			while ($oldstr !== $str);
+			unset($oldstr);
+		}
+
+		/*
+		 * Convert character entities to ASCII
+		 *
+		 * This permits our tests below to work reliably.
+		 * We only convert entities that are within tags since
+		 * these are the ones that will pose security problems.
+		 */
+		$str = preg_replace_callback("/[^a-z0-9>]+[a-z0-9]+=([\'\"]).*?\\1/si", array($this, '_convert_attribute'), $str);
+		$str = preg_replace_callback('/<\w+.*/si', array($this, '_decode_entity'), $str);
+
+		// Remove Invisible Characters Again!
+		$str = remove_invisible_characters($str);
+
+		/*
+		 * Convert all tabs to spaces
+		 *
+		 * This prevents strings like this: ja	vascript
+		 * NOTE: we deal with spaces between characters later.
+		 * NOTE: preg_replace was found to be amazingly slow here on
+		 * large blocks of data, so we use str_replace.
+		 */
+		$str = str_replace("\t", ' ', $str);
+
+		// Capture converted string for later comparison
+		$converted_string = $str;
+
+		// Remove Strings that are never allowed
+		$str = $this->_do_never_allowed($str);
+
+		/*
+		 * Makes PHP tags safe
+		 *
+		 * Note: XML tags are inadvertently replaced too:
+		 *
+		 * <?xml
+		 *
+		 * But it doesn't seem to pose a problem.
+		 */
+		if ($is_image === TRUE)
+		{
+			// Images have a tendency to have the PHP short opening and
+			// closing tags every so often so we skip those and only
+			// do the long opening tags.
+			$str = preg_replace('/<\?(php)/i', '&lt;?\\1', $str);
+		}
+		else
+		{
+			$str = str_replace(array('<?', '?'.'>'), array('&lt;?', '?&gt;'), $str);
+		}
+
+		/*
+		 * Compact any exploded words
+		 *
+		 * This corrects words like:  j a v a s c r i p t
+		 * These words are compacted back to their correct state.
+		 */
+		$words = array(
+			'javascript', 'expression', 'vbscript', 'jscript', 'wscript',
+			'vbs', 'script', 'base64', 'applet', 'alert', 'document',
+			'write', 'cookie', 'window', 'confirm', 'prompt', 'eval'
+		);
+
+		foreach ($words as $word)
+		{
+			$word = implode('\s*', str_split($word)).'\s*';
+
+			// We only want to do this when it is followed by a non-word character
+			// That way valid stuff like "dealer to" does not become "dealerto"
+			$str = preg_replace_callback('#('.substr($word, 0, -3).')(\W)#is', array($this, '_compact_exploded_words'), $str);
+		}
+
+		/*
+		 * Remove disallowed Javascript in links or img tags
+		 * We used to do some version comparisons and use of stripos(),
+		 * but it is dog slow compared to these simplified non-capturing
+		 * preg_match(), especially if the pattern exists in the string
+		 *
+		 * Note: It was reported that not only space characters, but all in
+		 * the following pattern can be parsed as separators between a tag name
+		 * and its attributes: [\d\s"\'`;,\/\=\(\x00\x0B\x09\x0C]
+		 * ... however, remove_invisible_characters() above already strips the
+		 * hex-encoded ones, so we'll skip them below.
+		 */
+		do
+		{
+			$original = $str;
+
+			if (preg_match('/<a/i', $str))
+			{
+				$str = preg_replace_callback('#<a(?:rea)?[^a-z0-9>]+([^>]*?)(?:>|$)#si', array($this, '_js_link_removal'), $str);
+			}
+
+			if (preg_match('/<img/i', $str))
+			{
+				$str = preg_replace_callback('#<img[^a-z0-9]+([^>]*?)(?:\s?/?>|$)#si', array($this, '_js_img_removal'), $str);
+			}
+
+			if (preg_match('/script|xss/i', $str))
+			{
+				$str = preg_replace('#</*(?:script|xss).*?>#si', '[removed]', $str);
+			}
+		}
+		while ($original !== $str);
+		unset($original);
+
+		/*
+		 * Sanitize naughty HTML elements
+		 *
+		 * If a tag containing any of the words in the list
+		 * below is found, the tag gets converted to entities.
+		 *
+		 * So this: <blink>
+		 * Becomes: &lt;blink&gt;
+		 */
+		$pattern = '#'
+			.'<((?<slash>/*\s*)((?<tagName>[a-z0-9]+)(?=[^a-z0-9]|$)|.+)' // tag start and name, followed by a non-tag character
+			.'[^\s\042\047a-z0-9>/=]*' // a valid attribute character immediately after the tag would count as a separator
+			// optional attributes
+			.'(?<attributes>(?:[\s\042\047/=]*' // non-attribute characters, excluding > (tag close) for obvious reasons
+			.'[^\s\042\047>/=]+' // attribute characters
+			// optional attribute-value
+				.'(?:\s*=' // attribute-value separator
+					.'(?:[^\s\042\047=><`]+|\s*\042[^\042]*\042|\s*\047[^\047]*\047|\s*(?U:[^\s\042\047=><`]*))' // single, double or non-quoted value
+				.')?' // end optional attribute-value group
+			.')*)' // end optional attributes group
+			.'[^>]*)(?<closeTag>\>)?#isS';
+
+		// Note: It would be nice to optimize this for speed, BUT
+		//       only matching the naughty elements here results in
+		//       false positives and in turn - vulnerabilities!
+		do
+		{
+			$old_str = $str;
+			$str = preg_replace_callback($pattern, array($this, '_sanitize_naughty_html'), $str);
+		}
+		while ($old_str !== $str);
+		unset($old_str);
+
+		/*
+		 * Sanitize naughty scripting elements
+		 *
+		 * Similar to above, only instead of looking for
+		 * tags it looks for PHP and JavaScript commands
+		 * that are disallowed. Rather than removing the
+		 * code, it simply converts the parenthesis to entities
+		 * rendering the code un-executable.
+		 *
+		 * For example:	eval('some code')
+		 * Becomes:	eval&#40;'some code'&#41;
+		 */
+		$str = preg_replace(
+			'#(alert|prompt|confirm|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)\((.*?)\)#si',
+			'\\1\\2&#40;\\3&#41;',
+			$str
+		);
+
+		// Final clean up
+		// This adds a bit of extra precaution in case
+		// something got through the above filters
+		$str = $this->_do_never_allowed($str);
+
+		/*
+		 * Images are Handled in a Special Way
+		 * - Essentially, we want to know that after all of the character
+		 * conversion is done whether any unwanted, likely XSS, code was found.
+		 * If not, we return TRUE, as the image is clean.
+		 * However, if the string post-conversion does not matched the
+		 * string post-removal of XSS, then it fails, as there was unwanted XSS
+		 * code found and removed/changed during processing.
+		 */
+		if ($is_image === TRUE)
+		{
+			return ($str === $converted_string);
+		}
+
+		return $str;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * XSS Hash
+	 *
+	 * Generates the XSS hash if needed and returns it.
+	 *
+	 * @see		CI_Security::$_xss_hash
+	 * @return	string	XSS hash
+	 */
+	public function xss_hash()
+	{
+		if ($this->_xss_hash === NULL)
+		{
+			$rand = $this->get_random_bytes(16);
+			$this->_xss_hash = ($rand === FALSE)
+				? md5(uniqid(mt_rand(), TRUE))
+				: bin2hex($rand);
+		}
+
+		return $this->_xss_hash;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Get random bytes
+	 *
+	 * @param	int	$length	Output length
+	 * @return	string
+	 */
+	public function get_random_bytes($length)
+	{
+		if (empty($length) OR ! ctype_digit((string) $length))
+		{
+			return FALSE;
+		}
+
+		if (function_exists('random_bytes'))
+		{
+			try
+			{
+				// The cast is required to avoid TypeError
+				return random_bytes((int) $length);
+			}
+			catch (Exception $e)
+			{
+				// If random_bytes() can't do the job, we can't either ...
+				// There's no point in using fallbacks.
+				log_message('error', $e->getMessage());
+				return FALSE;
+			}
+		}
+
+		// Unfortunately, none of the following PRNGs is guaranteed to exist ...
+		if (defined('MCRYPT_DEV_URANDOM') && ($output = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM)) !== FALSE)
+		{
+			return $output;
+		}
+
+
+		if (is_readable('/dev/urandom') && ($fp = fopen('/dev/urandom', 'rb')) !== FALSE)
+		{
+			// Try not to waste entropy ...
+			is_php('5.4') && stream_set_chunk_size($fp, $length);
+			$output = fread($fp, $length);
+			fclose($fp);
+			if ($output !== FALSE)
+			{
+				return $output;
+			}
+		}
+
+		if (function_exists('openssl_random_pseudo_bytes'))
+		{
+			return openssl_random_pseudo_bytes($length);
+		}
+
+		return FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * HTML Entities Decode
+	 *
+	 * A replacement for html_entity_decode()
+	 *
+	 * The reason we are not using html_entity_decode() by itself is because
+	 * while it is not technically correct to leave out the semicolon
+	 * at the end of an entity most browsers will still interpret the entity
+	 * correctly. html_entity_decode() does not convert entities without
+	 * semicolons, so we are left with our own little solution here. Bummer.
+	 *
+	 * @link	http://php.net/html-entity-decode
+	 *
+	 * @param	string	$str		Input
+	 * @param	string	$charset	Character set
+	 * @return	string
+	 */
+	public function entity_decode($str, $charset = NULL)
+	{
+		if (strpos($str, '&') === FALSE)
+		{
+			return $str;
+		}
+
+		static $_entities;
+
+		isset($charset) OR $charset = $this->charset;
+		$flag = is_php('5.4')
+			? ENT_COMPAT | ENT_HTML5
+			: ENT_COMPAT;
+
+		if ( ! isset($_entities))
+		{
+			$_entities = array_map('strtolower', get_html_translation_table(HTML_ENTITIES, $flag, $charset));
+
+			// If we're not on PHP 5.4+, add the possibly dangerous HTML 5
+			// entities to the array manually
+			if ($flag === ENT_COMPAT)
+			{
+				$_entities[':'] = '&colon;';
+				$_entities['('] = '&lpar;';
+				$_entities[')'] = '&rpar;';
+				$_entities["\n"] = '&NewLine;';
+				$_entities["\t"] = '&Tab;';
+			}
+		}
+
+		do
+		{
+			$str_compare = $str;
+
+			// Decode standard entities, avoiding false positives
+			if (preg_match_all('/&[a-z]{2,}(?![a-z;])/i', $str, $matches))
+			{
+				$replace = array();
+				$matches = array_unique(array_map('strtolower', $matches[0]));
+				foreach ($matches as &$match)
+				{
+					if (($char = array_search($match.';', $_entities, TRUE)) !== FALSE)
+					{
+						$replace[$match] = $char;
+					}
+				}
+
+				$str = str_replace(array_keys($replace), array_values($replace), $str);
+			}
+
+			// Decode numeric & UTF16 two byte entities
+			$str = html_entity_decode(
+				preg_replace('/(&#(?:x0*[0-9a-f]{2,5}(?![0-9a-f;])|(?:0*\d{2,4}(?![0-9;]))))/iS', '$1;', $str),
+				$flag,
+				$charset
+			);
+
+			if ($flag === ENT_COMPAT)
+			{
+				$str = str_replace(array_values($_entities), array_keys($_entities), $str);
+			}
+		}
+		while ($str_compare !== $str);
+		return $str;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Sanitize Filename
+	 *
+	 * @param	string	$str		Input file name
+	 * @param 	bool	$relative_path	Whether to preserve paths
+	 * @return	string
+	 */
+	public function sanitize_filename($str, $relative_path = FALSE)
+	{
+		$bad = $this->filename_bad_chars;
+
+		if ( ! $relative_path)
+		{
+			$bad[] = './';
+			$bad[] = '/';
+		}
+
+		$str = remove_invisible_characters($str, FALSE);
+
+		do
+		{
+			$old = $str;
+			$str = str_replace($bad, '', $str);
+		}
+		while ($old !== $str);
+
+		return stripslashes($str);
+	}
+
+	// ----------------------------------------------------------------
+
+	/**
+	 * Strip Image Tags
+	 *
+	 * @param	string	$str
+	 * @return	string
+	 */
+	public function strip_image_tags($str)
+	{
+		return preg_replace(
+			array(
+				'#<img[\s/]+.*?src\s*=\s*(["\'])([^\\1]+?)\\1.*?\>#i',
+				'#<img[\s/]+.*?src\s*=\s*?(([^\s"\'=<>`]+)).*?\>#i'
+			),
+			'\\2',
+			$str
+		);
+	}
+
+	// ----------------------------------------------------------------
+
+	/**
+	 * URL-decode taking spaces into account
+	 *
+	 * @see		https://github.com/bcit-ci/CodeIgniter/issues/4877
+	 * @param	array	$matches
+	 * @return	string
+	 */
+	protected function _urldecodespaces($matches)
+	{
+		$input    = $matches[0];
+		$nospaces = preg_replace('#\s+#', '', $input);
+		return ($nospaces === $input)
+			? $input
+			: rawurldecode($nospaces);
+	}
+
+	// ----------------------------------------------------------------
+
+	/**
+	 * Compact Exploded Words
+	 *
+	 * Callback method for xss_clean() to remove whitespace from
+	 * things like 'j a v a s c r i p t'.
+	 *
+	 * @used-by	CI_Security::xss_clean()
+	 * @param	array	$matches
+	 * @return	string
+	 */
+	protected function _compact_exploded_words($matches)
+	{
+		return preg_replace('/\s+/s', '', $matches[1]).$matches[2];
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Sanitize Naughty HTML
+	 *
+	 * Callback method for xss_clean() to remove naughty HTML elements.
+	 *
+	 * @used-by	CI_Security::xss_clean()
+	 * @param	array	$matches
+	 * @return	string
+	 */
+	protected function _sanitize_naughty_html($matches)
+	{
+		static $naughty_tags    = array(
+			'alert', 'area', 'prompt', 'confirm', 'applet', 'audio', 'basefont', 'base', 'behavior', 'bgsound',
+			'blink', 'body', 'embed', 'expression', 'form', 'frameset', 'frame', 'head', 'html', 'ilayer',
+			'iframe', 'input', 'button', 'select', 'isindex', 'layer', 'link', 'meta', 'keygen', 'object',
+			'plaintext', 'style', 'script', 'textarea', 'title', 'math', 'video', 'svg', 'xml', 'xss'
+		);
+
+		static $evil_attributes = array(
+			'on\w+', 'style', 'xmlns', 'formaction', 'form', 'xlink:href', 'FSCommand', 'seekSegmentTime'
+		);
+
+		// First, escape unclosed tags
+		if (empty($matches['closeTag']))
+		{
+			return '&lt;'.$matches[1];
+		}
+		// Is the element that we caught naughty? If so, escape it
+		elseif (in_array(strtolower($matches['tagName']), $naughty_tags, TRUE))
+		{
+			return '&lt;'.$matches[1].'&gt;';
+		}
+		// For other tags, see if their attributes are "evil" and strip those
+		elseif (isset($matches['attributes']))
+		{
+			// We'll store the already fitlered attributes here
+			$attributes = array();
+
+			// Attribute-catching pattern
+			$attributes_pattern = '#'
+				.'(?<name>[^\s\042\047>/=]+)' // attribute characters
+				// optional attribute-value
+				.'(?:\s*=(?<value>[^\s\042\047=><`]+|\s*\042[^\042]*\042|\s*\047[^\047]*\047|\s*(?U:[^\s\042\047=><`]*)))' // attribute-value separator
+				.'#i';
+
+			// Blacklist pattern for evil attribute names
+			$is_evil_pattern = '#^('.implode('|', $evil_attributes).')$#i';
+
+			// Each iteration filters a single attribute
+			do
+			{
+				// Strip any non-alpha characters that may precede an attribute.
+				// Browsers often parse these incorrectly and that has been a
+				// of numerous XSS issues we've had.
+				$matches['attributes'] = preg_replace('#^[^a-z]+#i', '', $matches['attributes']);
+
+				if ( ! preg_match($attributes_pattern, $matches['attributes'], $attribute, PREG_OFFSET_CAPTURE))
+				{
+					// No (valid) attribute found? Discard everything else inside the tag
+					break;
+				}
+
+				if (
+					// Is it indeed an "evil" attribute?
+					preg_match($is_evil_pattern, $attribute['name'][0])
+					// Or does it have an equals sign, but no value and not quoted? Strip that too!
+					OR (trim($attribute['value'][0]) === '')
+				)
+				{
+					$attributes[] = 'xss=removed';
+				}
+				else
+				{
+					$attributes[] = $attribute[0][0];
+				}
+
+				$matches['attributes'] = substr($matches['attributes'], $attribute[0][1] + strlen($attribute[0][0]));
+			}
+			while ($matches['attributes'] !== '');
+
+			$attributes = empty($attributes)
+				? ''
+				: ' '.implode(' ', $attributes);
+			return '<'.$matches['slash'].$matches['tagName'].$attributes.'>';
+		}
+
+		return $matches[0];
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * JS Link Removal
+	 *
+	 * Callback method for xss_clean() to sanitize links.
+	 *
+	 * This limits the PCRE backtracks, making it more performance friendly
+	 * and prevents PREG_BACKTRACK_LIMIT_ERROR from being triggered in
+	 * PHP 5.2+ on link-heavy strings.
+	 *
+	 * @used-by	CI_Security::xss_clean()
+	 * @param	array	$match
+	 * @return	string
+	 */
+	protected function _js_link_removal($match)
+	{
+		return str_replace(
+			$match[1],
+			preg_replace(
+				'#href=.*?(?:(?:alert|prompt|confirm)(?:\(|&\#40;)|javascript:|livescript:|mocha:|charset=|window\.|document\.|\.cookie|<script|<xss|d\s*a\s*t\s*a\s*:)#si',
+				'',
+				$this->_filter_attributes($match[1])
+			),
+			$match[0]
+		);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * JS Image Removal
+	 *
+	 * Callback method for xss_clean() to sanitize image tags.
+	 *
+	 * This limits the PCRE backtracks, making it more performance friendly
+	 * and prevents PREG_BACKTRACK_LIMIT_ERROR from being triggered in
+	 * PHP 5.2+ on image tag heavy strings.
+	 *
+	 * @used-by	CI_Security::xss_clean()
+	 * @param	array	$match
+	 * @return	string
+	 */
+	protected function _js_img_removal($match)
+	{
+		return str_replace(
+			$match[1],
+			preg_replace(
+				'#src=.*?(?:(?:alert|prompt|confirm|eval)(?:\(|&\#40;)|javascript:|livescript:|mocha:|charset=|window\.|document\.|\.cookie|<script|<xss|base64\s*,)#si',
+				'',
+				$this->_filter_attributes($match[1])
+			),
+			$match[0]
+		);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Attribute Conversion
+	 *
+	 * @used-by	CI_Security::xss_clean()
+	 * @param	array	$match
+	 * @return	string
+	 */
+	protected function _convert_attribute($match)
+	{
+		return str_replace(array('>', '<', '\\'), array('&gt;', '&lt;', '\\\\'), $match[0]);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Filter Attributes
+	 *
+	 * Filters tag attributes for consistency and safety.
+	 *
+	 * @used-by	CI_Security::_js_img_removal()
+	 * @used-by	CI_Security::_js_link_removal()
+	 * @param	string	$str
+	 * @return	string
+	 */
+	protected function _filter_attributes($str)
+	{
+		$out = '';
+		if (preg_match_all('#\s*[a-z\-]+\s*=\s*(\042|\047)([^\\1]*?)\\1#is', $str, $matches))
+		{
+			foreach ($matches[0] as $match)
+			{
+				$out .= preg_replace('#/\*.*?\*/#s', '', $match);
+			}
+		}
+
+		return $out;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * HTML Entity Decode Callback
+	 *
+	 * @used-by	CI_Security::xss_clean()
+	 * @param	array	$match
+	 * @return	string
+	 */
+	protected function _decode_entity($match)
+	{
+		// Protect GET variables in URLs
+		// 901119URL5918AMP18930PROTECT8198
+		$match = preg_replace('|\&([a-z\_0-9\-]+)\=([a-z\_0-9\-/]+)|i', $this->xss_hash().'\\1=\\2', $match[0]);
+
+		// Decode, then un-protect URL GET vars
+		return str_replace(
+			$this->xss_hash(),
+			'&',
+			$this->entity_decode($match, $this->charset)
+		);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Do Never Allowed
+	 *
+	 * @used-by	CI_Security::xss_clean()
+	 * @param 	string
+	 * @return 	string
+	 */
+	protected function _do_never_allowed($str)
+	{
+		$str = str_replace(array_keys($this->_never_allowed_str), $this->_never_allowed_str, $str);
+
+		foreach ($this->_never_allowed_regex as $regex)
+		{
+			$str = preg_replace('#'.$regex.'#is', '[removed]', $str);
+		}
+
+		return $str;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Set CSRF Hash and Cookie
+	 *
+	 * @return	string
+	 */
+	protected function _csrf_set_hash()
+	{
+		if ($this->_csrf_hash === NULL)
+		{
+			// If the cookie exists we will use its value.
+			// We don't necessarily want to regenerate it with
+			// each page load since a page could contain embedded
+			// sub-pages causing this feature to fail
+			if (isset($_COOKIE[$this->_csrf_cookie_name]) && is_string($_COOKIE[$this->_csrf_cookie_name])
+				&& preg_match('#^[0-9a-f]{32}$#iS', $_COOKIE[$this->_csrf_cookie_name]) === 1)
+			{
+				return $this->_csrf_hash = $_COOKIE[$this->_csrf_cookie_name];
+			}
+
+			$rand = $this->get_random_bytes(16);
+			$this->_csrf_hash = ($rand === FALSE)
+				? md5(uniqid(mt_rand(), TRUE))
+				: bin2hex($rand);
+		}
+
+		return $this->_csrf_hash;
+	}
+
+}

+ 643 - 0
apps/system/core/URI.php

@@ -0,0 +1,643 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 1.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * URI Class
+ *
+ * Parses URIs and determines routing
+ *
+ * @package		CodeIgniter
+ * @subpackage	Libraries
+ * @category	URI
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/libraries/uri.html
+ */
+class CI_URI {
+
+	/**
+	 * List of cached URI segments
+	 *
+	 * @var	array
+	 */
+	public $keyval = array();
+
+	/**
+	 * Current URI string
+	 *
+	 * @var	string
+	 */
+	public $uri_string = '';
+
+	/**
+	 * List of URI segments
+	 *
+	 * Starts at 1 instead of 0.
+	 *
+	 * @var	array
+	 */
+	public $segments = array();
+
+	/**
+	 * List of routed URI segments
+	 *
+	 * Starts at 1 instead of 0.
+	 *
+	 * @var	array
+	 */
+	public $rsegments = array();
+
+	/**
+	 * Permitted URI chars
+	 *
+	 * PCRE character group allowed in URI segments
+	 *
+	 * @var	string
+	 */
+	protected $_permitted_uri_chars;
+
+	/**
+	 * Class constructor
+	 *
+	 * @return	void
+	 */
+	public function __construct()
+	{
+		$this->config =& load_class('Config', 'core');
+
+		// If query strings are enabled, we don't need to parse any segments.
+		// However, they don't make sense under CLI.
+		if (is_cli() OR $this->config->item('enable_query_strings') !== TRUE)
+		{
+			$this->_permitted_uri_chars = $this->config->item('permitted_uri_chars');
+
+			// If it's a CLI request, ignore the configuration
+			if (is_cli())
+			{
+				$uri = $this->_parse_argv();
+			}
+			else
+			{
+				$protocol = $this->config->item('uri_protocol');
+				empty($protocol) && $protocol = 'REQUEST_URI';
+
+				switch ($protocol)
+				{
+					case 'AUTO': // For BC purposes only
+					case 'REQUEST_URI':
+						$uri = $this->_parse_request_uri();
+						break;
+					case 'QUERY_STRING':
+						$uri = $this->_parse_query_string();
+						break;
+					case 'PATH_INFO':
+					default:
+						$uri = isset($_SERVER[$protocol])
+							? $_SERVER[$protocol]
+							: $this->_parse_request_uri();
+						break;
+				}
+			}
+
+			$this->_set_uri_string($uri);
+		}
+
+		log_message('info', 'URI Class Initialized');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Set URI String
+	 *
+	 * @param 	string	$str
+	 * @return	void
+	 */
+	protected function _set_uri_string($str)
+	{
+		// Filter out control characters and trim slashes
+		$this->uri_string = trim(remove_invisible_characters($str, FALSE), '/');
+
+		if ($this->uri_string !== '')
+		{
+			// Remove the URL suffix, if present
+			if (($suffix = (string) $this->config->item('url_suffix')) !== '')
+			{
+				$slen = strlen($suffix);
+
+				if (substr($this->uri_string, -$slen) === $suffix)
+				{
+					$this->uri_string = substr($this->uri_string, 0, -$slen);
+				}
+			}
+
+			$this->segments[0] = NULL;
+			// Populate the segments array
+			foreach (explode('/', trim($this->uri_string, '/')) as $val)
+			{
+				$val = trim($val);
+				// Filter segments for security
+				$this->filter_uri($val);
+
+				if ($val !== '')
+				{
+					$this->segments[] = $val;
+				}
+			}
+
+			unset($this->segments[0]);
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Parse REQUEST_URI
+	 *
+	 * Will parse REQUEST_URI and automatically detect the URI from it,
+	 * while fixing the query string if necessary.
+	 *
+	 * @return	string
+	 */
+	protected function _parse_request_uri()
+	{
+		if ( ! isset($_SERVER['REQUEST_URI'], $_SERVER['SCRIPT_NAME']))
+		{
+			return '';
+		}
+
+		// parse_url() returns false if no host is present, but the path or query string
+		// contains a colon followed by a number
+		$uri = parse_url('http://dummy'.$_SERVER['REQUEST_URI']);
+		$query = isset($uri['query']) ? $uri['query'] : '';
+		$uri = isset($uri['path']) ? $uri['path'] : '';
+
+		if (isset($_SERVER['SCRIPT_NAME'][0]))
+		{
+			if (strpos($uri, $_SERVER['SCRIPT_NAME']) === 0)
+			{
+				$uri = (string) substr($uri, strlen($_SERVER['SCRIPT_NAME']));
+			}
+			elseif (strpos($uri, dirname($_SERVER['SCRIPT_NAME'])) === 0)
+			{
+				$uri = (string) substr($uri, strlen(dirname($_SERVER['SCRIPT_NAME'])));
+			}
+		}
+
+		// This section ensures that even on servers that require the URI to be in the query string (Nginx) a correct
+		// URI is found, and also fixes the QUERY_STRING server var and $_GET array.
+		if (trim($uri, '/') === '' && strncmp($query, '/', 1) === 0)
+		{
+			$query = explode('?', $query, 2);
+			$uri = $query[0];
+			$_SERVER['QUERY_STRING'] = isset($query[1]) ? $query[1] : '';
+		}
+		else
+		{
+			$_SERVER['QUERY_STRING'] = $query;
+		}
+
+		parse_str($_SERVER['QUERY_STRING'], $_GET);
+
+		if ($uri === '/' OR $uri === '')
+		{
+			return '/';
+		}
+
+		// Do some final cleaning of the URI and return it
+		return $this->_remove_relative_directory($uri);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Parse QUERY_STRING
+	 *
+	 * Will parse QUERY_STRING and automatically detect the URI from it.
+	 *
+	 * @return	string
+	 */
+	protected function _parse_query_string()
+	{
+		$uri = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : @getenv('QUERY_STRING');
+
+		if (trim($uri, '/') === '')
+		{
+			return '';
+		}
+		elseif (strncmp($uri, '/', 1) === 0)
+		{
+			$uri = explode('?', $uri, 2);
+			$_SERVER['QUERY_STRING'] = isset($uri[1]) ? $uri[1] : '';
+			$uri = $uri[0];
+		}
+
+		parse_str($_SERVER['QUERY_STRING'], $_GET);
+
+		return $this->_remove_relative_directory($uri);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Parse CLI arguments
+	 *
+	 * Take each command line argument and assume it is a URI segment.
+	 *
+	 * @return	string
+	 */
+	protected function _parse_argv()
+	{
+		$args = array_slice($_SERVER['argv'], 1);
+		return $args ? implode('/', $args) : '';
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Remove relative directory (../) and multi slashes (///)
+	 *
+	 * Do some final cleaning of the URI and return it, currently only used in self::_parse_request_uri()
+	 *
+	 * @param	string	$uri
+	 * @return	string
+	 */
+	protected function _remove_relative_directory($uri)
+	{
+		$uris = array();
+		$tok = strtok($uri, '/');
+		while ($tok !== FALSE)
+		{
+			if (( ! empty($tok) OR $tok === '0') && $tok !== '..')
+			{
+				$uris[] = $tok;
+			}
+			$tok = strtok('/');
+		}
+
+		return implode('/', $uris);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Filter URI
+	 *
+	 * Filters segments for malicious characters.
+	 *
+	 * @param	string	$str
+	 * @return	void
+	 */
+	public function filter_uri(&$str)
+	{
+		if ( ! empty($str) && ! empty($this->_permitted_uri_chars) && ! preg_match('/^['.$this->_permitted_uri_chars.']+$/i'.(UTF8_ENABLED ? 'u' : ''), $str))
+		{
+			show_error('The URI you submitted has disallowed characters.', 400);
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Fetch URI Segment
+	 *
+	 * @see		CI_URI::$segments
+	 * @param	int		$n		Index
+	 * @param	mixed		$no_result	What to return if the segment index is not found
+	 * @return	mixed
+	 */
+	public function segment($n, $no_result = NULL)
+	{
+		return isset($this->segments[$n]) ? $this->segments[$n] : $no_result;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Fetch URI "routed" Segment
+	 *
+	 * Returns the re-routed URI segment (assuming routing rules are used)
+	 * based on the index provided. If there is no routing, will return
+	 * the same result as CI_URI::segment().
+	 *
+	 * @see		CI_URI::$rsegments
+	 * @see		CI_URI::segment()
+	 * @param	int		$n		Index
+	 * @param	mixed		$no_result	What to return if the segment index is not found
+	 * @return	mixed
+	 */
+	public function rsegment($n, $no_result = NULL)
+	{
+		return isset($this->rsegments[$n]) ? $this->rsegments[$n] : $no_result;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * URI to assoc
+	 *
+	 * Generates an associative array of URI data starting at the supplied
+	 * segment index. For example, if this is your URI:
+	 *
+	 *	example.com/user/search/name/joe/location/UK/gender/male
+	 *
+	 * You can use this method to generate an array with this prototype:
+	 *
+	 *	array (
+	 *		name => joe
+	 *		location => UK
+	 *		gender => male
+	 *	 )
+	 *
+	 * @param	int	$n		Index (default: 3)
+	 * @param	array	$default	Default values
+	 * @return	array
+	 */
+	public function uri_to_assoc($n = 3, $default = array())
+	{
+		return $this->_uri_to_assoc($n, $default, 'segment');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Routed URI to assoc
+	 *
+	 * Identical to CI_URI::uri_to_assoc(), only it uses the re-routed
+	 * segment array.
+	 *
+	 * @see		CI_URI::uri_to_assoc()
+	 * @param 	int	$n		Index (default: 3)
+	 * @param 	array	$default	Default values
+	 * @return 	array
+	 */
+	public function ruri_to_assoc($n = 3, $default = array())
+	{
+		return $this->_uri_to_assoc($n, $default, 'rsegment');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Internal URI-to-assoc
+	 *
+	 * Generates a key/value pair from the URI string or re-routed URI string.
+	 *
+	 * @used-by	CI_URI::uri_to_assoc()
+	 * @used-by	CI_URI::ruri_to_assoc()
+	 * @param	int	$n		Index (default: 3)
+	 * @param	array	$default	Default values
+	 * @param	string	$which		Array name ('segment' or 'rsegment')
+	 * @return	array
+	 */
+	protected function _uri_to_assoc($n = 3, $default = array(), $which = 'segment')
+	{
+		if ( ! is_numeric($n))
+		{
+			return $default;
+		}
+
+		if (isset($this->keyval[$which], $this->keyval[$which][$n]))
+		{
+			return $this->keyval[$which][$n];
+		}
+
+		$total_segments = "total_{$which}s";
+		$segment_array = "{$which}_array";
+
+		if ($this->$total_segments() < $n)
+		{
+			return (count($default) === 0)
+				? array()
+				: array_fill_keys($default, NULL);
+		}
+
+		$segments = array_slice($this->$segment_array(), ($n - 1));
+		$i = 0;
+		$lastval = '';
+		$retval = array();
+		foreach ($segments as $seg)
+		{
+			if ($i % 2)
+			{
+				$retval[$lastval] = $seg;
+			}
+			else
+			{
+				$retval[$seg] = NULL;
+				$lastval = $seg;
+			}
+
+			$i++;
+		}
+
+		if (count($default) > 0)
+		{
+			foreach ($default as $val)
+			{
+				if ( ! array_key_exists($val, $retval))
+				{
+					$retval[$val] = NULL;
+				}
+			}
+		}
+
+		// Cache the array for reuse
+		isset($this->keyval[$which]) OR $this->keyval[$which] = array();
+		$this->keyval[$which][$n] = $retval;
+		return $retval;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Assoc to URI
+	 *
+	 * Generates a URI string from an associative array.
+	 *
+	 * @param	array	$array	Input array of key/value pairs
+	 * @return	string	URI string
+	 */
+	public function assoc_to_uri($array)
+	{
+		$temp = array();
+		foreach ((array) $array as $key => $val)
+		{
+			$temp[] = $key;
+			$temp[] = $val;
+		}
+
+		return implode('/', $temp);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Slash segment
+	 *
+	 * Fetches an URI segment with a slash.
+	 *
+	 * @param	int	$n	Index
+	 * @param	string	$where	Where to add the slash ('trailing' or 'leading')
+	 * @return	string
+	 */
+	public function slash_segment($n, $where = 'trailing')
+	{
+		return $this->_slash_segment($n, $where, 'segment');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Slash routed segment
+	 *
+	 * Fetches an URI routed segment with a slash.
+	 *
+	 * @param	int	$n	Index
+	 * @param	string	$where	Where to add the slash ('trailing' or 'leading')
+	 * @return	string
+	 */
+	public function slash_rsegment($n, $where = 'trailing')
+	{
+		return $this->_slash_segment($n, $where, 'rsegment');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Internal Slash segment
+	 *
+	 * Fetches an URI Segment and adds a slash to it.
+	 *
+	 * @used-by	CI_URI::slash_segment()
+	 * @used-by	CI_URI::slash_rsegment()
+	 *
+	 * @param	int	$n	Index
+	 * @param	string	$where	Where to add the slash ('trailing' or 'leading')
+	 * @param	string	$which	Array name ('segment' or 'rsegment')
+	 * @return	string
+	 */
+	protected function _slash_segment($n, $where = 'trailing', $which = 'segment')
+	{
+		$leading = $trailing = '/';
+
+		if ($where === 'trailing')
+		{
+			$leading	= '';
+		}
+		elseif ($where === 'leading')
+		{
+			$trailing	= '';
+		}
+
+		return $leading.$this->$which($n).$trailing;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Segment Array
+	 *
+	 * @return	array	CI_URI::$segments
+	 */
+	public function segment_array()
+	{
+		return $this->segments;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Routed Segment Array
+	 *
+	 * @return	array	CI_URI::$rsegments
+	 */
+	public function rsegment_array()
+	{
+		return $this->rsegments;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Total number of segments
+	 *
+	 * @return	int
+	 */
+	public function total_segments()
+	{
+		return count($this->segments);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Total number of routed segments
+	 *
+	 * @return	int
+	 */
+	public function total_rsegments()
+	{
+		return count($this->rsegments);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Fetch URI string
+	 *
+	 * @return	string	CI_URI::$uri_string
+	 */
+	public function uri_string()
+	{
+		return $this->uri_string;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Fetch Re-routed URI string
+	 *
+	 * @return	string
+	 */
+	public function ruri_string()
+	{
+		return ltrim(load_class('Router', 'core')->directory, '/').implode('/', $this->rsegments);
+	}
+
+}

+ 164 - 0
apps/system/core/Utf8.php

@@ -0,0 +1,164 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 2.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * Utf8 Class
+ *
+ * Provides support for UTF-8 environments
+ *
+ * @package		CodeIgniter
+ * @subpackage	Libraries
+ * @category	UTF-8
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/libraries/utf8.html
+ */
+class CI_Utf8 {
+
+	/**
+	 * Class constructor
+	 *
+	 * Determines if UTF-8 support is to be enabled.
+	 *
+	 * @return	void
+	 */
+	public function __construct()
+	{
+		if (
+			defined('PREG_BAD_UTF8_ERROR')				// PCRE must support UTF-8
+			&& (ICONV_ENABLED === TRUE OR MB_ENABLED === TRUE)	// iconv or mbstring must be installed
+			&& strtoupper(config_item('charset')) === 'UTF-8'	// Application charset must be UTF-8
+			)
+		{
+			define('UTF8_ENABLED', TRUE);
+			log_message('debug', 'UTF-8 Support Enabled');
+		}
+		else
+		{
+			define('UTF8_ENABLED', FALSE);
+			log_message('debug', 'UTF-8 Support Disabled');
+		}
+
+		log_message('info', 'Utf8 Class Initialized');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Clean UTF-8 strings
+	 *
+	 * Ensures strings contain only valid UTF-8 characters.
+	 *
+	 * @param	string	$str	String to clean
+	 * @return	string
+	 */
+	public function clean_string($str)
+	{
+		if ($this->is_ascii($str) === FALSE)
+		{
+			if (MB_ENABLED)
+			{
+				$str = mb_convert_encoding($str, 'UTF-8', 'UTF-8');
+			}
+			elseif (ICONV_ENABLED)
+			{
+				$str = @iconv('UTF-8', 'UTF-8//IGNORE', $str);
+			}
+		}
+
+		return $str;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Remove ASCII control characters
+	 *
+	 * Removes all ASCII control characters except horizontal tabs,
+	 * line feeds, and carriage returns, as all others can cause
+	 * problems in XML.
+	 *
+	 * @param	string	$str	String to clean
+	 * @return	string
+	 */
+	public function safe_ascii_for_xml($str)
+	{
+		return remove_invisible_characters($str, FALSE);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Convert to UTF-8
+	 *
+	 * Attempts to convert a string to UTF-8.
+	 *
+	 * @param	string	$str		Input string
+	 * @param	string	$encoding	Input encoding
+	 * @return	string	$str encoded in UTF-8 or FALSE on failure
+	 */
+	public function convert_to_utf8($str, $encoding)
+	{
+		if (MB_ENABLED)
+		{
+			return mb_convert_encoding($str, 'UTF-8', $encoding);
+		}
+		elseif (ICONV_ENABLED)
+		{
+			return @iconv($encoding, 'UTF-8', $str);
+		}
+
+		return FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Is ASCII?
+	 *
+	 * Tests if a string is standard 7-bit ASCII or not.
+	 *
+	 * @param	string	$str	String to check
+	 * @return	bool
+	 */
+	public function is_ascii($str)
+	{
+		return (preg_match('/[^\x00-\x7F]/S', $str) === 0);
+	}
+
+}

+ 254 - 0
apps/system/core/compat/hash.php

@@ -0,0 +1,254 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 3.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * PHP ext/hash compatibility package
+ *
+ * @package		CodeIgniter
+ * @subpackage	CodeIgniter
+ * @category	Compatibility
+ * @author		Andrey Andreev
+ * @link		https://codeigniter.com/user_guide/
+ * @link		http://php.net/hash
+ */
+
+// ------------------------------------------------------------------------
+
+if (is_php('5.6'))
+{
+	return;
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('hash_equals'))
+{
+	/**
+	 * hash_equals()
+	 *
+	 * @link	http://php.net/hash_equals
+	 * @param	string	$known_string
+	 * @param	string	$user_string
+	 * @return	bool
+	 */
+	function hash_equals($known_string, $user_string)
+	{
+		if ( ! is_string($known_string))
+		{
+			trigger_error('hash_equals(): Expected known_string to be a string, '.strtolower(gettype($known_string)).' given', E_USER_WARNING);
+			return FALSE;
+		}
+		elseif ( ! is_string($user_string))
+		{
+			trigger_error('hash_equals(): Expected user_string to be a string, '.strtolower(gettype($user_string)).' given', E_USER_WARNING);
+			return FALSE;
+		}
+		elseif (($length = strlen($known_string)) !== strlen($user_string))
+		{
+			return FALSE;
+		}
+
+		$diff = 0;
+		for ($i = 0; $i < $length; $i++)
+		{
+			$diff |= ord($known_string[$i]) ^ ord($user_string[$i]);
+		}
+
+		return ($diff === 0);
+	}
+}
+
+// ------------------------------------------------------------------------
+
+if (is_php('5.5'))
+{
+	return;
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('hash_pbkdf2'))
+{
+	/**
+	 * hash_pbkdf2()
+	 *
+	 * @link	http://php.net/hash_pbkdf2
+	 * @param	string	$algo
+	 * @param	string	$password
+	 * @param	string	$salt
+	 * @param	int	$iterations
+	 * @param	int	$length
+	 * @param	bool	$raw_output
+	 * @return	string
+	 */
+	function hash_pbkdf2($algo, $password, $salt, $iterations, $length = 0, $raw_output = FALSE)
+	{
+		if ( ! in_array(strtolower($algo), hash_algos(), TRUE))
+		{
+			trigger_error('hash_pbkdf2(): Unknown hashing algorithm: '.$algo, E_USER_WARNING);
+			return FALSE;
+		}
+
+		if (($type = gettype($iterations)) !== 'integer')
+		{
+			if ($type === 'object' && method_exists($iterations, '__toString'))
+			{
+				$iterations = (string) $iterations;
+			}
+
+			if (is_string($iterations) && is_numeric($iterations))
+			{
+				$iterations = (int) $iterations;
+			}
+			else
+			{
+				trigger_error('hash_pbkdf2() expects parameter 4 to be long, '.$type.' given', E_USER_WARNING);
+				return NULL;
+			}
+		}
+
+		if ($iterations < 1)
+		{
+			trigger_error('hash_pbkdf2(): Iterations must be a positive integer: '.$iterations, E_USER_WARNING);
+			return FALSE;
+		}
+
+		if (($type = gettype($length)) !== 'integer')
+		{
+			if ($type === 'object' && method_exists($length, '__toString'))
+			{
+				$length = (string) $length;
+			}
+
+			if (is_string($length) && is_numeric($length))
+			{
+				$length = (int) $length;
+			}
+			else
+			{
+				trigger_error('hash_pbkdf2() expects parameter 5 to be long, '.$type.' given', E_USER_WARNING);
+				return NULL;
+			}
+		}
+
+		if ($length < 0)
+		{
+			trigger_error('hash_pbkdf2(): Length must be greater than or equal to 0: '.$length, E_USER_WARNING);
+			return FALSE;
+		}
+
+		$hash_length = defined('MB_OVERLOAD_STRING')
+			? mb_strlen(hash($algo, NULL, TRUE), '8bit')
+			: strlen(hash($algo, NULL, TRUE));
+		empty($length) && $length = $hash_length;
+
+		// Pre-hash password inputs longer than the algorithm's block size
+		// (i.e. prepare HMAC key) to mitigate potential DoS attacks.
+		static $block_sizes;
+		empty($block_sizes) && $block_sizes = array(
+			'gost' => 32,
+			'haval128,3' => 128,
+			'haval160,3' => 128,
+			'haval192,3' => 128,
+			'haval224,3' => 128,
+			'haval256,3' => 128,
+			'haval128,4' => 128,
+			'haval160,4' => 128,
+			'haval192,4' => 128,
+			'haval224,4' => 128,
+			'haval256,4' => 128,
+			'haval128,5' => 128,
+			'haval160,5' => 128,
+			'haval192,5' => 128,
+			'haval224,5' => 128,
+			'haval256,5' => 128,
+			'md2' => 16,
+			'md4' => 64,
+			'md5' => 64,
+			'ripemd128' => 64,
+			'ripemd160' => 64,
+			'ripemd256' => 64,
+			'ripemd320' => 64,
+			'salsa10' => 64,
+			'salsa20' => 64,
+			'sha1' => 64,
+			'sha224' => 64,
+			'sha256' => 64,
+			'sha384' => 128,
+			'sha512' => 128,
+			'snefru' => 32,
+			'snefru256' => 32,
+			'tiger128,3' => 64,
+			'tiger160,3' => 64,
+			'tiger192,3' => 64,
+			'tiger128,4' => 64,
+			'tiger160,4' => 64,
+			'tiger192,4' => 64,
+			'whirlpool' => 64
+		);
+
+		if (isset($block_sizes[$algo], $password[$block_sizes[$algo]]))
+		{
+			$password = hash($algo, $password, TRUE);
+		}
+
+		$hash = '';
+		// Note: Blocks are NOT 0-indexed
+		for ($bc = (int) ceil($length / $hash_length), $bi = 1; $bi <= $bc; $bi++)
+		{
+			$key = $derived_key = hash_hmac($algo, $salt.pack('N', $bi), $password, TRUE);
+			for ($i = 1; $i < $iterations; $i++)
+			{
+				$derived_key ^= $key = hash_hmac($algo, $key, $password, TRUE);
+			}
+
+			$hash .= $derived_key;
+		}
+
+		// This is not RFC-compatible, but we're aiming for natural PHP compatibility
+		if ( ! $raw_output)
+		{
+			$hash = bin2hex($hash);
+		}
+
+		return defined('MB_OVERLOAD_STRING')
+			? mb_substr($hash, 0, $length, '8bit')
+			: substr($hash, 0, $length);
+	}
+}

+ 11 - 0
apps/system/core/compat/index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>403 Forbidden</title>
+</head>
+<body>
+
+<p>Directory access is forbidden.</p>
+
+</body>
+</html>

+ 149 - 0
apps/system/core/compat/mbstring.php

@@ -0,0 +1,149 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 3.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * PHP ext/mbstring compatibility package
+ *
+ * @package		CodeIgniter
+ * @subpackage	CodeIgniter
+ * @category	Compatibility
+ * @author		Andrey Andreev
+ * @link		https://codeigniter.com/user_guide/
+ * @link		http://php.net/mbstring
+ */
+
+// ------------------------------------------------------------------------
+
+if (MB_ENABLED === TRUE)
+{
+	return;
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('mb_strlen'))
+{
+	/**
+	 * mb_strlen()
+	 *
+	 * WARNING: This function WILL fall-back to strlen()
+	 * if iconv is not available!
+	 *
+	 * @link	http://php.net/mb_strlen
+	 * @param	string	$str
+	 * @param	string	$encoding
+	 * @return	int
+	 */
+	function mb_strlen($str, $encoding = NULL)
+	{
+		if (ICONV_ENABLED === TRUE)
+		{
+			return iconv_strlen($str, isset($encoding) ? $encoding : config_item('charset'));
+		}
+
+		log_message('debug', 'Compatibility (mbstring): iconv_strlen() is not available, falling back to strlen().');
+		return strlen($str);
+	}
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('mb_strpos'))
+{
+	/**
+	 * mb_strpos()
+	 *
+	 * WARNING: This function WILL fall-back to strpos()
+	 * if iconv is not available!
+	 *
+	 * @link	http://php.net/mb_strpos
+	 * @param	string	$haystack
+	 * @param	string	$needle
+	 * @param	int	$offset
+	 * @param	string	$encoding
+	 * @return	mixed
+	 */
+	function mb_strpos($haystack, $needle, $offset = 0, $encoding = NULL)
+	{
+		if (ICONV_ENABLED === TRUE)
+		{
+			return iconv_strpos($haystack, $needle, $offset, isset($encoding) ? $encoding : config_item('charset'));
+		}
+
+		log_message('debug', 'Compatibility (mbstring): iconv_strpos() is not available, falling back to strpos().');
+		return strpos($haystack, $needle, $offset);
+	}
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('mb_substr'))
+{
+	/**
+	 * mb_substr()
+	 *
+	 * WARNING: This function WILL fall-back to substr()
+	 * if iconv is not available.
+	 *
+	 * @link	http://php.net/mb_substr
+	 * @param	string	$str
+	 * @param	int	$start
+	 * @param	int 	$length
+	 * @param	string	$encoding
+	 * @return	string
+	 */
+	function mb_substr($str, $start, $length = NULL, $encoding = NULL)
+	{
+		if (ICONV_ENABLED === TRUE)
+		{
+			isset($encoding) OR $encoding = config_item('charset');
+			return iconv_substr(
+				$str,
+				$start,
+				isset($length) ? $length : iconv_strlen($str, $encoding), // NULL doesn't work
+				$encoding
+			);
+		}
+
+		log_message('debug', 'Compatibility (mbstring): iconv_substr() is not available, falling back to substr().');
+		return isset($length)
+			? substr($str, $start, $length)
+			: substr($str, $start);
+	}
+}

+ 251 - 0
apps/system/core/compat/password.php

@@ -0,0 +1,251 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 3.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * PHP ext/standard/password compatibility package
+ *
+ * @package		CodeIgniter
+ * @subpackage	CodeIgniter
+ * @category	Compatibility
+ * @author		Andrey Andreev
+ * @link		https://codeigniter.com/user_guide/
+ * @link		http://php.net/password
+ */
+
+// ------------------------------------------------------------------------
+
+if (is_php('5.5') OR ! defined('CRYPT_BLOWFISH') OR CRYPT_BLOWFISH !== 1 OR defined('HHVM_VERSION'))
+{
+	return;
+}
+
+// ------------------------------------------------------------------------
+
+defined('PASSWORD_BCRYPT') OR define('PASSWORD_BCRYPT', 1);
+defined('PASSWORD_DEFAULT') OR define('PASSWORD_DEFAULT', PASSWORD_BCRYPT);
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('password_get_info'))
+{
+	/**
+	 * password_get_info()
+	 *
+	 * @link	http://php.net/password_get_info
+	 * @param	string	$hash
+	 * @return	array
+	 */
+	function password_get_info($hash)
+	{
+		return (strlen($hash) < 60 OR sscanf($hash, '$2y$%d', $hash) !== 1)
+			? array('algo' => 0, 'algoName' => 'unknown', 'options' => array())
+			: array('algo' => 1, 'algoName' => 'bcrypt', 'options' => array('cost' => $hash));
+	}
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('password_hash'))
+{
+	/**
+	 * password_hash()
+	 *
+	 * @link	http://php.net/password_hash
+	 * @param	string	$password
+	 * @param	int	$algo
+	 * @param	array	$options
+	 * @return	mixed
+	 */
+	function password_hash($password, $algo, array $options = array())
+	{
+		static $func_overload;
+		isset($func_overload) OR $func_overload = (extension_loaded('mbstring') && ini_get('mbstring.func_overload'));
+
+		if ($algo !== 1)
+		{
+			trigger_error('password_hash(): Unknown hashing algorithm: '.(int) $algo, E_USER_WARNING);
+			return NULL;
+		}
+
+		if (isset($options['cost']) && ($options['cost'] < 4 OR $options['cost'] > 31))
+		{
+			trigger_error('password_hash(): Invalid bcrypt cost parameter specified: '.(int) $options['cost'], E_USER_WARNING);
+			return NULL;
+		}
+
+		if (isset($options['salt']) && ($saltlen = ($func_overload ? mb_strlen($options['salt'], '8bit') : strlen($options['salt']))) < 22)
+		{
+			trigger_error('password_hash(): Provided salt is too short: '.$saltlen.' expecting 22', E_USER_WARNING);
+			return NULL;
+		}
+		elseif ( ! isset($options['salt']))
+		{
+			if (function_exists('random_bytes'))
+			{
+				try
+				{
+					$options['salt'] = random_bytes(16);
+				}
+				catch (Exception $e)
+				{
+					log_message('error', 'compat/password: Error while trying to use random_bytes(): '.$e->getMessage());
+					return FALSE;
+				}
+			}
+			elseif (defined('MCRYPT_DEV_URANDOM'))
+			{
+				$options['salt'] = mcrypt_create_iv(16, MCRYPT_DEV_URANDOM);
+			}
+			elseif (DIRECTORY_SEPARATOR === '/' && (is_readable($dev = '/dev/arandom') OR is_readable($dev = '/dev/urandom')))
+			{
+				if (($fp = fopen($dev, 'rb')) === FALSE)
+				{
+					log_message('error', 'compat/password: Unable to open '.$dev.' for reading.');
+					return FALSE;
+				}
+
+				// Try not to waste entropy ...
+				is_php('5.4') && stream_set_chunk_size($fp, 16);
+
+				$options['salt'] = '';
+				for ($read = 0; $read < 16; $read = ($func_overload) ? mb_strlen($options['salt'], '8bit') : strlen($options['salt']))
+				{
+					if (($read = fread($fp, 16 - $read)) === FALSE)
+					{
+						log_message('error', 'compat/password: Error while reading from '.$dev.'.');
+						return FALSE;
+					}
+					$options['salt'] .= $read;
+				}
+
+				fclose($fp);
+			}
+			elseif (function_exists('openssl_random_pseudo_bytes'))
+			{
+				$is_secure = NULL;
+				$options['salt'] = openssl_random_pseudo_bytes(16, $is_secure);
+				if ($is_secure !== TRUE)
+				{
+					log_message('error', 'compat/password: openssl_random_pseudo_bytes() set the $cryto_strong flag to FALSE');
+					return FALSE;
+				}
+			}
+			else
+			{
+				log_message('error', 'compat/password: No CSPRNG available.');
+				return FALSE;
+			}
+
+			$options['salt'] = str_replace('+', '.', rtrim(base64_encode($options['salt']), '='));
+		}
+		elseif ( ! preg_match('#^[a-zA-Z0-9./]+$#D', $options['salt']))
+		{
+			$options['salt'] = str_replace('+', '.', rtrim(base64_encode($options['salt']), '='));
+		}
+
+		isset($options['cost']) OR $options['cost'] = 10;
+
+		return (strlen($password = crypt($password, sprintf('$2y$%02d$%s', $options['cost'], $options['salt']))) === 60)
+			? $password
+			: FALSE;
+	}
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('password_needs_rehash'))
+{
+	/**
+	 * password_needs_rehash()
+	 *
+	 * @link	http://php.net/password_needs_rehash
+	 * @param	string	$hash
+	 * @param	int	$algo
+	 * @param	array	$options
+	 * @return	bool
+	 */
+	function password_needs_rehash($hash, $algo, array $options = array())
+	{
+		$info = password_get_info($hash);
+
+		if ($algo !== $info['algo'])
+		{
+			return TRUE;
+		}
+		elseif ($algo === 1)
+		{
+			$options['cost'] = isset($options['cost']) ? (int) $options['cost'] : 10;
+			return ($info['options']['cost'] !== $options['cost']);
+		}
+
+		// Odd at first glance, but according to a comment in PHP's own unit tests,
+		// because it is an unknown algorithm - it's valid and therefore doesn't
+		// need rehashing.
+		return FALSE;
+	}
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('password_verify'))
+{
+	/**
+	 * password_verify()
+	 *
+	 * @link	http://php.net/password_verify
+	 * @param	string	$password
+	 * @param	string	$hash
+	 * @return	bool
+	 */
+	function password_verify($password, $hash)
+	{
+		if (strlen($hash) !== 60 OR strlen($password = crypt($password, $hash)) !== 60)
+		{
+			return FALSE;
+		}
+
+		$compare = 0;
+		for ($i = 0; $i < 60; $i++)
+		{
+			$compare |= (ord($password[$i]) ^ ord($hash[$i]));
+		}
+
+		return ($compare === 0);
+	}
+}

+ 182 - 0
apps/system/core/compat/standard.php

@@ -0,0 +1,182 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 3.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * PHP ext/standard compatibility package
+ *
+ * @package		CodeIgniter
+ * @subpackage	CodeIgniter
+ * @category	Compatibility
+ * @author		Andrey Andreev
+ * @link		https://codeigniter.com/user_guide/
+ */
+
+// ------------------------------------------------------------------------
+
+if (is_php('5.5'))
+{
+	return;
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('array_column'))
+{
+	/**
+	 * array_column()
+	 *
+	 * @link	http://php.net/array_column
+	 * @param	array	$array
+	 * @param	mixed	$column_key
+	 * @param	mixed	$index_key
+	 * @return	array
+	 */
+	function array_column(array $array, $column_key, $index_key = NULL)
+	{
+		if ( ! in_array($type = gettype($column_key), array('integer', 'string', 'NULL'), TRUE))
+		{
+			if ($type === 'double')
+			{
+				$column_key = (int) $column_key;
+			}
+			elseif ($type === 'object' && method_exists($column_key, '__toString'))
+			{
+				$column_key = (string) $column_key;
+			}
+			else
+			{
+				trigger_error('array_column(): The column key should be either a string or an integer', E_USER_WARNING);
+				return FALSE;
+			}
+		}
+
+		if ( ! in_array($type = gettype($index_key), array('integer', 'string', 'NULL'), TRUE))
+		{
+			if ($type === 'double')
+			{
+				$index_key = (int) $index_key;
+			}
+			elseif ($type === 'object' && method_exists($index_key, '__toString'))
+			{
+				$index_key = (string) $index_key;
+			}
+			else
+			{
+				trigger_error('array_column(): The index key should be either a string or an integer', E_USER_WARNING);
+				return FALSE;
+			}
+		}
+
+		$result = array();
+		foreach ($array as &$a)
+		{
+			if ($column_key === NULL)
+			{
+				$value = $a;
+			}
+			elseif (is_array($a) && array_key_exists($column_key, $a))
+			{
+				$value = $a[$column_key];
+			}
+			else
+			{
+				continue;
+			}
+
+			if ($index_key === NULL OR ! array_key_exists($index_key, $a))
+			{
+				$result[] = $value;
+			}
+			else
+			{
+				$result[$a[$index_key]] = $value;
+			}
+		}
+
+		return $result;
+	}
+}
+
+// ------------------------------------------------------------------------
+
+if (is_php('5.4'))
+{
+	return;
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('hex2bin'))
+{
+	/**
+	 * hex2bin()
+	 *
+	 * @link	http://php.net/hex2bin
+	 * @param	string	$data
+	 * @return	string
+	 */
+	function hex2bin($data)
+	{
+		if (in_array($type = gettype($data), array('array', 'double', 'object', 'resource'), TRUE))
+		{
+			if ($type === 'object' && method_exists($data, '__toString'))
+			{
+				$data = (string) $data;
+			}
+			else
+			{
+				trigger_error('hex2bin() expects parameter 1 to be string, '.$type.' given', E_USER_WARNING);
+				return NULL;
+			}
+		}
+
+		if (strlen($data) % 2 !== 0)
+		{
+			trigger_error('Hexadecimal input string must have an even length', E_USER_WARNING);
+			return FALSE;
+		}
+		elseif ( ! preg_match('/^[0-9a-f]*$/i', $data))
+		{
+			trigger_error('Input string must be hexadecimal string', E_USER_WARNING);
+			return FALSE;
+		}
+
+		return pack('H*', $data);
+	}
+}

+ 11 - 0
apps/system/core/index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>403 Forbidden</title>
+</head>
+<body>
+
+<p>Directory access is forbidden.</p>
+
+</body>
+</html>

+ 218 - 0
apps/system/database/DB.php

@@ -0,0 +1,218 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 1.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * Initialize the database
+ *
+ * @category	Database
+ * @author	EllisLab Dev Team
+ * @link	https://codeigniter.com/user_guide/database/
+ *
+ * @param 	string|string[]	$params
+ * @param 	bool		$query_builder_override
+ *				Determines if query builder should be used or not
+ */
+function &DB($params = '', $query_builder_override = NULL)
+{
+	// Load the DB config file if a DSN string wasn't passed
+	if (is_string($params) && strpos($params, '://') === FALSE)
+	{
+		// Is the config file in the environment folder?
+		if ( ! file_exists($file_path = APPPATH.'config/'.ENVIRONMENT.'/database.php')
+			&& ! file_exists($file_path = APPPATH.'config/database.php'))
+		{
+			show_error('The configuration file database.php does not exist.');
+		}
+
+		include($file_path);
+
+		// Make packages contain database config files,
+		// given that the controller instance already exists
+		if (class_exists('CI_Controller', FALSE))
+		{
+			foreach (get_instance()->load->get_package_paths() as $path)
+			{
+				if ($path !== APPPATH)
+				{
+					if (file_exists($file_path = $path.'config/'.ENVIRONMENT.'/database.php'))
+					{
+						include($file_path);
+					}
+					elseif (file_exists($file_path = $path.'config/database.php'))
+					{
+						include($file_path);
+					}
+				}
+			}
+		}
+
+		if ( ! isset($db) OR count($db) === 0)
+		{
+			show_error('No database connection settings were found in the database config file.');
+		}
+
+		if ($params !== '')
+		{
+			$active_group = $params;
+		}
+
+		if ( ! isset($active_group))
+		{
+			show_error('You have not specified a database connection group via $active_group in your config/database.php file.');
+		}
+		elseif ( ! isset($db[$active_group]))
+		{
+			show_error('You have specified an invalid database connection group ('.$active_group.') in your config/database.php file.');
+		}
+
+		$params = $db[$active_group];
+	}
+	elseif (is_string($params))
+	{
+		/**
+		 * Parse the URL from the DSN string
+		 * Database settings can be passed as discreet
+		 * parameters or as a data source name in the first
+		 * parameter. DSNs must have this prototype:
+		 * $dsn = 'driver://username:password@hostname/database';
+		 */
+		if (($dsn = @parse_url($params)) === FALSE)
+		{
+			show_error('Invalid DB Connection String');
+		}
+
+		$params = array(
+			'dbdriver'	=> $dsn['scheme'],
+			'hostname'	=> isset($dsn['host']) ? rawurldecode($dsn['host']) : '',
+			'port'		=> isset($dsn['port']) ? rawurldecode($dsn['port']) : '',
+			'username'	=> isset($dsn['user']) ? rawurldecode($dsn['user']) : '',
+			'password'	=> isset($dsn['pass']) ? rawurldecode($dsn['pass']) : '',
+			'database'	=> isset($dsn['path']) ? rawurldecode(substr($dsn['path'], 1)) : ''
+		);
+
+		// Were additional config items set?
+		if (isset($dsn['query']))
+		{
+			parse_str($dsn['query'], $extra);
+
+			foreach ($extra as $key => $val)
+			{
+				if (is_string($val) && in_array(strtoupper($val), array('TRUE', 'FALSE', 'NULL')))
+				{
+					$val = var_export($val, TRUE);
+				}
+
+				$params[$key] = $val;
+			}
+		}
+	}
+
+	// No DB specified yet? Beat them senseless...
+	if (empty($params['dbdriver']))
+	{
+		show_error('You have not selected a database type to connect to.');
+	}
+
+	// Load the DB classes. Note: Since the query builder class is optional
+	// we need to dynamically create a class that extends proper parent class
+	// based on whether we're using the query builder class or not.
+	if ($query_builder_override !== NULL)
+	{
+		$query_builder = $query_builder_override;
+	}
+	// Backwards compatibility work-around for keeping the
+	// $active_record config variable working. Should be
+	// removed in v3.1
+	elseif ( ! isset($query_builder) && isset($active_record))
+	{
+		$query_builder = $active_record;
+	}
+
+	require_once(BASEPATH.'database/DB_driver.php');
+
+	if ( ! isset($query_builder) OR $query_builder === TRUE)
+	{
+		require_once(BASEPATH.'database/DB_query_builder.php');
+		if ( ! class_exists('CI_DB', FALSE))
+		{
+			/**
+			 * CI_DB
+			 *
+			 * Acts as an alias for both CI_DB_driver and CI_DB_query_builder.
+			 *
+			 * @see	CI_DB_query_builder
+			 * @see	CI_DB_driver
+			 */
+			class CI_DB extends CI_DB_query_builder { }
+		}
+	}
+	elseif ( ! class_exists('CI_DB', FALSE))
+	{
+		/**
+	 	 * @ignore
+		 */
+		class CI_DB extends CI_DB_driver { }
+	}
+
+	// Load the DB driver
+	$driver_file = BASEPATH.'database/drivers/'.$params['dbdriver'].'/'.$params['dbdriver'].'_driver.php';
+
+	file_exists($driver_file) OR show_error('Invalid DB driver');
+	require_once($driver_file);
+
+	// Instantiate the DB adapter
+	$driver = 'CI_DB_'.$params['dbdriver'].'_driver';
+	$DB = new $driver($params);
+
+	// Check for a subdriver
+	if ( ! empty($DB->subdriver))
+	{
+		$driver_file = BASEPATH.'database/drivers/'.$DB->dbdriver.'/subdrivers/'.$DB->dbdriver.'_'.$DB->subdriver.'_driver.php';
+
+		if (file_exists($driver_file))
+		{
+			require_once($driver_file);
+			$driver = 'CI_DB_'.$DB->dbdriver.'_'.$DB->subdriver.'_driver';
+			$DB = new $driver($params);
+		}
+	}
+
+	$DB->initialize();
+	return $DB;
+}

+ 221 - 0
apps/system/database/DB_cache.php

@@ -0,0 +1,221 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 1.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * Database Cache Class
+ *
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/database/
+ */
+class CI_DB_Cache {
+
+	/**
+	 * CI Singleton
+	 *
+	 * @var	object
+	 */
+	public $CI;
+
+	/**
+	 * Database object
+	 *
+	 * Allows passing of DB object so that multiple database connections
+	 * and returned DB objects can be supported.
+	 *
+	 * @var	object
+	 */
+	public $db;
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Constructor
+	 *
+	 * @param	object	&$db
+	 * @return	void
+	 */
+	public function __construct(&$db)
+	{
+		// Assign the main CI object to $this->CI and load the file helper since we use it a lot
+		$this->CI =& get_instance();
+		$this->db =& $db;
+		$this->CI->load->helper('file');
+
+		$this->check_path();
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Set Cache Directory Path
+	 *
+	 * @param	string	$path	Path to the cache directory
+	 * @return	bool
+	 */
+	public function check_path($path = '')
+	{
+		if ($path === '')
+		{
+			if ($this->db->cachedir === '')
+			{
+				return $this->db->cache_off();
+			}
+
+			$path = $this->db->cachedir;
+		}
+
+		// Add a trailing slash to the path if needed
+		$path = realpath($path)
+			? rtrim(realpath($path), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR
+			: rtrim($path, '/').'/';
+
+		if ( ! is_dir($path))
+		{
+			log_message('debug', 'DB cache path error: '.$path);
+
+			// If the path is wrong we'll turn off caching
+			return $this->db->cache_off();
+		}
+
+		if ( ! is_really_writable($path))
+		{
+			log_message('debug', 'DB cache dir not writable: '.$path);
+
+			// If the path is not really writable we'll turn off caching
+			return $this->db->cache_off();
+		}
+
+		$this->db->cachedir = $path;
+		return TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Retrieve a cached query
+	 *
+	 * The URI being requested will become the name of the cache sub-folder.
+	 * An MD5 hash of the SQL statement will become the cache file name.
+	 *
+	 * @param	string	$sql
+	 * @return	string
+	 */
+	public function read($sql)
+	{
+		$segment_one = ($this->CI->uri->segment(1) == FALSE) ? 'default' : $this->CI->uri->segment(1);
+		$segment_two = ($this->CI->uri->segment(2) == FALSE) ? 'index' : $this->CI->uri->segment(2);
+		$filepath = $this->db->cachedir.$segment_one.'+'.$segment_two.'/'.md5($sql);
+
+		if ( ! is_file($filepath) OR FALSE === ($cachedata = file_get_contents($filepath)))
+		{
+			return FALSE;
+		}
+
+		return unserialize($cachedata);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Write a query to a cache file
+	 *
+	 * @param	string	$sql
+	 * @param	object	$object
+	 * @return	bool
+	 */
+	public function write($sql, $object)
+	{
+		$segment_one = ($this->CI->uri->segment(1) == FALSE) ? 'default' : $this->CI->uri->segment(1);
+		$segment_two = ($this->CI->uri->segment(2) == FALSE) ? 'index' : $this->CI->uri->segment(2);
+		$dir_path = $this->db->cachedir.$segment_one.'+'.$segment_two.'/';
+		$filename = md5($sql);
+
+		if ( ! is_dir($dir_path) && ! @mkdir($dir_path, 0750))
+		{
+			return FALSE;
+		}
+
+		if (write_file($dir_path.$filename, serialize($object)) === FALSE)
+		{
+			return FALSE;
+		}
+
+		chmod($dir_path.$filename, 0640);
+		return TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Delete cache files within a particular directory
+	 *
+	 * @param	string	$segment_one
+	 * @param	string	$segment_two
+	 * @return	void
+	 */
+	public function delete($segment_one = '', $segment_two = '')
+	{
+		if ($segment_one === '')
+		{
+			$segment_one  = ($this->CI->uri->segment(1) == FALSE) ? 'default' : $this->CI->uri->segment(1);
+		}
+
+		if ($segment_two === '')
+		{
+			$segment_two = ($this->CI->uri->segment(2) == FALSE) ? 'index' : $this->CI->uri->segment(2);
+		}
+
+		$dir_path = $this->db->cachedir.$segment_one.'+'.$segment_two.'/';
+		delete_files($dir_path, TRUE);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Delete all existing cache files
+	 *
+	 * @return	void
+	 */
+	public function delete_all()
+	{
+		delete_files($this->db->cachedir, TRUE, TRUE);
+	}
+
+}

+ 1991 - 0
apps/system/database/DB_driver.php

@@ -0,0 +1,1991 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 1.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * Database Driver Class
+ *
+ * This is the platform-independent base DB implementation class.
+ * This class will not be called directly. Rather, the adapter
+ * class for the specific database will extend and instantiate it.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Drivers
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/database/
+ */
+abstract class CI_DB_driver {
+
+	/**
+	 * Data Source Name / Connect string
+	 *
+	 * @var	string
+	 */
+	public $dsn;
+
+	/**
+	 * Username
+	 *
+	 * @var	string
+	 */
+	public $username;
+
+	/**
+	 * Password
+	 *
+	 * @var	string
+	 */
+	public $password;
+
+	/**
+	 * Hostname
+	 *
+	 * @var	string
+	 */
+	public $hostname;
+
+	/**
+	 * Database name
+	 *
+	 * @var	string
+	 */
+	public $database;
+
+	/**
+	 * Database driver
+	 *
+	 * @var	string
+	 */
+	public $dbdriver		= 'mysqli';
+
+	/**
+	 * Sub-driver
+	 *
+	 * @used-by	CI_DB_pdo_driver
+	 * @var	string
+	 */
+	public $subdriver;
+
+	/**
+	 * Table prefix
+	 *
+	 * @var	string
+	 */
+	public $dbprefix		= '';
+
+	/**
+	 * Character set
+	 *
+	 * @var	string
+	 */
+	public $char_set		= 'utf8';
+
+	/**
+	 * Collation
+	 *
+	 * @var	string
+	 */
+	public $dbcollat		= 'utf8_general_ci';
+
+	/**
+	 * Encryption flag/data
+	 *
+	 * @var	mixed
+	 */
+	public $encrypt			= FALSE;
+
+	/**
+	 * Swap Prefix
+	 *
+	 * @var	string
+	 */
+	public $swap_pre		= '';
+
+	/**
+	 * Database port
+	 *
+	 * @var	int
+	 */
+	public $port			= '';
+
+	/**
+	 * Persistent connection flag
+	 *
+	 * @var	bool
+	 */
+	public $pconnect		= FALSE;
+
+	/**
+	 * Connection ID
+	 *
+	 * @var	object|resource
+	 */
+	public $conn_id			= FALSE;
+
+	/**
+	 * Result ID
+	 *
+	 * @var	object|resource
+	 */
+	public $result_id		= FALSE;
+
+	/**
+	 * Debug flag
+	 *
+	 * Whether to display error messages.
+	 *
+	 * @var	bool
+	 */
+	public $db_debug		= FALSE;
+
+	/**
+	 * Benchmark time
+	 *
+	 * @var	int
+	 */
+	public $benchmark		= 0;
+
+	/**
+	 * Executed queries count
+	 *
+	 * @var	int
+	 */
+	public $query_count		= 0;
+
+	/**
+	 * Bind marker
+	 *
+	 * Character used to identify values in a prepared statement.
+	 *
+	 * @var	string
+	 */
+	public $bind_marker		= '?';
+
+	/**
+	 * Save queries flag
+	 *
+	 * Whether to keep an in-memory history of queries for debugging purposes.
+	 *
+	 * @var	bool
+	 */
+	public $save_queries		= TRUE;
+
+	/**
+	 * Queries list
+	 *
+	 * @see	CI_DB_driver::$save_queries
+	 * @var	string[]
+	 */
+	public $queries			= array();
+
+	/**
+	 * Query times
+	 *
+	 * A list of times that queries took to execute.
+	 *
+	 * @var	array
+	 */
+	public $query_times		= array();
+
+	/**
+	 * Data cache
+	 *
+	 * An internal generic value cache.
+	 *
+	 * @var	array
+	 */
+	public $data_cache		= array();
+
+	/**
+	 * Transaction enabled flag
+	 *
+	 * @var	bool
+	 */
+	public $trans_enabled		= TRUE;
+
+	/**
+	 * Strict transaction mode flag
+	 *
+	 * @var	bool
+	 */
+	public $trans_strict		= TRUE;
+
+	/**
+	 * Transaction depth level
+	 *
+	 * @var	int
+	 */
+	protected $_trans_depth		= 0;
+
+	/**
+	 * Transaction status flag
+	 *
+	 * Used with transactions to determine if a rollback should occur.
+	 *
+	 * @var	bool
+	 */
+	protected $_trans_status	= TRUE;
+
+	/**
+	 * Transaction failure flag
+	 *
+	 * Used with transactions to determine if a transaction has failed.
+	 *
+	 * @var	bool
+	 */
+	protected $_trans_failure	= FALSE;
+
+	/**
+	 * Cache On flag
+	 *
+	 * @var	bool
+	 */
+	public $cache_on		= FALSE;
+
+	/**
+	 * Cache directory path
+	 *
+	 * @var	bool
+	 */
+	public $cachedir		= '';
+
+	/**
+	 * Cache auto-delete flag
+	 *
+	 * @var	bool
+	 */
+	public $cache_autodel		= FALSE;
+
+	/**
+	 * DB Cache object
+	 *
+	 * @see	CI_DB_cache
+	 * @var	object
+	 */
+	public $CACHE;
+
+	/**
+	 * Protect identifiers flag
+	 *
+	 * @var	bool
+	 */
+	protected $_protect_identifiers		= TRUE;
+
+	/**
+	 * List of reserved identifiers
+	 *
+	 * Identifiers that must NOT be escaped.
+	 *
+	 * @var	string[]
+	 */
+	protected $_reserved_identifiers	= array('*');
+
+	/**
+	 * Identifier escape character
+	 *
+	 * @var	string
+	 */
+	protected $_escape_char = '"';
+
+	/**
+	 * ESCAPE statement string
+	 *
+	 * @var	string
+	 */
+	protected $_like_escape_str = " ESCAPE '%s' ";
+
+	/**
+	 * ESCAPE character
+	 *
+	 * @var	string
+	 */
+	protected $_like_escape_chr = '!';
+
+	/**
+	 * ORDER BY random keyword
+	 *
+	 * @var	array
+	 */
+	protected $_random_keyword = array('RAND()', 'RAND(%d)');
+
+	/**
+	 * COUNT string
+	 *
+	 * @used-by	CI_DB_driver::count_all()
+	 * @used-by	CI_DB_query_builder::count_all_results()
+	 *
+	 * @var	string
+	 */
+	protected $_count_string = 'SELECT COUNT(*) AS ';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * @param	array	$params
+	 * @return	void
+	 */
+	public function __construct($params)
+	{
+		if (is_array($params))
+		{
+			foreach ($params as $key => $val)
+			{
+				$this->$key = $val;
+			}
+		}
+
+		log_message('info', 'Database Driver Class Initialized');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Initialize Database Settings
+	 *
+	 * @return	bool
+	 */
+	public function initialize()
+	{
+		/* If an established connection is available, then there's
+		 * no need to connect and select the database.
+		 *
+		 * Depending on the database driver, conn_id can be either
+		 * boolean TRUE, a resource or an object.
+		 */
+		if ($this->conn_id)
+		{
+			return TRUE;
+		}
+
+		// ----------------------------------------------------------------
+
+		// Connect to the database and set the connection ID
+		$this->conn_id = $this->db_connect($this->pconnect);
+
+		// No connection resource? Check if there is a failover else throw an error
+		if ( ! $this->conn_id)
+		{
+			// Check if there is a failover set
+			if ( ! empty($this->failover) && is_array($this->failover))
+			{
+				// Go over all the failovers
+				foreach ($this->failover as $failover)
+				{
+					// Replace the current settings with those of the failover
+					foreach ($failover as $key => $val)
+					{
+						$this->$key = $val;
+					}
+
+					// Try to connect
+					$this->conn_id = $this->db_connect($this->pconnect);
+
+					// If a connection is made break the foreach loop
+					if ($this->conn_id)
+					{
+						break;
+					}
+				}
+			}
+
+			// We still don't have a connection?
+			if ( ! $this->conn_id)
+			{
+				log_message('error', 'Unable to connect to the database');
+
+				if ($this->db_debug)
+				{
+					$this->display_error('db_unable_to_connect');
+				}
+
+				return FALSE;
+			}
+		}
+
+		// Now we set the character set and that's all
+		return $this->db_set_charset($this->char_set);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * DB connect
+	 *
+	 * This is just a dummy method that all drivers will override.
+	 *
+	 * @return	mixed
+	 */
+	public function db_connect()
+	{
+		return TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Persistent database connection
+	 *
+	 * @return	mixed
+	 */
+	public function db_pconnect()
+	{
+		return $this->db_connect(TRUE);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Reconnect
+	 *
+	 * Keep / reestablish the db connection if no queries have been
+	 * sent for a length of time exceeding the server's idle timeout.
+	 *
+	 * This is just a dummy method to allow drivers without such
+	 * functionality to not declare it, while others will override it.
+	 *
+	 * @return	void
+	 */
+	public function reconnect()
+	{
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Select database
+	 *
+	 * This is just a dummy method to allow drivers without such
+	 * functionality to not declare it, while others will override it.
+	 *
+	 * @return	bool
+	 */
+	public function db_select()
+	{
+		return TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Last error
+	 *
+	 * @return	array
+	 */
+	public function error()
+	{
+		return array('code' => NULL, 'message' => NULL);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Set client character set
+	 *
+	 * @param	string
+	 * @return	bool
+	 */
+	public function db_set_charset($charset)
+	{
+		if (method_exists($this, '_db_set_charset') && ! $this->_db_set_charset($charset))
+		{
+			log_message('error', 'Unable to set database connection charset: '.$charset);
+
+			if ($this->db_debug)
+			{
+				$this->display_error('db_unable_to_set_charset', $charset);
+			}
+
+			return FALSE;
+		}
+
+		return TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * The name of the platform in use (mysql, mssql, etc...)
+	 *
+	 * @return	string
+	 */
+	public function platform()
+	{
+		return $this->dbdriver;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Database version number
+	 *
+	 * Returns a string containing the version of the database being used.
+	 * Most drivers will override this method.
+	 *
+	 * @return	string
+	 */
+	public function version()
+	{
+		if (isset($this->data_cache['version']))
+		{
+			return $this->data_cache['version'];
+		}
+
+		if (FALSE === ($sql = $this->_version()))
+		{
+			return ($this->db_debug) ? $this->display_error('db_unsupported_function') : FALSE;
+		}
+
+		$query = $this->query($sql)->row();
+		return $this->data_cache['version'] = $query->ver;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Version number query string
+	 *
+	 * @return	string
+	 */
+	protected function _version()
+	{
+		return 'SELECT VERSION() AS ver';
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Execute the query
+	 *
+	 * Accepts an SQL string as input and returns a result object upon
+	 * successful execution of a "read" type query. Returns boolean TRUE
+	 * upon successful execution of a "write" type query. Returns boolean
+	 * FALSE upon failure, and if the $db_debug variable is set to TRUE
+	 * will raise an error.
+	 *
+	 * @param	string	$sql
+	 * @param	array	$binds = FALSE		An array of binding data
+	 * @param	bool	$return_object = NULL
+	 * @return	mixed
+	 */
+	public function query($sql, $binds = FALSE, $return_object = NULL)
+	{
+		if ($sql === '')
+		{
+			log_message('error', 'Invalid query: '.$sql);
+			return ($this->db_debug) ? $this->display_error('db_invalid_query') : FALSE;
+		}
+		elseif ( ! is_bool($return_object))
+		{
+			$return_object = ! $this->is_write_type($sql);
+		}
+
+		// Verify table prefix and replace if necessary
+		if ($this->dbprefix !== '' && $this->swap_pre !== '' && $this->dbprefix !== $this->swap_pre)
+		{
+			$sql = preg_replace('/(\W)'.$this->swap_pre.'(\S+?)/', '\\1'.$this->dbprefix.'\\2', $sql);
+		}
+
+		// Compile binds if needed
+		if ($binds !== FALSE)
+		{
+			$sql = $this->compile_binds($sql, $binds);
+		}
+
+		// Is query caching enabled? If the query is a "read type"
+		// we will load the caching class and return the previously
+		// cached query if it exists
+		if ($this->cache_on === TRUE && $return_object === TRUE && $this->_cache_init())
+		{
+			$this->load_rdriver();
+			if (FALSE !== ($cache = $this->CACHE->read($sql)))
+			{
+				return $cache;
+			}
+		}
+
+		// Save the query for debugging
+		if ($this->save_queries === TRUE)
+		{
+			$this->queries[] = $sql;
+		}
+
+		// Start the Query Timer
+		$time_start = microtime(TRUE);
+
+		// Run the Query
+		if (FALSE === ($this->result_id = $this->simple_query($sql)))
+		{
+			if ($this->save_queries === TRUE)
+			{
+				$this->query_times[] = 0;
+			}
+
+			// This will trigger a rollback if transactions are being used
+			if ($this->_trans_depth !== 0)
+			{
+				$this->_trans_status = FALSE;
+			}
+
+			// Grab the error now, as we might run some additional queries before displaying the error
+			$error = $this->error();
+
+			// Log errors
+			log_message('error', 'Query error: '.$error['message'].' - Invalid query: '.$sql);
+
+			if ($this->db_debug)
+			{
+				// We call this function in order to roll-back queries
+				// if transactions are enabled. If we don't call this here
+				// the error message will trigger an exit, causing the
+				// transactions to remain in limbo.
+				while ($this->_trans_depth !== 0)
+				{
+					$trans_depth = $this->_trans_depth;
+					$this->trans_complete();
+					if ($trans_depth === $this->_trans_depth)
+					{
+						log_message('error', 'Database: Failure during an automated transaction commit/rollback!');
+						break;
+					}
+				}
+
+				// Display errors
+				return $this->display_error(array('Error Number: '.$error['code'], $error['message'], $sql));
+			}
+
+			return FALSE;
+		}
+
+		// Stop and aggregate the query time results
+		$time_end = microtime(TRUE);
+		$this->benchmark += $time_end - $time_start;
+
+		if ($this->save_queries === TRUE)
+		{
+			$this->query_times[] = $time_end - $time_start;
+		}
+
+		// Increment the query counter
+		$this->query_count++;
+
+		// Will we have a result object instantiated? If not - we'll simply return TRUE
+		if ($return_object !== TRUE)
+		{
+			// If caching is enabled we'll auto-cleanup any existing files related to this particular URI
+			if ($this->cache_on === TRUE && $this->cache_autodel === TRUE && $this->_cache_init())
+			{
+				$this->CACHE->delete();
+			}
+
+			return TRUE;
+		}
+
+		// Load and instantiate the result driver
+		$driver		= $this->load_rdriver();
+		$RES		= new $driver($this);
+
+		// Is query caching enabled? If so, we'll serialize the
+		// result object and save it to a cache file.
+		if ($this->cache_on === TRUE && $this->_cache_init())
+		{
+			// We'll create a new instance of the result object
+			// only without the platform specific driver since
+			// we can't use it with cached data (the query result
+			// resource ID won't be any good once we've cached the
+			// result object, so we'll have to compile the data
+			// and save it)
+			$CR = new CI_DB_result($this);
+			$CR->result_object	= $RES->result_object();
+			$CR->result_array	= $RES->result_array();
+			$CR->num_rows		= $RES->num_rows();
+
+			// Reset these since cached objects can not utilize resource IDs.
+			$CR->conn_id		= NULL;
+			$CR->result_id		= NULL;
+
+			$this->CACHE->write($sql, $CR);
+		}
+
+		return $RES;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Load the result drivers
+	 *
+	 * @return	string	the name of the result class
+	 */
+	public function load_rdriver()
+	{
+		$driver = 'CI_DB_'.$this->dbdriver.'_result';
+
+		if ( ! class_exists($driver, FALSE))
+		{
+			require_once(BASEPATH.'database/DB_result.php');
+			require_once(BASEPATH.'database/drivers/'.$this->dbdriver.'/'.$this->dbdriver.'_result.php');
+		}
+
+		return $driver;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Simple Query
+	 * This is a simplified version of the query() function. Internally
+	 * we only use it when running transaction commands since they do
+	 * not require all the features of the main query() function.
+	 *
+	 * @param	string	the sql query
+	 * @return	mixed
+	 */
+	public function simple_query($sql)
+	{
+		if ( ! $this->conn_id)
+		{
+			if ( ! $this->initialize())
+			{
+				return FALSE;
+			}
+		}
+
+		return $this->_execute($sql);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Disable Transactions
+	 * This permits transactions to be disabled at run-time.
+	 *
+	 * @return	void
+	 */
+	public function trans_off()
+	{
+		$this->trans_enabled = FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Enable/disable Transaction Strict Mode
+	 *
+	 * When strict mode is enabled, if you are running multiple groups of
+	 * transactions, if one group fails all subsequent groups will be
+	 * rolled back.
+	 *
+	 * If strict mode is disabled, each group is treated autonomously,
+	 * meaning a failure of one group will not affect any others
+	 *
+	 * @param	bool	$mode = TRUE
+	 * @return	void
+	 */
+	public function trans_strict($mode = TRUE)
+	{
+		$this->trans_strict = is_bool($mode) ? $mode : TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Start Transaction
+	 *
+	 * @param	bool	$test_mode = FALSE
+	 * @return	bool
+	 */
+	public function trans_start($test_mode = FALSE)
+	{
+		if ( ! $this->trans_enabled)
+		{
+			return FALSE;
+		}
+
+		return $this->trans_begin($test_mode);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Complete Transaction
+	 *
+	 * @return	bool
+	 */
+	public function trans_complete()
+	{
+		if ( ! $this->trans_enabled)
+		{
+			return FALSE;
+		}
+
+		// The query() function will set this flag to FALSE in the event that a query failed
+		if ($this->_trans_status === FALSE OR $this->_trans_failure === TRUE)
+		{
+			$this->trans_rollback();
+
+			// If we are NOT running in strict mode, we will reset
+			// the _trans_status flag so that subsequent groups of
+			// transactions will be permitted.
+			if ($this->trans_strict === FALSE)
+			{
+				$this->_trans_status = TRUE;
+			}
+
+			log_message('debug', 'DB Transaction Failure');
+			return FALSE;
+		}
+
+		return $this->trans_commit();
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Lets you retrieve the transaction flag to determine if it has failed
+	 *
+	 * @return	bool
+	 */
+	public function trans_status()
+	{
+		return $this->_trans_status;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Begin Transaction
+	 *
+	 * @param	bool	$test_mode
+	 * @return	bool
+	 */
+	public function trans_begin($test_mode = FALSE)
+	{
+		if ( ! $this->trans_enabled)
+		{
+			return FALSE;
+		}
+		// When transactions are nested we only begin/commit/rollback the outermost ones
+		elseif ($this->_trans_depth > 0)
+		{
+			$this->_trans_depth++;
+			return TRUE;
+		}
+
+		// Reset the transaction failure flag.
+		// If the $test_mode flag is set to TRUE transactions will be rolled back
+		// even if the queries produce a successful result.
+		$this->_trans_failure = ($test_mode === TRUE);
+
+		if ($this->_trans_begin())
+		{
+			$this->_trans_status = TRUE;
+			$this->_trans_depth++;
+			return TRUE;
+		}
+
+		return FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Commit Transaction
+	 *
+	 * @return	bool
+	 */
+	public function trans_commit()
+	{
+		if ( ! $this->trans_enabled OR $this->_trans_depth === 0)
+		{
+			return FALSE;
+		}
+		// When transactions are nested we only begin/commit/rollback the outermost ones
+		elseif ($this->_trans_depth > 1 OR $this->_trans_commit())
+		{
+			$this->_trans_depth--;
+			return TRUE;
+		}
+
+		return FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Rollback Transaction
+	 *
+	 * @return	bool
+	 */
+	public function trans_rollback()
+	{
+		if ( ! $this->trans_enabled OR $this->_trans_depth === 0)
+		{
+			return FALSE;
+		}
+		// When transactions are nested we only begin/commit/rollback the outermost ones
+		elseif ($this->_trans_depth > 1 OR $this->_trans_rollback())
+		{
+			$this->_trans_depth--;
+			return TRUE;
+		}
+
+		return FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Compile Bindings
+	 *
+	 * @param	string	the sql statement
+	 * @param	array	an array of bind data
+	 * @return	string
+	 */
+	public function compile_binds($sql, $binds)
+	{
+		if (empty($this->bind_marker) OR strpos($sql, $this->bind_marker) === FALSE)
+		{
+			return $sql;
+		}
+		elseif ( ! is_array($binds))
+		{
+			$binds = array($binds);
+			$bind_count = 1;
+		}
+		else
+		{
+			// Make sure we're using numeric keys
+			$binds = array_values($binds);
+			$bind_count = count($binds);
+		}
+
+		// We'll need the marker length later
+		$ml = strlen($this->bind_marker);
+
+		// Make sure not to replace a chunk inside a string that happens to match the bind marker
+		if ($c = preg_match_all("/'[^']*'|\"[^\"]*\"/i", $sql, $matches))
+		{
+			$c = preg_match_all('/'.preg_quote($this->bind_marker, '/').'/i',
+				str_replace($matches[0],
+					str_replace($this->bind_marker, str_repeat(' ', $ml), $matches[0]),
+					$sql, $c),
+				$matches, PREG_OFFSET_CAPTURE);
+
+			// Bind values' count must match the count of markers in the query
+			if ($bind_count !== $c)
+			{
+				return $sql;
+			}
+		}
+		elseif (($c = preg_match_all('/'.preg_quote($this->bind_marker, '/').'/i', $sql, $matches, PREG_OFFSET_CAPTURE)) !== $bind_count)
+		{
+			return $sql;
+		}
+
+		do
+		{
+			$c--;
+			$escaped_value = $this->escape($binds[$c]);
+			if (is_array($escaped_value))
+			{
+				$escaped_value = '('.implode(',', $escaped_value).')';
+			}
+			$sql = substr_replace($sql, $escaped_value, $matches[0][$c][1], $ml);
+		}
+		while ($c !== 0);
+
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Determines if a query is a "write" type.
+	 *
+	 * @param	string	An SQL query string
+	 * @return	bool
+	 */
+	public function is_write_type($sql)
+	{
+		return (bool) preg_match('/^\s*"?(SET|INSERT|UPDATE|DELETE|REPLACE|CREATE|DROP|TRUNCATE|LOAD|COPY|ALTER|RENAME|GRANT|REVOKE|LOCK|UNLOCK|REINDEX|MERGE)\s/i', $sql);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Calculate the aggregate query elapsed time
+	 *
+	 * @param	int	The number of decimal places
+	 * @return	string
+	 */
+	public function elapsed_time($decimals = 6)
+	{
+		return number_format($this->benchmark, $decimals);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Returns the total number of queries
+	 *
+	 * @return	int
+	 */
+	public function total_queries()
+	{
+		return $this->query_count;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Returns the last query that was executed
+	 *
+	 * @return	string
+	 */
+	public function last_query()
+	{
+		return end($this->queries);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * "Smart" Escape String
+	 *
+	 * Escapes data based on type
+	 * Sets boolean and null types
+	 *
+	 * @param	string
+	 * @return	mixed
+	 */
+	public function escape($str)
+	{
+		if (is_array($str))
+		{
+			$str = array_map(array(&$this, 'escape'), $str);
+			return $str;
+		}
+		elseif (is_string($str) OR (is_object($str) && method_exists($str, '__toString')))
+		{
+			return "'".$this->escape_str($str)."'";
+		}
+		elseif (is_bool($str))
+		{
+			return ($str === FALSE) ? 0 : 1;
+		}
+		elseif ($str === NULL)
+		{
+			return 'NULL';
+		}
+
+		return $str;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Escape String
+	 *
+	 * @param	string|string[]	$str	Input string
+	 * @param	bool	$like	Whether or not the string will be used in a LIKE condition
+	 * @return	string
+	 */
+	public function escape_str($str, $like = FALSE)
+	{
+		if (is_array($str))
+		{
+			foreach ($str as $key => $val)
+			{
+				$str[$key] = $this->escape_str($val, $like);
+			}
+
+			return $str;
+		}
+
+		$str = $this->_escape_str($str);
+
+		// escape LIKE condition wildcards
+		if ($like === TRUE)
+		{
+			return str_replace(
+				array($this->_like_escape_chr, '%', '_'),
+				array($this->_like_escape_chr.$this->_like_escape_chr, $this->_like_escape_chr.'%', $this->_like_escape_chr.'_'),
+				$str
+			);
+		}
+
+		return $str;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Escape LIKE String
+	 *
+	 * Calls the individual driver for platform
+	 * specific escaping for LIKE conditions
+	 *
+	 * @param	string|string[]
+	 * @return	mixed
+	 */
+	public function escape_like_str($str)
+	{
+		return $this->escape_str($str, TRUE);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Platform-dependent string escape
+	 *
+	 * @param	string
+	 * @return	string
+	 */
+	protected function _escape_str($str)
+	{
+		return str_replace("'", "''", remove_invisible_characters($str, FALSE));
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Primary
+	 *
+	 * Retrieves the primary key. It assumes that the row in the first
+	 * position is the primary key
+	 *
+	 * @param	string	$table	Table name
+	 * @return	string
+	 */
+	public function primary($table)
+	{
+		$fields = $this->list_fields($table);
+		return is_array($fields) ? current($fields) : FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * "Count All" query
+	 *
+	 * Generates a platform-specific query string that counts all records in
+	 * the specified database
+	 *
+	 * @param	string
+	 * @return	int
+	 */
+	public function count_all($table = '')
+	{
+		if ($table === '')
+		{
+			return 0;
+		}
+
+		$query = $this->query($this->_count_string.$this->escape_identifiers('numrows').' FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE));
+		if ($query->num_rows() === 0)
+		{
+			return 0;
+		}
+
+		$query = $query->row();
+		$this->_reset_select();
+		return (int) $query->numrows;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Returns an array of table names
+	 *
+	 * @param	string	$constrain_by_prefix = FALSE
+	 * @return	array
+	 */
+	public function list_tables($constrain_by_prefix = FALSE)
+	{
+		// Is there a cached result?
+		if (isset($this->data_cache['table_names']))
+		{
+			return $this->data_cache['table_names'];
+		}
+
+		if (FALSE === ($sql = $this->_list_tables($constrain_by_prefix)))
+		{
+			return ($this->db_debug) ? $this->display_error('db_unsupported_function') : FALSE;
+		}
+
+		$this->data_cache['table_names'] = array();
+		$query = $this->query($sql);
+
+		foreach ($query->result_array() as $row)
+		{
+			// Do we know from which column to get the table name?
+			if ( ! isset($key))
+			{
+				if (isset($row['table_name']))
+				{
+					$key = 'table_name';
+				}
+				elseif (isset($row['TABLE_NAME']))
+				{
+					$key = 'TABLE_NAME';
+				}
+				else
+				{
+					/* We have no other choice but to just get the first element's key.
+					 * Due to array_shift() accepting its argument by reference, if
+					 * E_STRICT is on, this would trigger a warning. So we'll have to
+					 * assign it first.
+					 */
+					$key = array_keys($row);
+					$key = array_shift($key);
+				}
+			}
+
+			$this->data_cache['table_names'][] = $row[$key];
+		}
+
+		return $this->data_cache['table_names'];
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Determine if a particular table exists
+	 *
+	 * @param	string	$table_name
+	 * @return	bool
+	 */
+	public function table_exists($table_name)
+	{
+		return in_array($this->protect_identifiers($table_name, TRUE, FALSE, FALSE), $this->list_tables());
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Fetch Field Names
+	 *
+	 * @param	string	$table	Table name
+	 * @return	array
+	 */
+	public function list_fields($table)
+	{
+		// Is there a cached result?
+		if (isset($this->data_cache['field_names'][$table]))
+		{
+			return $this->data_cache['field_names'][$table];
+		}
+
+		if (FALSE === ($sql = $this->_list_columns($table)))
+		{
+			return ($this->db_debug) ? $this->display_error('db_unsupported_function') : FALSE;
+		}
+
+		$query = $this->query($sql);
+		$this->data_cache['field_names'][$table] = array();
+
+		foreach ($query->result_array() as $row)
+		{
+			// Do we know from where to get the column's name?
+			if ( ! isset($key))
+			{
+				if (isset($row['column_name']))
+				{
+					$key = 'column_name';
+				}
+				elseif (isset($row['COLUMN_NAME']))
+				{
+					$key = 'COLUMN_NAME';
+				}
+				else
+				{
+					// We have no other choice but to just get the first element's key.
+					$key = key($row);
+				}
+			}
+
+			$this->data_cache['field_names'][$table][] = $row[$key];
+		}
+
+		return $this->data_cache['field_names'][$table];
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Determine if a particular field exists
+	 *
+	 * @param	string
+	 * @param	string
+	 * @return	bool
+	 */
+	public function field_exists($field_name, $table_name)
+	{
+		return in_array($field_name, $this->list_fields($table_name));
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Returns an object with field data
+	 *
+	 * @param	string	$table	the table name
+	 * @return	array
+	 */
+	public function field_data($table)
+	{
+		$query = $this->query($this->_field_data($this->protect_identifiers($table, TRUE, NULL, FALSE)));
+		return ($query) ? $query->field_data() : FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Escape the SQL Identifiers
+	 *
+	 * This function escapes column and table names
+	 *
+	 * @param	mixed
+	 * @return	mixed
+	 */
+	public function escape_identifiers($item)
+	{
+		if ($this->_escape_char === '' OR empty($item) OR in_array($item, $this->_reserved_identifiers))
+		{
+			return $item;
+		}
+		elseif (is_array($item))
+		{
+			foreach ($item as $key => $value)
+			{
+				$item[$key] = $this->escape_identifiers($value);
+			}
+
+			return $item;
+		}
+		// Avoid breaking functions and literal values inside queries
+		elseif (ctype_digit($item) OR $item[0] === "'" OR ($this->_escape_char !== '"' && $item[0] === '"') OR strpos($item, '(') !== FALSE)
+		{
+			return $item;
+		}
+
+		static $preg_ec = array();
+
+		if (empty($preg_ec))
+		{
+			if (is_array($this->_escape_char))
+			{
+				$preg_ec = array(
+					preg_quote($this->_escape_char[0], '/'),
+					preg_quote($this->_escape_char[1], '/'),
+					$this->_escape_char[0],
+					$this->_escape_char[1]
+				);
+			}
+			else
+			{
+				$preg_ec[0] = $preg_ec[1] = preg_quote($this->_escape_char, '/');
+				$preg_ec[2] = $preg_ec[3] = $this->_escape_char;
+			}
+		}
+
+		foreach ($this->_reserved_identifiers as $id)
+		{
+			if (strpos($item, '.'.$id) !== FALSE)
+			{
+				return preg_replace('/'.$preg_ec[0].'?([^'.$preg_ec[1].'\.]+)'.$preg_ec[1].'?\./i', $preg_ec[2].'$1'.$preg_ec[3].'.', $item);
+			}
+		}
+
+		return preg_replace('/'.$preg_ec[0].'?([^'.$preg_ec[1].'\.]+)'.$preg_ec[1].'?(\.)?/i', $preg_ec[2].'$1'.$preg_ec[3].'$2', $item);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Generate an insert string
+	 *
+	 * @param	string	the table upon which the query will be performed
+	 * @param	array	an associative array data of key/values
+	 * @return	string
+	 */
+	public function insert_string($table, $data)
+	{
+		$fields = $values = array();
+
+		foreach ($data as $key => $val)
+		{
+			$fields[] = $this->escape_identifiers($key);
+			$values[] = $this->escape($val);
+		}
+
+		return $this->_insert($this->protect_identifiers($table, TRUE, NULL, FALSE), $fields, $values);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Insert statement
+	 *
+	 * Generates a platform-specific insert string from the supplied data
+	 *
+	 * @param	string	the table name
+	 * @param	array	the insert keys
+	 * @param	array	the insert values
+	 * @return	string
+	 */
+	protected function _insert($table, $keys, $values)
+	{
+		return 'INSERT INTO '.$table.' ('.implode(', ', $keys).') VALUES ('.implode(', ', $values).')';
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Generate an update string
+	 *
+	 * @param	string	the table upon which the query will be performed
+	 * @param	array	an associative array data of key/values
+	 * @param	mixed	the "where" statement
+	 * @return	string
+	 */
+	public function update_string($table, $data, $where)
+	{
+		if (empty($where))
+		{
+			return FALSE;
+		}
+
+		$this->where($where);
+
+		$fields = array();
+		foreach ($data as $key => $val)
+		{
+			$fields[$this->protect_identifiers($key)] = $this->escape($val);
+		}
+
+		$sql = $this->_update($this->protect_identifiers($table, TRUE, NULL, FALSE), $fields);
+		$this->_reset_write();
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Update statement
+	 *
+	 * Generates a platform-specific update string from the supplied data
+	 *
+	 * @param	string	the table name
+	 * @param	array	the update data
+	 * @return	string
+	 */
+	protected function _update($table, $values)
+	{
+		foreach ($values as $key => $val)
+		{
+			$valstr[] = $key.' = '.$val;
+		}
+
+		return 'UPDATE '.$table.' SET '.implode(', ', $valstr)
+			.$this->_compile_wh('qb_where')
+			.$this->_compile_order_by()
+			.($this->qb_limit ? ' LIMIT '.$this->qb_limit : '');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Tests whether the string has an SQL operator
+	 *
+	 * @param	string
+	 * @return	bool
+	 */
+	protected function _has_operator($str)
+	{
+		return (bool) preg_match('/(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sIN\s*\(|\s)/i', trim($str));
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Returns the SQL string operator
+	 *
+	 * @param	string
+	 * @return	string
+	 */
+	protected function _get_operator($str)
+	{
+		static $_operators;
+
+		if (empty($_operators))
+		{
+			$_les = ($this->_like_escape_str !== '')
+				? '\s+'.preg_quote(trim(sprintf($this->_like_escape_str, $this->_like_escape_chr)), '/')
+				: '';
+			$_operators = array(
+				'\s*(?:<|>|!)?=\s*',             // =, <=, >=, !=
+				'\s*<>?\s*',                     // <, <>
+				'\s*>\s*',                       // >
+				'\s+IS NULL',                    // IS NULL
+				'\s+IS NOT NULL',                // IS NOT NULL
+				'\s+EXISTS\s*\(.*\)',        // EXISTS(sql)
+				'\s+NOT EXISTS\s*\(.*\)',    // NOT EXISTS(sql)
+				'\s+BETWEEN\s+',                 // BETWEEN value AND value
+				'\s+IN\s*\(.*\)',            // IN(list)
+				'\s+NOT IN\s*\(.*\)',        // NOT IN (list)
+				'\s+LIKE\s+\S.*('.$_les.')?',    // LIKE 'expr'[ ESCAPE '%s']
+				'\s+NOT LIKE\s+\S.*('.$_les.')?' // NOT LIKE 'expr'[ ESCAPE '%s']
+			);
+
+		}
+
+		return preg_match('/'.implode('|', $_operators).'/i', $str, $match)
+			? $match[0] : FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Enables a native PHP function to be run, using a platform agnostic wrapper.
+	 *
+	 * @param	string	$function	Function name
+	 * @return	mixed
+	 */
+	public function call_function($function)
+	{
+		$driver = ($this->dbdriver === 'postgre') ? 'pg_' : $this->dbdriver.'_';
+
+		if (FALSE === strpos($driver, $function))
+		{
+			$function = $driver.$function;
+		}
+
+		if ( ! function_exists($function))
+		{
+			return ($this->db_debug) ? $this->display_error('db_unsupported_function') : FALSE;
+		}
+
+		return (func_num_args() > 1)
+			? call_user_func_array($function, array_slice(func_get_args(), 1))
+			: call_user_func($function);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Set Cache Directory Path
+	 *
+	 * @param	string	the path to the cache directory
+	 * @return	void
+	 */
+	public function cache_set_path($path = '')
+	{
+		$this->cachedir = $path;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Enable Query Caching
+	 *
+	 * @return	bool	cache_on value
+	 */
+	public function cache_on()
+	{
+		return $this->cache_on = TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Disable Query Caching
+	 *
+	 * @return	bool	cache_on value
+	 */
+	public function cache_off()
+	{
+		return $this->cache_on = FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Delete the cache files associated with a particular URI
+	 *
+	 * @param	string	$segment_one = ''
+	 * @param	string	$segment_two = ''
+	 * @return	bool
+	 */
+	public function cache_delete($segment_one = '', $segment_two = '')
+	{
+		return $this->_cache_init()
+			? $this->CACHE->delete($segment_one, $segment_two)
+			: FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Delete All cache files
+	 *
+	 * @return	bool
+	 */
+	public function cache_delete_all()
+	{
+		return $this->_cache_init()
+			? $this->CACHE->delete_all()
+			: FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Initialize the Cache Class
+	 *
+	 * @return	bool
+	 */
+	protected function _cache_init()
+	{
+		if ( ! class_exists('CI_DB_Cache', FALSE))
+		{
+			require_once(BASEPATH.'database/DB_cache.php');
+		}
+		elseif (is_object($this->CACHE))
+		{
+			return TRUE;
+		}
+
+		$this->CACHE = new CI_DB_Cache($this); // pass db object to support multiple db connections and returned db objects
+		return TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Close DB Connection
+	 *
+	 * @return	void
+	 */
+	public function close()
+	{
+		if ($this->conn_id)
+		{
+			$this->_close();
+			$this->conn_id = FALSE;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Close DB Connection
+	 *
+	 * This method would be overridden by most of the drivers.
+	 *
+	 * @return	void
+	 */
+	protected function _close()
+	{
+		$this->conn_id = FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Display an error message
+	 *
+	 * @param	string	the error message
+	 * @param	string	any "swap" values
+	 * @param	bool	whether to localize the message
+	 * @return	string	sends the application/views/errors/error_db.php template
+	 */
+	public function display_error($error = '', $swap = '', $native = FALSE)
+	{
+		$LANG =& load_class('Lang', 'core');
+		$LANG->load('db');
+
+		$heading = $LANG->line('db_error_heading');
+
+		if ($native === TRUE)
+		{
+			$message = (array) $error;
+		}
+		else
+		{
+			$message = is_array($error) ? $error : array(str_replace('%s', $swap, $LANG->line($error)));
+		}
+
+		// Find the most likely culprit of the error by going through
+		// the backtrace until the source file is no longer in the
+		// database folder.
+		$trace = debug_backtrace();
+		foreach ($trace as $call)
+		{
+			if (isset($call['file'], $call['class']))
+			{
+				// We'll need this on Windows, as APPPATH and BASEPATH will always use forward slashes
+				if (DIRECTORY_SEPARATOR !== '/')
+				{
+					$call['file'] = str_replace('\\', '/', $call['file']);
+				}
+
+				if (strpos($call['file'], BASEPATH.'database') === FALSE && strpos($call['class'], 'Loader') === FALSE)
+				{
+					// Found it - use a relative path for safety
+					$message[] = 'Filename: '.str_replace(array(APPPATH, BASEPATH), '', $call['file']);
+					$message[] = 'Line Number: '.$call['line'];
+					break;
+				}
+			}
+		}
+
+		$error =& load_class('Exceptions', 'core');
+		echo $error->show_error($heading, $message, 'error_db');
+		exit(8); // EXIT_DATABASE
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Protect Identifiers
+	 *
+	 * This function is used extensively by the Query Builder class, and by
+	 * a couple functions in this class.
+	 * It takes a column or table name (optionally with an alias) and inserts
+	 * the table prefix onto it. Some logic is necessary in order to deal with
+	 * column names that include the path. Consider a query like this:
+	 *
+	 * SELECT hostname.database.table.column AS c FROM hostname.database.table
+	 *
+	 * Or a query with aliasing:
+	 *
+	 * SELECT m.member_id, m.member_name FROM members AS m
+	 *
+	 * Since the column name can include up to four segments (host, DB, table, column)
+	 * or also have an alias prefix, we need to do a bit of work to figure this out and
+	 * insert the table prefix (if it exists) in the proper position, and escape only
+	 * the correct identifiers.
+	 *
+	 * @param	string
+	 * @param	bool
+	 * @param	mixed
+	 * @param	bool
+	 * @return	string
+	 */
+	public function protect_identifiers($item, $prefix_single = FALSE, $protect_identifiers = NULL, $field_exists = TRUE)
+	{
+		if ( ! is_bool($protect_identifiers))
+		{
+			$protect_identifiers = $this->_protect_identifiers;
+		}
+
+		if (is_array($item))
+		{
+			$escaped_array = array();
+			foreach ($item as $k => $v)
+			{
+				$escaped_array[$this->protect_identifiers($k)] = $this->protect_identifiers($v, $prefix_single, $protect_identifiers, $field_exists);
+			}
+
+			return $escaped_array;
+		}
+
+		// This is basically a bug fix for queries that use MAX, MIN, etc.
+		// If a parenthesis is found we know that we do not need to
+		// escape the data or add a prefix. There's probably a more graceful
+		// way to deal with this, but I'm not thinking of it -- Rick
+		//
+		// Added exception for single quotes as well, we don't want to alter
+		// literal strings. -- Narf
+		if (strcspn($item, "()'") !== strlen($item))
+		{
+			return $item;
+		}
+
+		// Convert tabs or multiple spaces into single spaces
+		$item = preg_replace('/\s+/', ' ', trim($item));
+
+		// If the item has an alias declaration we remove it and set it aside.
+		// Note: strripos() is used in order to support spaces in table names
+		if ($offset = strripos($item, ' AS '))
+		{
+			$alias = ($protect_identifiers)
+				? substr($item, $offset, 4).$this->escape_identifiers(substr($item, $offset + 4))
+				: substr($item, $offset);
+			$item = substr($item, 0, $offset);
+		}
+		elseif ($offset = strrpos($item, ' '))
+		{
+			$alias = ($protect_identifiers)
+				? ' '.$this->escape_identifiers(substr($item, $offset + 1))
+				: substr($item, $offset);
+			$item = substr($item, 0, $offset);
+		}
+		else
+		{
+			$alias = '';
+		}
+
+		// Break the string apart if it contains periods, then insert the table prefix
+		// in the correct location, assuming the period doesn't indicate that we're dealing
+		// with an alias. While we're at it, we will escape the components
+		if (strpos($item, '.') !== FALSE)
+		{
+			$parts = explode('.', $item);
+
+			// Does the first segment of the exploded item match
+			// one of the aliases previously identified? If so,
+			// we have nothing more to do other than escape the item
+			//
+			// NOTE: The ! empty() condition prevents this method
+			//       from breaking when QB isn't enabled.
+			if ( ! empty($this->qb_aliased_tables) && in_array($parts[0], $this->qb_aliased_tables))
+			{
+				if ($protect_identifiers === TRUE)
+				{
+					foreach ($parts as $key => $val)
+					{
+						if ( ! in_array($val, $this->_reserved_identifiers))
+						{
+							$parts[$key] = $this->escape_identifiers($val);
+						}
+					}
+
+					$item = implode('.', $parts);
+				}
+
+				return $item.$alias;
+			}
+
+			// Is there a table prefix defined in the config file? If not, no need to do anything
+			if ($this->dbprefix !== '')
+			{
+				// We now add the table prefix based on some logic.
+				// Do we have 4 segments (hostname.database.table.column)?
+				// If so, we add the table prefix to the column name in the 3rd segment.
+				if (isset($parts[3]))
+				{
+					$i = 2;
+				}
+				// Do we have 3 segments (database.table.column)?
+				// If so, we add the table prefix to the column name in 2nd position
+				elseif (isset($parts[2]))
+				{
+					$i = 1;
+				}
+				// Do we have 2 segments (table.column)?
+				// If so, we add the table prefix to the column name in 1st segment
+				else
+				{
+					$i = 0;
+				}
+
+				// This flag is set when the supplied $item does not contain a field name.
+				// This can happen when this function is being called from a JOIN.
+				if ($field_exists === FALSE)
+				{
+					$i++;
+				}
+
+				// dbprefix may've already been applied, with or without the identifier escaped
+				$ec = '(?<ec>'.preg_quote(is_array($this->_escape_char) ? $this->_escape_char[0] : $this->_escape_char).')?';
+				isset($ec[0]) && $ec .= '?'; // Just in case someone has disabled escaping by forcing an empty escape character
+
+				// Verify table prefix and replace if necessary
+				if ($this->swap_pre !== '' && preg_match('#^'.$ec.preg_quote($this->swap_pre).'#', $parts[$i]))
+				{
+					$parts[$i] = preg_replace('#^'.$ec.preg_quote($this->swap_pre).'(\S+?)#', '\\1'.$this->dbprefix.'\\2', $parts[$i]);
+				}
+				// We only add the table prefix if it does not already exist
+				else
+				{
+					preg_match('#^'.$ec.preg_quote($this->dbprefix).'#', $parts[$i]) OR $parts[$i] = $this->dbprefix.$parts[$i];
+				}
+
+				// Put the parts back together
+				$item = implode('.', $parts);
+			}
+
+			if ($protect_identifiers === TRUE)
+			{
+				$item = $this->escape_identifiers($item);
+			}
+
+			return $item.$alias;
+		}
+
+		// Is there a table prefix? If not, no need to insert it
+		if ($this->dbprefix !== '')
+		{
+			// Verify table prefix and replace if necessary
+			if ($this->swap_pre !== '' && strpos($item, $this->swap_pre) === 0)
+			{
+				$item = preg_replace('/^'.$this->swap_pre.'(\S+?)/', $this->dbprefix.'\\1', $item);
+			}
+			// Do we prefix an item with no segments?
+			elseif ($prefix_single === TRUE && strpos($item, $this->dbprefix) !== 0)
+			{
+				$item = $this->dbprefix.$item;
+			}
+		}
+
+		if ($protect_identifiers === TRUE && ! in_array($item, $this->_reserved_identifiers))
+		{
+			$item = $this->escape_identifiers($item);
+		}
+
+		return $item.$alias;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Dummy method that allows Query Builder class to be disabled
+	 * and keep count_all() working.
+	 *
+	 * @return	void
+	 */
+	protected function _reset_select()
+	{
+	}
+
+}

+ 1030 - 0
apps/system/database/DB_forge.php

@@ -0,0 +1,1030 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 1.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * Database Forge Class
+ *
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/database/
+ */
+abstract class CI_DB_forge {
+
+	/**
+	 * Database object
+	 *
+	 * @var	object
+	 */
+	protected $db;
+
+	/**
+	 * Fields data
+	 *
+	 * @var	array
+	 */
+	public $fields		= array();
+
+	/**
+	 * Keys data
+	 *
+	 * @var	array
+	 */
+	public $keys		= array();
+
+	/**
+	 * Primary Keys data
+	 *
+	 * @var	array
+	 */
+	public $primary_keys	= array();
+
+	/**
+	 * Database character set
+	 *
+	 * @var	string
+	 */
+	public $db_char_set	= '';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * CREATE DATABASE statement
+	 *
+	 * @var	string
+	 */
+	protected $_create_database	= 'CREATE DATABASE %s';
+
+	/**
+	 * DROP DATABASE statement
+	 *
+	 * @var	string
+	 */
+	protected $_drop_database	= 'DROP DATABASE %s';
+
+	/**
+	 * CREATE TABLE statement
+	 *
+	 * @var	string
+	 */
+	protected $_create_table	= "%s %s (%s\n)";
+
+	/**
+	 * CREATE TABLE IF statement
+	 *
+	 * @var	string
+	 */
+	protected $_create_table_if	= 'CREATE TABLE IF NOT EXISTS';
+
+	/**
+	 * CREATE TABLE keys flag
+	 *
+	 * Whether table keys are created from within the
+	 * CREATE TABLE statement.
+	 *
+	 * @var	bool
+	 */
+	protected $_create_table_keys	= FALSE;
+
+	/**
+	 * DROP TABLE IF EXISTS statement
+	 *
+	 * @var	string
+	 */
+	protected $_drop_table_if	= 'DROP TABLE IF EXISTS';
+
+	/**
+	 * RENAME TABLE statement
+	 *
+	 * @var	string
+	 */
+	protected $_rename_table	= 'ALTER TABLE %s RENAME TO %s;';
+
+	/**
+	 * UNSIGNED support
+	 *
+	 * @var	bool|array
+	 */
+	protected $_unsigned		= TRUE;
+
+	/**
+	 * NULL value representation in CREATE/ALTER TABLE statements
+	 *
+	 * @var	string
+	 */
+	protected $_null		= '';
+
+	/**
+	 * DEFAULT value representation in CREATE/ALTER TABLE statements
+	 *
+	 * @var	string
+	 */
+	protected $_default		= ' DEFAULT ';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * @param	object	&$db	Database object
+	 * @return	void
+	 */
+	public function __construct(&$db)
+	{
+		$this->db =& $db;
+		log_message('info', 'Database Forge Class Initialized');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Create database
+	 *
+	 * @param	string	$db_name
+	 * @return	bool
+	 */
+	public function create_database($db_name)
+	{
+		if ($this->_create_database === FALSE)
+		{
+			return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
+		}
+		elseif ( ! $this->db->query(sprintf($this->_create_database, $this->db->escape_identifiers($db_name), $this->db->char_set, $this->db->dbcollat)))
+		{
+			return ($this->db->db_debug) ? $this->db->display_error('db_unable_to_drop') : FALSE;
+		}
+
+		if ( ! empty($this->db->data_cache['db_names']))
+		{
+			$this->db->data_cache['db_names'][] = $db_name;
+		}
+
+		return TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Drop database
+	 *
+	 * @param	string	$db_name
+	 * @return	bool
+	 */
+	public function drop_database($db_name)
+	{
+		if ($this->_drop_database === FALSE)
+		{
+			return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
+		}
+		elseif ( ! $this->db->query(sprintf($this->_drop_database, $this->db->escape_identifiers($db_name))))
+		{
+			return ($this->db->db_debug) ? $this->db->display_error('db_unable_to_drop') : FALSE;
+		}
+
+		if ( ! empty($this->db->data_cache['db_names']))
+		{
+			$key = array_search(strtolower($db_name), array_map('strtolower', $this->db->data_cache['db_names']), TRUE);
+			if ($key !== FALSE)
+			{
+				unset($this->db->data_cache['db_names'][$key]);
+			}
+		}
+
+		return TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Add Key
+	 *
+	 * @param	string	$key
+	 * @param	bool	$primary
+	 * @return	CI_DB_forge
+	 */
+	public function add_key($key, $primary = FALSE)
+	{
+		// DO NOT change this! This condition is only applicable
+		// for PRIMARY keys because you can only have one such,
+		// and therefore all fields you add to it will be included
+		// in the same, composite PRIMARY KEY.
+		//
+		// It's not the same for regular indexes.
+		if ($primary === TRUE && is_array($key))
+		{
+			foreach ($key as $one)
+			{
+				$this->add_key($one, $primary);
+			}
+
+			return $this;
+		}
+
+		if ($primary === TRUE)
+		{
+			$this->primary_keys[] = $key;
+		}
+		else
+		{
+			$this->keys[] = $key;
+		}
+
+		return $this;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Add Field
+	 *
+	 * @param	array	$field
+	 * @return	CI_DB_forge
+	 */
+	public function add_field($field)
+	{
+		if (is_string($field))
+		{
+			if ($field === 'id')
+			{
+				$this->add_field(array(
+					'id' => array(
+						'type' => 'INT',
+						'constraint' => 9,
+						'auto_increment' => TRUE
+					)
+				));
+				$this->add_key('id', TRUE);
+			}
+			else
+			{
+				if (strpos($field, ' ') === FALSE)
+				{
+					show_error('Field information is required for that operation.');
+				}
+
+				$this->fields[] = $field;
+			}
+		}
+
+		if (is_array($field))
+		{
+			$this->fields = array_merge($this->fields, $field);
+		}
+
+		return $this;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Create Table
+	 *
+	 * @param	string	$table		Table name
+	 * @param	bool	$if_not_exists	Whether to add IF NOT EXISTS condition
+	 * @param	array	$attributes	Associative array of table attributes
+	 * @return	bool
+	 */
+	public function create_table($table, $if_not_exists = FALSE, array $attributes = array())
+	{
+		if ($table === '')
+		{
+			show_error('A table name is required for that operation.');
+		}
+		else
+		{
+			$table = $this->db->dbprefix.$table;
+		}
+
+		if (count($this->fields) === 0)
+		{
+			show_error('Field information is required.');
+		}
+
+		$sql = $this->_create_table($table, $if_not_exists, $attributes);
+
+		if (is_bool($sql))
+		{
+			$this->_reset();
+			if ($sql === FALSE)
+			{
+				return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
+			}
+		}
+
+		if (($result = $this->db->query($sql)) !== FALSE)
+		{
+			isset($this->db->data_cache['table_names']) && $this->db->data_cache['table_names'][] = $table;
+
+			// Most databases don't support creating indexes from within the CREATE TABLE statement
+			if ( ! empty($this->keys))
+			{
+				for ($i = 0, $sqls = $this->_process_indexes($table), $c = count($sqls); $i < $c; $i++)
+				{
+					$this->db->query($sqls[$i]);
+				}
+			}
+		}
+
+		$this->_reset();
+		return $result;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Create Table
+	 *
+	 * @param	string	$table		Table name
+	 * @param	bool	$if_not_exists	Whether to add 'IF NOT EXISTS' condition
+	 * @param	array	$attributes	Associative array of table attributes
+	 * @return	mixed
+	 */
+	protected function _create_table($table, $if_not_exists, $attributes)
+	{
+		if ($if_not_exists === TRUE && $this->_create_table_if === FALSE)
+		{
+			if ($this->db->table_exists($table))
+			{
+				return TRUE;
+			}
+
+			$if_not_exists = FALSE;
+		}
+
+		$sql = ($if_not_exists)
+			? sprintf($this->_create_table_if, $this->db->escape_identifiers($table))
+			: 'CREATE TABLE';
+
+		$columns = $this->_process_fields(TRUE);
+		for ($i = 0, $c = count($columns); $i < $c; $i++)
+		{
+			$columns[$i] = ($columns[$i]['_literal'] !== FALSE)
+					? "\n\t".$columns[$i]['_literal']
+					: "\n\t".$this->_process_column($columns[$i]);
+		}
+
+		$columns = implode(',', $columns)
+				.$this->_process_primary_keys($table);
+
+		// Are indexes created from within the CREATE TABLE statement? (e.g. in MySQL)
+		if ($this->_create_table_keys === TRUE)
+		{
+			$columns .= $this->_process_indexes($table);
+		}
+
+		// _create_table will usually have the following format: "%s %s (%s\n)"
+		$sql = sprintf($this->_create_table.'%s',
+			$sql,
+			$this->db->escape_identifiers($table),
+			$columns,
+			$this->_create_table_attr($attributes)
+		);
+
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * CREATE TABLE attributes
+	 *
+	 * @param	array	$attributes	Associative array of table attributes
+	 * @return	string
+	 */
+	protected function _create_table_attr($attributes)
+	{
+		$sql = '';
+
+		foreach (array_keys($attributes) as $key)
+		{
+			if (is_string($key))
+			{
+				$sql .= ' '.strtoupper($key).' '.$attributes[$key];
+			}
+		}
+
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Drop Table
+	 *
+	 * @param	string	$table_name	Table name
+	 * @param	bool	$if_exists	Whether to add an IF EXISTS condition
+	 * @return	bool
+	 */
+	public function drop_table($table_name, $if_exists = FALSE)
+	{
+		if ($table_name === '')
+		{
+			return ($this->db->db_debug) ? $this->db->display_error('db_table_name_required') : FALSE;
+		}
+
+		if (($query = $this->_drop_table($this->db->dbprefix.$table_name, $if_exists)) === TRUE)
+		{
+			return TRUE;
+		}
+
+		$query = $this->db->query($query);
+
+		// Update table list cache
+		if ($query && ! empty($this->db->data_cache['table_names']))
+		{
+			$key = array_search(strtolower($this->db->dbprefix.$table_name), array_map('strtolower', $this->db->data_cache['table_names']), TRUE);
+			if ($key !== FALSE)
+			{
+				unset($this->db->data_cache['table_names'][$key]);
+			}
+		}
+
+		return $query;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Drop Table
+	 *
+	 * Generates a platform-specific DROP TABLE string
+	 *
+	 * @param	string	$table		Table name
+	 * @param	bool	$if_exists	Whether to add an IF EXISTS condition
+	 * @return	mixed	(Returns a platform-specific DROP table string, or TRUE to indicate there's nothing to do)
+	 */
+	protected function _drop_table($table, $if_exists)
+	{
+		$sql = 'DROP TABLE';
+
+		if ($if_exists)
+		{
+			if ($this->_drop_table_if === FALSE)
+			{
+				if ( ! $this->db->table_exists($table))
+				{
+					return TRUE;
+				}
+			}
+			else
+			{
+				$sql = sprintf($this->_drop_table_if, $this->db->escape_identifiers($table));
+			}
+		}
+
+		return $sql.' '.$this->db->escape_identifiers($table);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Rename Table
+	 *
+	 * @param	string	$table_name	Old table name
+	 * @param	string	$new_table_name	New table name
+	 * @return	bool
+	 */
+	public function rename_table($table_name, $new_table_name)
+	{
+		if ($table_name === '' OR $new_table_name === '')
+		{
+			show_error('A table name is required for that operation.');
+			return FALSE;
+		}
+		elseif ($this->_rename_table === FALSE)
+		{
+			return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
+		}
+
+		$result = $this->db->query(sprintf($this->_rename_table,
+						$this->db->escape_identifiers($this->db->dbprefix.$table_name),
+						$this->db->escape_identifiers($this->db->dbprefix.$new_table_name))
+					);
+
+		if ($result && ! empty($this->db->data_cache['table_names']))
+		{
+			$key = array_search(strtolower($this->db->dbprefix.$table_name), array_map('strtolower', $this->db->data_cache['table_names']), TRUE);
+			if ($key !== FALSE)
+			{
+				$this->db->data_cache['table_names'][$key] = $this->db->dbprefix.$new_table_name;
+			}
+		}
+
+		return $result;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Column Add
+	 *
+	 * @todo	Remove deprecated $_after option in 3.1+
+	 * @param	string	$table	Table name
+	 * @param	array	$field	Column definition
+	 * @param	string	$_after	Column for AFTER clause (deprecated)
+	 * @return	bool
+	 */
+	public function add_column($table, $field, $_after = NULL)
+	{
+		// Work-around for literal column definitions
+		is_array($field) OR $field = array($field);
+
+		foreach (array_keys($field) as $k)
+		{
+			// Backwards-compatibility work-around for MySQL/CUBRID AFTER clause (remove in 3.1+)
+			if ($_after !== NULL && is_array($field[$k]) && ! isset($field[$k]['after']))
+			{
+				$field[$k]['after'] = $_after;
+			}
+
+			$this->add_field(array($k => $field[$k]));
+		}
+
+		$sqls = $this->_alter_table('ADD', $this->db->dbprefix.$table, $this->_process_fields());
+		$this->_reset();
+		if ($sqls === FALSE)
+		{
+			return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
+		}
+
+		for ($i = 0, $c = count($sqls); $i < $c; $i++)
+		{
+			if ($this->db->query($sqls[$i]) === FALSE)
+			{
+				return FALSE;
+			}
+		}
+
+		return TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Column Drop
+	 *
+	 * @param	string	$table		Table name
+	 * @param	string	$column_name	Column name
+	 * @return	bool
+	 */
+	public function drop_column($table, $column_name)
+	{
+		$sql = $this->_alter_table('DROP', $this->db->dbprefix.$table, $column_name);
+		if ($sql === FALSE)
+		{
+			return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
+		}
+
+		return $this->db->query($sql);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Column Modify
+	 *
+	 * @param	string	$table	Table name
+	 * @param	string	$field	Column definition
+	 * @return	bool
+	 */
+	public function modify_column($table, $field)
+	{
+		// Work-around for literal column definitions
+		is_array($field) OR $field = array($field);
+
+		foreach (array_keys($field) as $k)
+		{
+			$this->add_field(array($k => $field[$k]));
+		}
+
+		if (count($this->fields) === 0)
+		{
+			show_error('Field information is required.');
+		}
+
+		$sqls = $this->_alter_table('CHANGE', $this->db->dbprefix.$table, $this->_process_fields());
+		$this->_reset();
+		if ($sqls === FALSE)
+		{
+			return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
+		}
+
+		for ($i = 0, $c = count($sqls); $i < $c; $i++)
+		{
+			if ($this->db->query($sqls[$i]) === FALSE)
+			{
+				return FALSE;
+			}
+		}
+
+		return TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ALTER TABLE
+	 *
+	 * @param	string	$alter_type	ALTER type
+	 * @param	string	$table		Table name
+	 * @param	mixed	$field		Column definition
+	 * @return	string|string[]
+	 */
+	protected function _alter_table($alter_type, $table, $field)
+	{
+		$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table).' ';
+
+		// DROP has everything it needs now.
+		if ($alter_type === 'DROP')
+		{
+			return $sql.'DROP COLUMN '.$this->db->escape_identifiers($field);
+		}
+
+		$sql .= ($alter_type === 'ADD')
+			? 'ADD '
+			: $alter_type.' COLUMN ';
+
+		$sqls = array();
+		for ($i = 0, $c = count($field); $i < $c; $i++)
+		{
+			$sqls[] = $sql
+				.($field[$i]['_literal'] !== FALSE ? $field[$i]['_literal'] : $this->_process_column($field[$i]));
+		}
+
+		return $sqls;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Process fields
+	 *
+	 * @param	bool	$create_table
+	 * @return	array
+	 */
+	protected function _process_fields($create_table = FALSE)
+	{
+		$fields = array();
+
+		foreach ($this->fields as $key => $attributes)
+		{
+			if (is_int($key) && ! is_array($attributes))
+			{
+				$fields[] = array('_literal' => $attributes);
+				continue;
+			}
+
+			$attributes = array_change_key_case($attributes, CASE_UPPER);
+
+			if ($create_table === TRUE && empty($attributes['TYPE']))
+			{
+				continue;
+			}
+
+			isset($attributes['TYPE']) && $this->_attr_type($attributes);
+
+			$field = array(
+				'name'			=> $key,
+				'new_name'		=> isset($attributes['NAME']) ? $attributes['NAME'] : NULL,
+				'type'			=> isset($attributes['TYPE']) ? $attributes['TYPE'] : NULL,
+				'length'		=> '',
+				'unsigned'		=> '',
+				'null'			=> '',
+				'unique'		=> '',
+				'default'		=> '',
+				'auto_increment'	=> '',
+				'_literal'		=> FALSE
+			);
+
+			isset($attributes['TYPE']) && $this->_attr_unsigned($attributes, $field);
+
+			if ($create_table === FALSE)
+			{
+				if (isset($attributes['AFTER']))
+				{
+					$field['after'] = $attributes['AFTER'];
+				}
+				elseif (isset($attributes['FIRST']))
+				{
+					$field['first'] = (bool) $attributes['FIRST'];
+				}
+			}
+
+			$this->_attr_default($attributes, $field);
+
+			if (isset($attributes['NULL']))
+			{
+				if ($attributes['NULL'] === TRUE)
+				{
+					$field['null'] = empty($this->_null) ? '' : ' '.$this->_null;
+				}
+				else
+				{
+					$field['null'] = ' NOT NULL';
+				}
+			}
+			elseif ($create_table === TRUE)
+			{
+				$field['null'] = ' NOT NULL';
+			}
+
+			$this->_attr_auto_increment($attributes, $field);
+			$this->_attr_unique($attributes, $field);
+
+			if (isset($attributes['COMMENT']))
+			{
+				$field['comment'] = $this->db->escape($attributes['COMMENT']);
+			}
+
+			if (isset($attributes['TYPE']) && ! empty($attributes['CONSTRAINT']))
+			{
+				switch (strtoupper($attributes['TYPE']))
+				{
+					case 'ENUM':
+					case 'SET':
+						$attributes['CONSTRAINT'] = $this->db->escape($attributes['CONSTRAINT']);
+					default:
+						$field['length'] = is_array($attributes['CONSTRAINT'])
+							? '('.implode(',', $attributes['CONSTRAINT']).')'
+							: '('.$attributes['CONSTRAINT'].')';
+						break;
+				}
+			}
+
+			$fields[] = $field;
+		}
+
+		return $fields;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Process column
+	 *
+	 * @param	array	$field
+	 * @return	string
+	 */
+	protected function _process_column($field)
+	{
+		return $this->db->escape_identifiers($field['name'])
+			.' '.$field['type'].$field['length']
+			.$field['unsigned']
+			.$field['default']
+			.$field['null']
+			.$field['auto_increment']
+			.$field['unique'];
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute TYPE
+	 *
+	 * Performs a data type mapping between different databases.
+	 *
+	 * @param	array	&$attributes
+	 * @return	void
+	 */
+	protected function _attr_type(&$attributes)
+	{
+		// Usually overridden by drivers
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute UNSIGNED
+	 *
+	 * Depending on the _unsigned property value:
+	 *
+	 *	- TRUE will always set $field['unsigned'] to 'UNSIGNED'
+	 *	- FALSE will always set $field['unsigned'] to ''
+	 *	- array(TYPE) will set $field['unsigned'] to 'UNSIGNED',
+	 *		if $attributes['TYPE'] is found in the array
+	 *	- array(TYPE => UTYPE) will change $field['type'],
+	 *		from TYPE to UTYPE in case of a match
+	 *
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
+	 */
+	protected function _attr_unsigned(&$attributes, &$field)
+	{
+		if (empty($attributes['UNSIGNED']) OR $attributes['UNSIGNED'] !== TRUE)
+		{
+			return;
+		}
+
+		// Reset the attribute in order to avoid issues if we do type conversion
+		$attributes['UNSIGNED'] = FALSE;
+
+		if (is_array($this->_unsigned))
+		{
+			foreach (array_keys($this->_unsigned) as $key)
+			{
+				if (is_int($key) && strcasecmp($attributes['TYPE'], $this->_unsigned[$key]) === 0)
+				{
+					$field['unsigned'] = ' UNSIGNED';
+					return;
+				}
+				elseif (is_string($key) && strcasecmp($attributes['TYPE'], $key) === 0)
+				{
+					$field['type'] = $key;
+					return;
+				}
+			}
+
+			return;
+		}
+
+		$field['unsigned'] = ($this->_unsigned === TRUE) ? ' UNSIGNED' : '';
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute DEFAULT
+	 *
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
+	 */
+	protected function _attr_default(&$attributes, &$field)
+	{
+		if ($this->_default === FALSE)
+		{
+			return;
+		}
+
+		if (array_key_exists('DEFAULT', $attributes))
+		{
+			if ($attributes['DEFAULT'] === NULL)
+			{
+				$field['default'] = empty($this->_null) ? '' : $this->_default.$this->_null;
+
+				// Override the NULL attribute if that's our default
+				$attributes['NULL'] = TRUE;
+				$field['null'] = empty($this->_null) ? '' : ' '.$this->_null;
+			}
+			else
+			{
+				$field['default'] = $this->_default.$this->db->escape($attributes['DEFAULT']);
+			}
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute UNIQUE
+	 *
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
+	 */
+	protected function _attr_unique(&$attributes, &$field)
+	{
+		if ( ! empty($attributes['UNIQUE']) && $attributes['UNIQUE'] === TRUE)
+		{
+			$field['unique'] = ' UNIQUE';
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute AUTO_INCREMENT
+	 *
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
+	 */
+	protected function _attr_auto_increment(&$attributes, &$field)
+	{
+		if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE && stripos($field['type'], 'int') !== FALSE)
+		{
+			$field['auto_increment'] = ' AUTO_INCREMENT';
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Process primary keys
+	 *
+	 * @param	string	$table	Table name
+	 * @return	string
+	 */
+	protected function _process_primary_keys($table)
+	{
+		$sql = '';
+
+		for ($i = 0, $c = count($this->primary_keys); $i < $c; $i++)
+		{
+			if ( ! isset($this->fields[$this->primary_keys[$i]]))
+			{
+				unset($this->primary_keys[$i]);
+			}
+		}
+
+		if (count($this->primary_keys) > 0)
+		{
+			$sql .= ",\n\tCONSTRAINT ".$this->db->escape_identifiers('pk_'.$table)
+				.' PRIMARY KEY('.implode(', ', $this->db->escape_identifiers($this->primary_keys)).')';
+		}
+
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Process indexes
+	 *
+	 * @param	string	$table	Table name
+	 * @return	string[] list of SQL statements
+	 */
+	protected function _process_indexes($table)
+	{
+		$sqls = array();
+
+		for ($i = 0, $c = count($this->keys); $i < $c; $i++)
+		{
+			if (is_array($this->keys[$i]))
+			{
+				for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++)
+				{
+					if ( ! isset($this->fields[$this->keys[$i][$i2]]))
+					{
+						unset($this->keys[$i][$i2]);
+						continue;
+					}
+				}
+			}
+			elseif ( ! isset($this->fields[$this->keys[$i]]))
+			{
+				unset($this->keys[$i]);
+				continue;
+			}
+
+			is_array($this->keys[$i]) OR $this->keys[$i] = array($this->keys[$i]);
+
+			$sqls[] = 'CREATE INDEX '.$this->db->escape_identifiers($table.'_'.implode('_', $this->keys[$i]))
+				.' ON '.$this->db->escape_identifiers($table)
+				.' ('.implode(', ', $this->db->escape_identifiers($this->keys[$i])).');';
+		}
+
+		return $sqls;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Reset
+	 *
+	 * Resets table creation vars
+	 *
+	 * @return	void
+	 */
+	protected function _reset()
+	{
+		$this->fields = $this->keys = $this->primary_keys = array();
+	}
+
+}

+ 2803 - 0
apps/system/database/DB_query_builder.php

@@ -0,0 +1,2803 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 1.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * Query Builder Class
+ *
+ * This is the platform-independent base Query Builder implementation class.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Drivers
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/database/
+ */
+
+abstract class CI_DB_query_builder extends CI_DB_driver {
+
+	/**
+	 * Return DELETE SQL flag
+	 *
+	 * @var	bool
+	 */
+	protected $return_delete_sql		= FALSE;
+
+	/**
+	 * Reset DELETE data flag
+	 *
+	 * @var	bool
+	 */
+	protected $reset_delete_data		= FALSE;
+
+	/**
+	 * QB SELECT data
+	 *
+	 * @var	array
+	 */
+	protected $qb_select			= array();
+
+	/**
+	 * QB DISTINCT flag
+	 *
+	 * @var	bool
+	 */
+	protected $qb_distinct			= FALSE;
+
+	/**
+	 * QB FROM data
+	 *
+	 * @var	array
+	 */
+	protected $qb_from			= array();
+
+	/**
+	 * QB JOIN data
+	 *
+	 * @var	array
+	 */
+	protected $qb_join			= array();
+
+	/**
+	 * QB WHERE data
+	 *
+	 * @var	array
+	 */
+	protected $qb_where			= array();
+
+	/**
+	 * QB GROUP BY data
+	 *
+	 * @var	array
+	 */
+	protected $qb_groupby			= array();
+
+	/**
+	 * QB HAVING data
+	 *
+	 * @var	array
+	 */
+	protected $qb_having			= array();
+
+	/**
+	 * QB keys
+	 *
+	 * @var	array
+	 */
+	protected $qb_keys			= array();
+
+	/**
+	 * QB LIMIT data
+	 *
+	 * @var	int
+	 */
+	protected $qb_limit			= FALSE;
+
+	/**
+	 * QB OFFSET data
+	 *
+	 * @var	int
+	 */
+	protected $qb_offset			= FALSE;
+
+	/**
+	 * QB ORDER BY data
+	 *
+	 * @var	array
+	 */
+	protected $qb_orderby			= array();
+
+	/**
+	 * QB data sets
+	 *
+	 * @var	array
+	 */
+	protected $qb_set			= array();
+
+	/**
+	 * QB data set for update_batch()
+	 *
+	 * @var	array
+	 */
+	protected $qb_set_ub			= array();
+
+	/**
+	 * QB aliased tables list
+	 *
+	 * @var	array
+	 */
+	protected $qb_aliased_tables		= array();
+
+	/**
+	 * QB WHERE group started flag
+	 *
+	 * @var	bool
+	 */
+	protected $qb_where_group_started	= FALSE;
+
+	/**
+	 * QB WHERE group count
+	 *
+	 * @var	int
+	 */
+	protected $qb_where_group_count		= 0;
+
+	// Query Builder Caching variables
+
+	/**
+	 * QB Caching flag
+	 *
+	 * @var	bool
+	 */
+	protected $qb_caching				= FALSE;
+
+	/**
+	 * QB Cache exists list
+	 *
+	 * @var	array
+	 */
+	protected $qb_cache_exists			= array();
+
+	/**
+	 * QB Cache SELECT data
+	 *
+	 * @var	array
+	 */
+	protected $qb_cache_select			= array();
+
+	/**
+	 * QB Cache FROM data
+	 *
+	 * @var	array
+	 */
+	protected $qb_cache_from			= array();
+
+	/**
+	 * QB Cache JOIN data
+	 *
+	 * @var	array
+	 */
+	protected $qb_cache_join			= array();
+
+	/**
+	 * QB Cache aliased tables list
+	 *
+	 * @var	array
+	 */
+	protected $qb_cache_aliased_tables			= array();
+
+	/**
+	 * QB Cache WHERE data
+	 *
+	 * @var	array
+	 */
+	protected $qb_cache_where			= array();
+
+	/**
+	 * QB Cache GROUP BY data
+	 *
+	 * @var	array
+	 */
+	protected $qb_cache_groupby			= array();
+
+	/**
+	 * QB Cache HAVING data
+	 *
+	 * @var	array
+	 */
+	protected $qb_cache_having			= array();
+
+	/**
+	 * QB Cache ORDER BY data
+	 *
+	 * @var	array
+	 */
+	protected $qb_cache_orderby			= array();
+
+	/**
+	 * QB Cache data sets
+	 *
+	 * @var	array
+	 */
+	protected $qb_cache_set				= array();
+
+	/**
+	 * QB No Escape data
+	 *
+	 * @var	array
+	 */
+	protected $qb_no_escape 			= array();
+
+	/**
+	 * QB Cache No Escape data
+	 *
+	 * @var	array
+	 */
+	protected $qb_cache_no_escape			= array();
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Select
+	 *
+	 * Generates the SELECT portion of the query
+	 *
+	 * @param	string
+	 * @param	mixed
+	 * @return	CI_DB_query_builder
+	 */
+	public function select($select = '*', $escape = NULL)
+	{
+		if (is_string($select))
+		{
+			$select = explode(',', $select);
+		}
+
+		// If the escape value was not set, we will base it on the global setting
+		is_bool($escape) OR $escape = $this->_protect_identifiers;
+
+		foreach ($select as $val)
+		{
+			$val = trim($val);
+
+			if ($val !== '')
+			{
+				$this->qb_select[] = $val;
+				$this->qb_no_escape[] = $escape;
+
+				if ($this->qb_caching === TRUE)
+				{
+					$this->qb_cache_select[] = $val;
+					$this->qb_cache_exists[] = 'select';
+					$this->qb_cache_no_escape[] = $escape;
+				}
+			}
+		}
+
+		return $this;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Select Max
+	 *
+	 * Generates a SELECT MAX(field) portion of a query
+	 *
+	 * @param	string	the field
+	 * @param	string	an alias
+	 * @return	CI_DB_query_builder
+	 */
+	public function select_max($select = '', $alias = '')
+	{
+		return $this->_max_min_avg_sum($select, $alias, 'MAX');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Select Min
+	 *
+	 * Generates a SELECT MIN(field) portion of a query
+	 *
+	 * @param	string	the field
+	 * @param	string	an alias
+	 * @return	CI_DB_query_builder
+	 */
+	public function select_min($select = '', $alias = '')
+	{
+		return $this->_max_min_avg_sum($select, $alias, 'MIN');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Select Average
+	 *
+	 * Generates a SELECT AVG(field) portion of a query
+	 *
+	 * @param	string	the field
+	 * @param	string	an alias
+	 * @return	CI_DB_query_builder
+	 */
+	public function select_avg($select = '', $alias = '')
+	{
+		return $this->_max_min_avg_sum($select, $alias, 'AVG');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Select Sum
+	 *
+	 * Generates a SELECT SUM(field) portion of a query
+	 *
+	 * @param	string	the field
+	 * @param	string	an alias
+	 * @return	CI_DB_query_builder
+	 */
+	public function select_sum($select = '', $alias = '')
+	{
+		return $this->_max_min_avg_sum($select, $alias, 'SUM');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * SELECT [MAX|MIN|AVG|SUM]()
+	 *
+	 * @used-by	select_max()
+	 * @used-by	select_min()
+	 * @used-by	select_avg()
+	 * @used-by	select_sum()
+	 *
+	 * @param	string	$select	Field name
+	 * @param	string	$alias
+	 * @param	string	$type
+	 * @return	CI_DB_query_builder
+	 */
+	protected function _max_min_avg_sum($select = '', $alias = '', $type = 'MAX')
+	{
+		if ( ! is_string($select) OR $select === '')
+		{
+			$this->display_error('db_invalid_query');
+		}
+
+		$type = strtoupper($type);
+
+		if ( ! in_array($type, array('MAX', 'MIN', 'AVG', 'SUM')))
+		{
+			show_error('Invalid function type: '.$type);
+		}
+
+		if ($alias === '')
+		{
+			$alias = $this->_create_alias_from_table(trim($select));
+		}
+
+		$sql = $type.'('.$this->protect_identifiers(trim($select)).') AS '.$this->escape_identifiers(trim($alias));
+
+		$this->qb_select[] = $sql;
+		$this->qb_no_escape[] = NULL;
+
+		if ($this->qb_caching === TRUE)
+		{
+			$this->qb_cache_select[] = $sql;
+			$this->qb_cache_exists[] = 'select';
+		}
+
+		return $this;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Determines the alias name based on the table
+	 *
+	 * @param	string	$item
+	 * @return	string
+	 */
+	protected function _create_alias_from_table($item)
+	{
+		if (strpos($item, '.') !== FALSE)
+		{
+			$item = explode('.', $item);
+			return end($item);
+		}
+
+		return $item;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * DISTINCT
+	 *
+	 * Sets a flag which tells the query string compiler to add DISTINCT
+	 *
+	 * @param	bool	$val
+	 * @return	CI_DB_query_builder
+	 */
+	public function distinct($val = TRUE)
+	{
+		$this->qb_distinct = is_bool($val) ? $val : TRUE;
+		return $this;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * From
+	 *
+	 * Generates the FROM portion of the query
+	 *
+	 * @param	mixed	$from	can be a string or array
+	 * @return	CI_DB_query_builder
+	 */
+	public function from($from)
+	{
+		foreach ((array) $from as $val)
+		{
+			if (strpos($val, ',') !== FALSE)
+			{
+				foreach (explode(',', $val) as $v)
+				{
+					$v = trim($v);
+					$this->_track_aliases($v);
+
+					$this->qb_from[] = $v = $this->protect_identifiers($v, TRUE, NULL, FALSE);
+
+					if ($this->qb_caching === TRUE)
+					{
+						$this->qb_cache_from[] = $v;
+						$this->qb_cache_exists[] = 'from';
+					}
+				}
+			}
+			else
+			{
+				$val = trim($val);
+
+				// Extract any aliases that might exist. We use this information
+				// in the protect_identifiers to know whether to add a table prefix
+				$this->_track_aliases($val);
+
+				$this->qb_from[] = $val = $this->protect_identifiers($val, TRUE, NULL, FALSE);
+
+				if ($this->qb_caching === TRUE)
+				{
+					$this->qb_cache_from[] = $val;
+					$this->qb_cache_exists[] = 'from';
+				}
+			}
+		}
+
+		return $this;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * JOIN
+	 *
+	 * Generates the JOIN portion of the query
+	 *
+	 * @param	string
+	 * @param	string	the join condition
+	 * @param	string	the type of join
+	 * @param	string	whether not to try to escape identifiers
+	 * @return	CI_DB_query_builder
+	 */
+	public function join($table, $cond, $type = '', $escape = NULL)
+	{
+		if ($type !== '')
+		{
+			$type = strtoupper(trim($type));
+
+			if ( ! in_array($type, array('LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER'), TRUE))
+			{
+				$type = '';
+			}
+			else
+			{
+				$type .= ' ';
+			}
+		}
+
+		// Extract any aliases that might exist. We use this information
+		// in the protect_identifiers to know whether to add a table prefix
+		$this->_track_aliases($table);
+
+		is_bool($escape) OR $escape = $this->_protect_identifiers;
+
+		if ( ! $this->_has_operator($cond))
+		{
+			$cond = ' USING ('.($escape ? $this->escape_identifiers($cond) : $cond).')';
+		}
+		elseif ($escape === FALSE)
+		{
+			$cond = ' ON '.$cond;
+		}
+		else
+		{
+			// Split multiple conditions
+			if (preg_match_all('/\sAND\s|\sOR\s/i', $cond, $joints, PREG_OFFSET_CAPTURE))
+			{
+				$conditions = array();
+				$joints = $joints[0];
+				array_unshift($joints, array('', 0));
+
+				for ($i = count($joints) - 1, $pos = strlen($cond); $i >= 0; $i--)
+				{
+					$joints[$i][1] += strlen($joints[$i][0]); // offset
+					$conditions[$i] = substr($cond, $joints[$i][1], $pos - $joints[$i][1]);
+					$pos = $joints[$i][1] - strlen($joints[$i][0]);
+					$joints[$i] = $joints[$i][0];
+				}
+			}
+			else
+			{
+				$conditions = array($cond);
+				$joints = array('');
+			}
+
+			$cond = ' ON ';
+			for ($i = 0, $c = count($conditions); $i < $c; $i++)
+			{
+				$operator = $this->_get_operator($conditions[$i]);
+				$cond .= $joints[$i];
+				$cond .= preg_match("/(\(*)?([\[\]\w\.'-]+)".preg_quote($operator)."(.*)/i", $conditions[$i], $match)
+					? $match[1].$this->protect_identifiers($match[2]).$operator.$this->protect_identifiers($match[3])
+					: $conditions[$i];
+			}
+		}
+
+		// Do we want to escape the table name?
+		if ($escape === TRUE)
+		{
+			$table = $this->protect_identifiers($table, TRUE, NULL, FALSE);
+		}
+
+		// Assemble the JOIN statement
+		$this->qb_join[] = $join = $type.'JOIN '.$table.$cond;
+
+		if ($this->qb_caching === TRUE)
+		{
+			$this->qb_cache_join[] = $join;
+			$this->qb_cache_exists[] = 'join';
+		}
+
+		return $this;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * WHERE
+	 *
+	 * Generates the WHERE portion of the query.
+	 * Separates multiple calls with 'AND'.
+	 *
+	 * @param	mixed
+	 * @param	mixed
+	 * @param	bool
+	 * @return	CI_DB_query_builder
+	 */
+	public function where($key, $value = NULL, $escape = NULL)
+	{
+		return $this->_wh('qb_where', $key, $value, 'AND ', $escape);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * OR WHERE
+	 *
+	 * Generates the WHERE portion of the query.
+	 * Separates multiple calls with 'OR'.
+	 *
+	 * @param	mixed
+	 * @param	mixed
+	 * @param	bool
+	 * @return	CI_DB_query_builder
+	 */
+	public function or_where($key, $value = NULL, $escape = NULL)
+	{
+		return $this->_wh('qb_where', $key, $value, 'OR ', $escape);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * WHERE, HAVING
+	 *
+	 * @used-by	where()
+	 * @used-by	or_where()
+	 * @used-by	having()
+	 * @used-by	or_having()
+	 *
+	 * @param	string	$qb_key	'qb_where' or 'qb_having'
+	 * @param	mixed	$key
+	 * @param	mixed	$value
+	 * @param	string	$type
+	 * @param	bool	$escape
+	 * @return	CI_DB_query_builder
+	 */
+	protected function _wh($qb_key, $key, $value = NULL, $type = 'AND ', $escape = NULL)
+	{
+		$qb_cache_key = ($qb_key === 'qb_having') ? 'qb_cache_having' : 'qb_cache_where';
+
+		if ( ! is_array($key))
+		{
+			$key = array($key => $value);
+		}
+
+		// If the escape value was not set will base it on the global setting
+		is_bool($escape) OR $escape = $this->_protect_identifiers;
+
+		foreach ($key as $k => $v)
+		{
+			$prefix = (count($this->$qb_key) === 0 && count($this->$qb_cache_key) === 0)
+				? $this->_group_get_type('')
+				: $this->_group_get_type($type);
+
+			if ($v !== NULL)
+			{
+				if ($escape === TRUE)
+				{
+					$v = ' '.$this->escape($v);
+				}
+
+				if ( ! $this->_has_operator($k))
+				{
+					$k .= ' = ';
+				}
+			}
+			elseif ( ! $this->_has_operator($k))
+			{
+				// value appears not to have been set, assign the test to IS NULL
+				$k .= ' IS NULL';
+			}
+			elseif (preg_match('/\s*(!?=|<>|\sIS(?:\s+NOT)?\s)\s*$/i', $k, $match, PREG_OFFSET_CAPTURE))
+			{
+				$k = substr($k, 0, $match[0][1]).($match[1][0] === '=' ? ' IS NULL' : ' IS NOT NULL');
+			}
+
+			$this->{$qb_key}[] = array('condition' => $prefix.$k.$v, 'escape' => $escape);
+			if ($this->qb_caching === TRUE)
+			{
+				$this->{$qb_cache_key}[] = array('condition' => $prefix.$k.$v, 'escape' => $escape);
+				$this->qb_cache_exists[] = substr($qb_key, 3);
+			}
+
+		}
+
+		return $this;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * WHERE IN
+	 *
+	 * Generates a WHERE field IN('item', 'item') SQL query,
+	 * joined with 'AND' if appropriate.
+	 *
+	 * @param	string	$key	The field to search
+	 * @param	array	$values	The values searched on
+	 * @param	bool	$escape
+	 * @return	CI_DB_query_builder
+	 */
+	public function where_in($key = NULL, $values = NULL, $escape = NULL)
+	{
+		return $this->_where_in($key, $values, FALSE, 'AND ', $escape);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * OR WHERE IN
+	 *
+	 * Generates a WHERE field IN('item', 'item') SQL query,
+	 * joined with 'OR' if appropriate.
+	 *
+	 * @param	string	$key	The field to search
+	 * @param	array	$values	The values searched on
+	 * @param	bool	$escape
+	 * @return	CI_DB_query_builder
+	 */
+	public function or_where_in($key = NULL, $values = NULL, $escape = NULL)
+	{
+		return $this->_where_in($key, $values, FALSE, 'OR ', $escape);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * WHERE NOT IN
+	 *
+	 * Generates a WHERE field NOT IN('item', 'item') SQL query,
+	 * joined with 'AND' if appropriate.
+	 *
+	 * @param	string	$key	The field to search
+	 * @param	array	$values	The values searched on
+	 * @param	bool	$escape
+	 * @return	CI_DB_query_builder
+	 */
+	public function where_not_in($key = NULL, $values = NULL, $escape = NULL)
+	{
+		return $this->_where_in($key, $values, TRUE, 'AND ', $escape);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * OR WHERE NOT IN
+	 *
+	 * Generates a WHERE field NOT IN('item', 'item') SQL query,
+	 * joined with 'OR' if appropriate.
+	 *
+	 * @param	string	$key	The field to search
+	 * @param	array	$values	The values searched on
+	 * @param	bool	$escape
+	 * @return	CI_DB_query_builder
+	 */
+	public function or_where_not_in($key = NULL, $values = NULL, $escape = NULL)
+	{
+		return $this->_where_in($key, $values, TRUE, 'OR ', $escape);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Internal WHERE IN
+	 *
+	 * @used-by	where_in()
+	 * @used-by	or_where_in()
+	 * @used-by	where_not_in()
+	 * @used-by	or_where_not_in()
+	 *
+	 * @param	string	$key	The field to search
+	 * @param	array	$values	The values searched on
+	 * @param	bool	$not	If the statement would be IN or NOT IN
+	 * @param	string	$type
+	 * @param	bool	$escape
+	 * @return	CI_DB_query_builder
+	 */
+	protected function _where_in($key = NULL, $values = NULL, $not = FALSE, $type = 'AND ', $escape = NULL)
+	{
+		if ($key === NULL OR $values === NULL)
+		{
+			return $this;
+		}
+
+		if ( ! is_array($values))
+		{
+			$values = array($values);
+		}
+
+		is_bool($escape) OR $escape = $this->_protect_identifiers;
+
+		$not = ($not) ? ' NOT' : '';
+
+		if ($escape === TRUE)
+		{
+			$where_in = array();
+			foreach ($values as $value)
+			{
+				$where_in[] = $this->escape($value);
+			}
+		}
+		else
+		{
+			$where_in = array_values($values);
+		}
+
+		$prefix = (count($this->qb_where) === 0 && count($this->qb_cache_where) === 0)
+			? $this->_group_get_type('')
+			: $this->_group_get_type($type);
+
+		$where_in = array(
+			'condition' => $prefix.$key.$not.' IN('.implode(', ', $where_in).')',
+			'escape' => $escape
+		);
+
+		$this->qb_where[] = $where_in;
+		if ($this->qb_caching === TRUE)
+		{
+			$this->qb_cache_where[] = $where_in;
+			$this->qb_cache_exists[] = 'where';
+		}
+
+		return $this;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * LIKE
+	 *
+	 * Generates a %LIKE% portion of the query.
+	 * Separates multiple calls with 'AND'.
+	 *
+	 * @param	mixed	$field
+	 * @param	string	$match
+	 * @param	string	$side
+	 * @param	bool	$escape
+	 * @return	CI_DB_query_builder
+	 */
+	public function like($field, $match = '', $side = 'both', $escape = NULL)
+	{
+		return $this->_like($field, $match, 'AND ', $side, '', $escape);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * NOT LIKE
+	 *
+	 * Generates a NOT LIKE portion of the query.
+	 * Separates multiple calls with 'AND'.
+	 *
+	 * @param	mixed	$field
+	 * @param	string	$match
+	 * @param	string	$side
+	 * @param	bool	$escape
+	 * @return	CI_DB_query_builder
+	 */
+	public function not_like($field, $match = '', $side = 'both', $escape = NULL)
+	{
+		return $this->_like($field, $match, 'AND ', $side, 'NOT', $escape);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * OR LIKE
+	 *
+	 * Generates a %LIKE% portion of the query.
+	 * Separates multiple calls with 'OR'.
+	 *
+	 * @param	mixed	$field
+	 * @param	string	$match
+	 * @param	string	$side
+	 * @param	bool	$escape
+	 * @return	CI_DB_query_builder
+	 */
+	public function or_like($field, $match = '', $side = 'both', $escape = NULL)
+	{
+		return $this->_like($field, $match, 'OR ', $side, '', $escape);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * OR NOT LIKE
+	 *
+	 * Generates a NOT LIKE portion of the query.
+	 * Separates multiple calls with 'OR'.
+	 *
+	 * @param	mixed	$field
+	 * @param	string	$match
+	 * @param	string	$side
+	 * @param	bool	$escape
+	 * @return	CI_DB_query_builder
+	 */
+	public function or_not_like($field, $match = '', $side = 'both', $escape = NULL)
+	{
+		return $this->_like($field, $match, 'OR ', $side, 'NOT', $escape);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Internal LIKE
+	 *
+	 * @used-by	like()
+	 * @used-by	or_like()
+	 * @used-by	not_like()
+	 * @used-by	or_not_like()
+	 *
+	 * @param	mixed	$field
+	 * @param	string	$match
+	 * @param	string	$type
+	 * @param	string	$side
+	 * @param	string	$not
+	 * @param	bool	$escape
+	 * @return	CI_DB_query_builder
+	 */
+	protected function _like($field, $match = '', $type = 'AND ', $side = 'both', $not = '', $escape = NULL)
+	{
+		if ( ! is_array($field))
+		{
+			$field = array($field => $match);
+		}
+
+		is_bool($escape) OR $escape = $this->_protect_identifiers;
+		// lowercase $side in case somebody writes e.g. 'BEFORE' instead of 'before' (doh)
+		$side = strtolower($side);
+
+		foreach ($field as $k => $v)
+		{
+			$prefix = (count($this->qb_where) === 0 && count($this->qb_cache_where) === 0)
+				? $this->_group_get_type('') : $this->_group_get_type($type);
+
+			if ($escape === TRUE)
+			{
+				$v = $this->escape_like_str($v);
+			}
+
+			if ($side === 'none')
+			{
+				$like_statement = "{$prefix} {$k} {$not} LIKE '{$v}'";
+			}
+			elseif ($side === 'before')
+			{
+				$like_statement = "{$prefix} {$k} {$not} LIKE '%{$v}'";
+			}
+			elseif ($side === 'after')
+			{
+				$like_statement = "{$prefix} {$k} {$not} LIKE '{$v}%'";
+			}
+			else
+			{
+				$like_statement = "{$prefix} {$k} {$not} LIKE '%{$v}%'";
+			}
+
+			// some platforms require an escape sequence definition for LIKE wildcards
+			if ($escape === TRUE && $this->_like_escape_str !== '')
+			{
+				$like_statement .= sprintf($this->_like_escape_str, $this->_like_escape_chr);
+			}
+
+			$this->qb_where[] = array('condition' => $like_statement, 'escape' => $escape);
+			if ($this->qb_caching === TRUE)
+			{
+				$this->qb_cache_where[] = array('condition' => $like_statement, 'escape' => $escape);
+				$this->qb_cache_exists[] = 'where';
+			}
+		}
+
+		return $this;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Starts a query group.
+	 *
+	 * @param	string	$not	(Internal use only)
+	 * @param	string	$type	(Internal use only)
+	 * @return	CI_DB_query_builder
+	 */
+	public function group_start($not = '', $type = 'AND ')
+	{
+		$type = $this->_group_get_type($type);
+
+		$this->qb_where_group_started = TRUE;
+		$prefix = (count($this->qb_where) === 0 && count($this->qb_cache_where) === 0) ? '' : $type;
+		$where = array(
+			'condition' => $prefix.$not.str_repeat(' ', ++$this->qb_where_group_count).' (',
+			'escape' => FALSE
+		);
+
+		$this->qb_where[] = $where;
+		if ($this->qb_caching)
+		{
+			$this->qb_cache_where[] = $where;
+		}
+
+		return $this;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Starts a query group, but ORs the group
+	 *
+	 * @return	CI_DB_query_builder
+	 */
+	public function or_group_start()
+	{
+		return $this->group_start('', 'OR ');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Starts a query group, but NOTs the group
+	 *
+	 * @return	CI_DB_query_builder
+	 */
+	public function not_group_start()
+	{
+		return $this->group_start('NOT ', 'AND ');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Starts a query group, but OR NOTs the group
+	 *
+	 * @return	CI_DB_query_builder
+	 */
+	public function or_not_group_start()
+	{
+		return $this->group_start('NOT ', 'OR ');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Ends a query group
+	 *
+	 * @return	CI_DB_query_builder
+	 */
+	public function group_end()
+	{
+		$this->qb_where_group_started = FALSE;
+		$where = array(
+			'condition' => str_repeat(' ', $this->qb_where_group_count--).')',
+			'escape' => FALSE
+		);
+
+		$this->qb_where[] = $where;
+		if ($this->qb_caching)
+		{
+			$this->qb_cache_where[] = $where;
+		}
+
+		return $this;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Group_get_type
+	 *
+	 * @used-by	group_start()
+	 * @used-by	_like()
+	 * @used-by	_wh()
+	 * @used-by	_where_in()
+	 *
+	 * @param	string	$type
+	 * @return	string
+	 */
+	protected function _group_get_type($type)
+	{
+		if ($this->qb_where_group_started)
+		{
+			$type = '';
+			$this->qb_where_group_started = FALSE;
+		}
+
+		return $type;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * GROUP BY
+	 *
+	 * @param	string	$by
+	 * @param	bool	$escape
+	 * @return	CI_DB_query_builder
+	 */
+	public function group_by($by, $escape = NULL)
+	{
+		is_bool($escape) OR $escape = $this->_protect_identifiers;
+
+		if (is_string($by))
+		{
+			$by = ($escape === TRUE)
+				? explode(',', $by)
+				: array($by);
+		}
+
+		foreach ($by as $val)
+		{
+			$val = trim($val);
+
+			if ($val !== '')
+			{
+				$val = array('field' => $val, 'escape' => $escape);
+
+				$this->qb_groupby[] = $val;
+				if ($this->qb_caching === TRUE)
+				{
+					$this->qb_cache_groupby[] = $val;
+					$this->qb_cache_exists[] = 'groupby';
+				}
+			}
+		}
+
+		return $this;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * HAVING
+	 *
+	 * Separates multiple calls with 'AND'.
+	 *
+	 * @param	string	$key
+	 * @param	string	$value
+	 * @param	bool	$escape
+	 * @return	CI_DB_query_builder
+	 */
+	public function having($key, $value = NULL, $escape = NULL)
+	{
+		return $this->_wh('qb_having', $key, $value, 'AND ', $escape);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * OR HAVING
+	 *
+	 * Separates multiple calls with 'OR'.
+	 *
+	 * @param	string	$key
+	 * @param	string	$value
+	 * @param	bool	$escape
+	 * @return	CI_DB_query_builder
+	 */
+	public function or_having($key, $value = NULL, $escape = NULL)
+	{
+		return $this->_wh('qb_having', $key, $value, 'OR ', $escape);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ORDER BY
+	 *
+	 * @param	string	$orderby
+	 * @param	string	$direction	ASC, DESC or RANDOM
+	 * @param	bool	$escape
+	 * @return	CI_DB_query_builder
+	 */
+	public function order_by($orderby, $direction = '', $escape = NULL)
+	{
+		$direction = strtoupper(trim($direction));
+
+		if ($direction === 'RANDOM')
+		{
+			$direction = '';
+
+			// Do we have a seed value?
+			$orderby = ctype_digit((string) $orderby)
+				? sprintf($this->_random_keyword[1], $orderby)
+				: $this->_random_keyword[0];
+		}
+		elseif (empty($orderby))
+		{
+			return $this;
+		}
+		elseif ($direction !== '')
+		{
+			$direction = in_array($direction, array('ASC', 'DESC'), TRUE) ? ' '.$direction : '';
+		}
+
+		is_bool($escape) OR $escape = $this->_protect_identifiers;
+
+		if ($escape === FALSE)
+		{
+			$qb_orderby[] = array('field' => $orderby, 'direction' => $direction, 'escape' => FALSE);
+		}
+		else
+		{
+			$qb_orderby = array();
+			foreach (explode(',', $orderby) as $field)
+			{
+				$qb_orderby[] = ($direction === '' && preg_match('/\s+(ASC|DESC)$/i', rtrim($field), $match, PREG_OFFSET_CAPTURE))
+					? array('field' => ltrim(substr($field, 0, $match[0][1])), 'direction' => ' '.$match[1][0], 'escape' => TRUE)
+					: array('field' => trim($field), 'direction' => $direction, 'escape' => TRUE);
+			}
+		}
+
+		$this->qb_orderby = array_merge($this->qb_orderby, $qb_orderby);
+		if ($this->qb_caching === TRUE)
+		{
+			$this->qb_cache_orderby = array_merge($this->qb_cache_orderby, $qb_orderby);
+			$this->qb_cache_exists[] = 'orderby';
+		}
+
+		return $this;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * LIMIT
+	 *
+	 * @param	int	$value	LIMIT value
+	 * @param	int	$offset	OFFSET value
+	 * @return	CI_DB_query_builder
+	 */
+	public function limit($value, $offset = 0)
+	{
+		is_null($value) OR $this->qb_limit = (int) $value;
+		empty($offset) OR $this->qb_offset = (int) $offset;
+
+		return $this;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Sets the OFFSET value
+	 *
+	 * @param	int	$offset	OFFSET value
+	 * @return	CI_DB_query_builder
+	 */
+	public function offset($offset)
+	{
+		empty($offset) OR $this->qb_offset = (int) $offset;
+		return $this;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * LIMIT string
+	 *
+	 * Generates a platform-specific LIMIT clause.
+	 *
+	 * @param	string	$sql	SQL Query
+	 * @return	string
+	 */
+	protected function _limit($sql)
+	{
+		return $sql.' LIMIT '.($this->qb_offset ? $this->qb_offset.', ' : '').(int) $this->qb_limit;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * The "set" function.
+	 *
+	 * Allows key/value pairs to be set for inserting or updating
+	 *
+	 * @param	mixed
+	 * @param	string
+	 * @param	bool
+	 * @return	CI_DB_query_builder
+	 */
+	public function set($key, $value = '', $escape = NULL)
+	{
+		$key = $this->_object_to_array($key);
+
+		if ( ! is_array($key))
+		{
+			$key = array($key => $value);
+		}
+
+		is_bool($escape) OR $escape = $this->_protect_identifiers;
+
+		foreach ($key as $k => $v)
+		{
+			$this->qb_set[$this->protect_identifiers($k, FALSE, $escape)] = ($escape)
+				? $this->escape($v) : $v;
+		}
+
+		return $this;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Get SELECT query string
+	 *
+	 * Compiles a SELECT query string and returns the sql.
+	 *
+	 * @param	string	the table name to select from (optional)
+	 * @param	bool	TRUE: resets QB values; FALSE: leave QB values alone
+	 * @return	string
+	 */
+	public function get_compiled_select($table = '', $reset = TRUE)
+	{
+		if ($table !== '')
+		{
+			$this->_track_aliases($table);
+			$this->from($table);
+		}
+
+		$select = $this->_compile_select();
+
+		if ($reset === TRUE)
+		{
+			$this->_reset_select();
+		}
+
+		return $select;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Get
+	 *
+	 * Compiles the select statement based on the other functions called
+	 * and runs the query
+	 *
+	 * @param	string	the table
+	 * @param	string	the limit clause
+	 * @param	string	the offset clause
+	 * @return	CI_DB_result
+	 */
+	public function get($table = '', $limit = NULL, $offset = NULL)
+	{
+		if ($table !== '')
+		{
+			$this->_track_aliases($table);
+			$this->from($table);
+		}
+
+		if ( ! empty($limit))
+		{
+			$this->limit($limit, $offset);
+		}
+
+		$result = $this->query($this->_compile_select());
+		$this->_reset_select();
+		return $result;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * "Count All Results" query
+	 *
+	 * Generates a platform-specific query string that counts all records
+	 * returned by an Query Builder query.
+	 *
+	 * @param	string
+	 * @param	bool	the reset clause
+	 * @return	int
+	 */
+	public function count_all_results($table = '', $reset = TRUE)
+	{
+		if ($table !== '')
+		{
+			$this->_track_aliases($table);
+			$this->from($table);
+		}
+
+		// ORDER BY usage is often problematic here (most notably
+		// on Microsoft SQL Server) and ultimately unnecessary
+		// for selecting COUNT(*) ...
+		$qb_orderby       = $this->qb_orderby;
+		$qb_cache_orderby = $this->qb_cache_orderby;
+		$this->qb_orderby = $this->qb_cache_orderby = array();
+
+		$result = ($this->qb_distinct === TRUE OR ! empty($this->qb_groupby) OR ! empty($this->qb_cache_groupby) OR $this->qb_limit OR $this->qb_offset)
+			? $this->query($this->_count_string.$this->protect_identifiers('numrows')."\nFROM (\n".$this->_compile_select()."\n) CI_count_all_results")
+			: $this->query($this->_compile_select($this->_count_string.$this->protect_identifiers('numrows')));
+
+		if ($reset === TRUE)
+		{
+			$this->_reset_select();
+		}
+		else
+		{
+			$this->qb_orderby       = $qb_orderby;
+			$this->qb_cache_orderby = $qb_cache_orderby;
+		}
+
+		if ($result->num_rows() === 0)
+		{
+			return 0;
+		}
+
+		$row = $result->row();
+		return (int) $row->numrows;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Get_Where
+	 *
+	 * Allows the where clause, limit and offset to be added directly
+	 *
+	 * @param	string	$table
+	 * @param	string	$where
+	 * @param	int	$limit
+	 * @param	int	$offset
+	 * @return	CI_DB_result
+	 */
+	public function get_where($table = '', $where = NULL, $limit = NULL, $offset = NULL)
+	{
+		if ($table !== '')
+		{
+			$this->from($table);
+		}
+
+		if ($where !== NULL)
+		{
+			$this->where($where);
+		}
+
+		if ( ! empty($limit))
+		{
+			$this->limit($limit, $offset);
+		}
+
+		$result = $this->query($this->_compile_select());
+		$this->_reset_select();
+		return $result;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Insert_Batch
+	 *
+	 * Compiles batch insert strings and runs the queries
+	 *
+	 * @param	string	$table	Table to insert into
+	 * @param	array	$set 	An associative array of insert values
+	 * @param	bool	$escape	Whether to escape values and identifiers
+	 * @return	int	Number of rows inserted or FALSE on failure
+	 */
+	public function insert_batch($table, $set = NULL, $escape = NULL, $batch_size = 100)
+	{
+		if ($set === NULL)
+		{
+			if (empty($this->qb_set))
+			{
+				return ($this->db_debug) ? $this->display_error('db_must_use_set') : FALSE;
+			}
+		}
+		else
+		{
+			if (empty($set))
+			{
+				return ($this->db_debug) ? $this->display_error('insert_batch() called with no data') : FALSE;
+			}
+
+			$this->set_insert_batch($set, '', $escape);
+		}
+
+		if (strlen($table) === 0)
+		{
+			if ( ! isset($this->qb_from[0]))
+			{
+				return ($this->db_debug) ? $this->display_error('db_must_set_table') : FALSE;
+			}
+
+			$table = $this->qb_from[0];
+		}
+
+		// Batch this baby
+		$affected_rows = 0;
+		for ($i = 0, $total = count($this->qb_set); $i < $total; $i += $batch_size)
+		{
+			if ($this->query($this->_insert_batch($this->protect_identifiers($table, TRUE, $escape, FALSE), $this->qb_keys, array_slice($this->qb_set, $i, $batch_size))))
+			{
+				$affected_rows += $this->affected_rows();
+			}
+		}
+
+		$this->_reset_write();
+		return $affected_rows;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Insert batch statement
+	 *
+	 * Generates a platform-specific insert string from the supplied data.
+	 *
+	 * @param	string	$table	Table name
+	 * @param	array	$keys	INSERT keys
+	 * @param	array	$values	INSERT values
+	 * @return	string
+	 */
+	protected function _insert_batch($table, $keys, $values)
+	{
+		return 'INSERT INTO '.$table.' ('.implode(', ', $keys).') VALUES '.implode(', ', $values);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * The "set_insert_batch" function.  Allows key/value pairs to be set for batch inserts
+	 *
+	 * @param	mixed
+	 * @param	string
+	 * @param	bool
+	 * @return	CI_DB_query_builder
+	 */
+	public function set_insert_batch($key, $value = '', $escape = NULL)
+	{
+		$key = $this->_object_to_array_batch($key);
+
+		if ( ! is_array($key))
+		{
+			$key = array($key => $value);
+		}
+
+		is_bool($escape) OR $escape = $this->_protect_identifiers;
+
+		$keys = array_keys($this->_object_to_array(reset($key)));
+		sort($keys);
+
+		foreach ($key as $row)
+		{
+			$row = $this->_object_to_array($row);
+			if (count(array_diff($keys, array_keys($row))) > 0 OR count(array_diff(array_keys($row), $keys)) > 0)
+			{
+				// batch function above returns an error on an empty array
+				$this->qb_set[] = array();
+				return;
+			}
+
+			ksort($row); // puts $row in the same order as our keys
+
+			if ($escape !== FALSE)
+			{
+				$clean = array();
+				foreach ($row as $value)
+				{
+					$clean[] = $this->escape($value);
+				}
+
+				$row = $clean;
+			}
+
+			$this->qb_set[] = '('.implode(',', $row).')';
+		}
+
+		foreach ($keys as $k)
+		{
+			$this->qb_keys[] = $this->protect_identifiers($k, FALSE, $escape);
+		}
+
+		return $this;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Get INSERT query string
+	 *
+	 * Compiles an insert query and returns the sql
+	 *
+	 * @param	string	the table to insert into
+	 * @param	bool	TRUE: reset QB values; FALSE: leave QB values alone
+	 * @return	string
+	 */
+	public function get_compiled_insert($table = '', $reset = TRUE)
+	{
+		if ($this->_validate_insert($table) === FALSE)
+		{
+			return FALSE;
+		}
+
+		$sql = $this->_insert(
+			$this->protect_identifiers(
+				$this->qb_from[0], TRUE, NULL, FALSE
+			),
+			array_keys($this->qb_set),
+			array_values($this->qb_set)
+		);
+
+		if ($reset === TRUE)
+		{
+			$this->_reset_write();
+		}
+
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Insert
+	 *
+	 * Compiles an insert string and runs the query
+	 *
+	 * @param	string	the table to insert data into
+	 * @param	array	an associative array of insert values
+	 * @param	bool	$escape	Whether to escape values and identifiers
+	 * @return	bool	TRUE on success, FALSE on failure
+	 */
+	public function insert($table = '', $set = NULL, $escape = NULL)
+	{
+		if ($set !== NULL)
+		{
+			$this->set($set, '', $escape);
+		}
+
+		if ($this->_validate_insert($table) === FALSE)
+		{
+			return FALSE;
+		}
+
+		$sql = $this->_insert(
+			$this->protect_identifiers(
+				$this->qb_from[0], TRUE, $escape, FALSE
+			),
+			array_keys($this->qb_set),
+			array_values($this->qb_set)
+		);
+
+		$this->_reset_write();
+		return $this->query($sql);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Validate Insert
+	 *
+	 * This method is used by both insert() and get_compiled_insert() to
+	 * validate that the there data is actually being set and that table
+	 * has been chosen to be inserted into.
+	 *
+	 * @param	string	the table to insert data into
+	 * @return	string
+	 */
+	protected function _validate_insert($table = '')
+	{
+		if (count($this->qb_set) === 0)
+		{
+			return ($this->db_debug) ? $this->display_error('db_must_use_set') : FALSE;
+		}
+
+		if ($table !== '')
+		{
+			$this->qb_from[0] = $table;
+		}
+		elseif ( ! isset($this->qb_from[0]))
+		{
+			return ($this->db_debug) ? $this->display_error('db_must_set_table') : FALSE;
+		}
+
+		return TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Replace
+	 *
+	 * Compiles an replace into string and runs the query
+	 *
+	 * @param	string	the table to replace data into
+	 * @param	array	an associative array of insert values
+	 * @return	bool	TRUE on success, FALSE on failure
+	 */
+	public function replace($table = '', $set = NULL)
+	{
+		if ($set !== NULL)
+		{
+			$this->set($set);
+		}
+
+		if (count($this->qb_set) === 0)
+		{
+			return ($this->db_debug) ? $this->display_error('db_must_use_set') : FALSE;
+		}
+
+		if ($table === '')
+		{
+			if ( ! isset($this->qb_from[0]))
+			{
+				return ($this->db_debug) ? $this->display_error('db_must_set_table') : FALSE;
+			}
+
+			$table = $this->qb_from[0];
+		}
+
+		$sql = $this->_replace($this->protect_identifiers($table, TRUE, NULL, FALSE), array_keys($this->qb_set), array_values($this->qb_set));
+
+		$this->_reset_write();
+		return $this->query($sql);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Replace statement
+	 *
+	 * Generates a platform-specific replace string from the supplied data
+	 *
+	 * @param	string	the table name
+	 * @param	array	the insert keys
+	 * @param	array	the insert values
+	 * @return	string
+	 */
+	protected function _replace($table, $keys, $values)
+	{
+		return 'REPLACE INTO '.$table.' ('.implode(', ', $keys).') VALUES ('.implode(', ', $values).')';
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * FROM tables
+	 *
+	 * Groups tables in FROM clauses if needed, so there is no confusion
+	 * about operator precedence.
+	 *
+	 * Note: This is only used (and overridden) by MySQL and CUBRID.
+	 *
+	 * @return	string
+	 */
+	protected function _from_tables()
+	{
+		return implode(', ', $this->qb_from);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Get UPDATE query string
+	 *
+	 * Compiles an update query and returns the sql
+	 *
+	 * @param	string	the table to update
+	 * @param	bool	TRUE: reset QB values; FALSE: leave QB values alone
+	 * @return	string
+	 */
+	public function get_compiled_update($table = '', $reset = TRUE)
+	{
+		// Combine any cached components with the current statements
+		$this->_merge_cache();
+
+		if ($this->_validate_update($table) === FALSE)
+		{
+			return FALSE;
+		}
+
+		$sql = $this->_update($this->qb_from[0], $this->qb_set);
+
+		if ($reset === TRUE)
+		{
+			$this->_reset_write();
+		}
+
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * UPDATE
+	 *
+	 * Compiles an update string and runs the query.
+	 *
+	 * @param	string	$table
+	 * @param	array	$set	An associative array of update values
+	 * @param	mixed	$where
+	 * @param	int	$limit
+	 * @return	bool	TRUE on success, FALSE on failure
+	 */
+	public function update($table = '', $set = NULL, $where = NULL, $limit = NULL)
+	{
+		// Combine any cached components with the current statements
+		$this->_merge_cache();
+
+		if ($set !== NULL)
+		{
+			$this->set($set);
+		}
+
+		if ($this->_validate_update($table) === FALSE)
+		{
+			return FALSE;
+		}
+
+		if ($where !== NULL)
+		{
+			$this->where($where);
+		}
+
+		if ( ! empty($limit))
+		{
+			$this->limit($limit);
+		}
+
+		$sql = $this->_update($this->qb_from[0], $this->qb_set);
+		$this->_reset_write();
+		return $this->query($sql);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Validate Update
+	 *
+	 * This method is used by both update() and get_compiled_update() to
+	 * validate that data is actually being set and that a table has been
+	 * chosen to be update.
+	 *
+	 * @param	string	the table to update data on
+	 * @return	bool
+	 */
+	protected function _validate_update($table)
+	{
+		if (count($this->qb_set) === 0)
+		{
+			return ($this->db_debug) ? $this->display_error('db_must_use_set') : FALSE;
+		}
+
+		if ($table !== '')
+		{
+			$this->qb_from = array($this->protect_identifiers($table, TRUE, NULL, FALSE));
+		}
+		elseif ( ! isset($this->qb_from[0]))
+		{
+			return ($this->db_debug) ? $this->display_error('db_must_set_table') : FALSE;
+		}
+
+		return TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Update_Batch
+	 *
+	 * Compiles an update string and runs the query
+	 *
+	 * @param	string	the table to retrieve the results from
+	 * @param	array	an associative array of update values
+	 * @param	string	the where key
+	 * @return	int	number of rows affected or FALSE on failure
+	 */
+	public function update_batch($table, $set = NULL, $index = NULL, $batch_size = 100)
+	{
+		// Combine any cached components with the current statements
+		$this->_merge_cache();
+
+		if ($index === NULL)
+		{
+			return ($this->db_debug) ? $this->display_error('db_must_use_index') : FALSE;
+		}
+
+		if ($set === NULL)
+		{
+			if (empty($this->qb_set_ub))
+			{
+				return ($this->db_debug) ? $this->display_error('db_must_use_set') : FALSE;
+			}
+		}
+		else
+		{
+			if (empty($set))
+			{
+				return ($this->db_debug) ? $this->display_error('update_batch() called with no data') : FALSE;
+			}
+
+			$this->set_update_batch($set, $index);
+		}
+
+		if (strlen($table) === 0)
+		{
+			if ( ! isset($this->qb_from[0]))
+			{
+				return ($this->db_debug) ? $this->display_error('db_must_set_table') : FALSE;
+			}
+
+			$table = $this->qb_from[0];
+		}
+
+		// Batch this baby
+		$affected_rows = 0;
+		for ($i = 0, $total = count($this->qb_set_ub); $i < $total; $i += $batch_size)
+		{
+			if ($this->query($this->_update_batch($this->protect_identifiers($table, TRUE, NULL, FALSE), array_slice($this->qb_set_ub, $i, $batch_size), $index)))
+			{
+				$affected_rows += $this->affected_rows();
+			}
+
+			$this->qb_where = array();
+		}
+
+		$this->_reset_write();
+		return $affected_rows;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Update_Batch statement
+	 *
+	 * Generates a platform-specific batch update string from the supplied data
+	 *
+	 * @param	string	$table	Table name
+	 * @param	array	$values	Update data
+	 * @param	string	$index	WHERE key
+	 * @return	string
+	 */
+	protected function _update_batch($table, $values, $index)
+	{
+		$ids = array();
+		foreach ($values as $key => $val)
+		{
+			$ids[] = $val[$index]['value'];
+
+			foreach (array_keys($val) as $field)
+			{
+				if ($field !== $index)
+				{
+					$final[$val[$field]['field']][] = 'WHEN '.$val[$index]['field'].' = '.$val[$index]['value'].' THEN '.$val[$field]['value'];
+				}
+			}
+		}
+
+		$cases = '';
+		foreach ($final as $k => $v)
+		{
+			$cases .= $k." = CASE \n"
+				.implode("\n", $v)."\n"
+				.'ELSE '.$k.' END, ';
+		}
+
+		$this->where($val[$index]['field'].' IN('.implode(',', $ids).')', NULL, FALSE);
+
+		return 'UPDATE '.$table.' SET '.substr($cases, 0, -2).$this->_compile_wh('qb_where');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * The "set_update_batch" function.  Allows key/value pairs to be set for batch updating
+	 *
+	 * @param	array
+	 * @param	string
+	 * @param	bool
+	 * @return	CI_DB_query_builder
+	 */
+	public function set_update_batch($key, $index = '', $escape = NULL)
+	{
+		$key = $this->_object_to_array_batch($key);
+
+		if ( ! is_array($key))
+		{
+			// @todo error
+		}
+
+		is_bool($escape) OR $escape = $this->_protect_identifiers;
+
+		foreach ($key as $k => $v)
+		{
+			$index_set = FALSE;
+			$clean = array();
+			foreach ($v as $k2 => $v2)
+			{
+				if ($k2 === $index)
+				{
+					$index_set = TRUE;
+				}
+
+				$clean[$k2] = array(
+					'field'  => $this->protect_identifiers($k2, FALSE, $escape),
+					'value'  => ($escape === FALSE ? $v2 : $this->escape($v2))
+				);
+			}
+
+			if ($index_set === FALSE)
+			{
+				return $this->display_error('db_batch_missing_index');
+			}
+
+			$this->qb_set_ub[] = $clean;
+		}
+
+		return $this;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Empty Table
+	 *
+	 * Compiles a delete string and runs "DELETE FROM table"
+	 *
+	 * @param	string	the table to empty
+	 * @return	bool	TRUE on success, FALSE on failure
+	 */
+	public function empty_table($table = '')
+	{
+		if ($table === '')
+		{
+			if ( ! isset($this->qb_from[0]))
+			{
+				return ($this->db_debug) ? $this->display_error('db_must_set_table') : FALSE;
+			}
+
+			$table = $this->qb_from[0];
+		}
+		else
+		{
+			$table = $this->protect_identifiers($table, TRUE, NULL, FALSE);
+		}
+
+		$sql = $this->_delete($table);
+		$this->_reset_write();
+		return $this->query($sql);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Truncate
+	 *
+	 * Compiles a truncate string and runs the query
+	 * If the database does not support the truncate() command
+	 * This function maps to "DELETE FROM table"
+	 *
+	 * @param	string	the table to truncate
+	 * @return	bool	TRUE on success, FALSE on failure
+	 */
+	public function truncate($table = '')
+	{
+		if ($table === '')
+		{
+			if ( ! isset($this->qb_from[0]))
+			{
+				return ($this->db_debug) ? $this->display_error('db_must_set_table') : FALSE;
+			}
+
+			$table = $this->qb_from[0];
+		}
+		else
+		{
+			$table = $this->protect_identifiers($table, TRUE, NULL, FALSE);
+		}
+
+		$sql = $this->_truncate($table);
+		$this->_reset_write();
+		return $this->query($sql);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Truncate statement
+	 *
+	 * Generates a platform-specific truncate string from the supplied data
+	 *
+	 * If the database does not support the truncate() command,
+	 * then this method maps to 'DELETE FROM table'
+	 *
+	 * @param	string	the table name
+	 * @return	string
+	 */
+	protected function _truncate($table)
+	{
+		return 'TRUNCATE '.$table;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Get DELETE query string
+	 *
+	 * Compiles a delete query string and returns the sql
+	 *
+	 * @param	string	the table to delete from
+	 * @param	bool	TRUE: reset QB values; FALSE: leave QB values alone
+	 * @return	string
+	 */
+	public function get_compiled_delete($table = '', $reset = TRUE)
+	{
+		$this->return_delete_sql = TRUE;
+		$sql = $this->delete($table, '', NULL, $reset);
+		$this->return_delete_sql = FALSE;
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Delete
+	 *
+	 * Compiles a delete string and runs the query
+	 *
+	 * @param	mixed	the table(s) to delete from. String or array
+	 * @param	mixed	the where clause
+	 * @param	mixed	the limit clause
+	 * @param	bool
+	 * @return	mixed
+	 */
+	public function delete($table = '', $where = '', $limit = NULL, $reset_data = TRUE)
+	{
+		// Combine any cached components with the current statements
+		$this->_merge_cache();
+
+		if ($table === '')
+		{
+			if ( ! isset($this->qb_from[0]))
+			{
+				return ($this->db_debug) ? $this->display_error('db_must_set_table') : FALSE;
+			}
+
+			$table = $this->qb_from[0];
+		}
+		elseif (is_array($table))
+		{
+			empty($where) && $reset_data = FALSE;
+
+			foreach ($table as $single_table)
+			{
+				$this->delete($single_table, $where, $limit, $reset_data);
+			}
+
+			return;
+		}
+		else
+		{
+			$table = $this->protect_identifiers($table, TRUE, NULL, FALSE);
+		}
+
+		if ($where !== '')
+		{
+			$this->where($where);
+		}
+
+		if ( ! empty($limit))
+		{
+			$this->limit($limit);
+		}
+
+		if (count($this->qb_where) === 0)
+		{
+			return ($this->db_debug) ? $this->display_error('db_del_must_use_where') : FALSE;
+		}
+
+		$sql = $this->_delete($table);
+		if ($reset_data)
+		{
+			$this->_reset_write();
+		}
+
+		return ($this->return_delete_sql === TRUE) ? $sql : $this->query($sql);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Delete statement
+	 *
+	 * Generates a platform-specific delete string from the supplied data
+	 *
+	 * @param	string	the table name
+	 * @return	string
+	 */
+	protected function _delete($table)
+	{
+		return 'DELETE FROM '.$table.$this->_compile_wh('qb_where')
+			.($this->qb_limit ? ' LIMIT '.$this->qb_limit : '');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * DB Prefix
+	 *
+	 * Prepends a database prefix if one exists in configuration
+	 *
+	 * @param	string	the table
+	 * @return	string
+	 */
+	public function dbprefix($table = '')
+	{
+		if ($table === '')
+		{
+			$this->display_error('db_table_name_required');
+		}
+
+		return $this->dbprefix.$table;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Set DB Prefix
+	 *
+	 * Set's the DB Prefix to something new without needing to reconnect
+	 *
+	 * @param	string	the prefix
+	 * @return	string
+	 */
+	public function set_dbprefix($prefix = '')
+	{
+		return $this->dbprefix = $prefix;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Track Aliases
+	 *
+	 * Used to track SQL statements written with aliased tables.
+	 *
+	 * @param	string	The table to inspect
+	 * @return	string
+	 */
+	protected function _track_aliases($table)
+	{
+		if (is_array($table))
+		{
+			foreach ($table as $t)
+			{
+				$this->_track_aliases($t);
+			}
+			return;
+		}
+
+		// Does the string contain a comma?  If so, we need to separate
+		// the string into discreet statements
+		if (strpos($table, ',') !== FALSE)
+		{
+			return $this->_track_aliases(explode(',', $table));
+		}
+
+		// if a table alias is used we can recognize it by a space
+		if (strpos($table, ' ') !== FALSE)
+		{
+			// if the alias is written with the AS keyword, remove it
+			$table = preg_replace('/\s+AS\s+/i', ' ', $table);
+
+			// Grab the alias
+			$table = trim(strrchr($table, ' '));
+
+			// Store the alias, if it doesn't already exist
+			if ( ! in_array($table, $this->qb_aliased_tables, TRUE))
+			{
+				$this->qb_aliased_tables[] = $table;
+				if ($this->qb_caching === TRUE && ! in_array($table, $this->qb_cache_aliased_tables, TRUE))
+				{
+					$this->qb_cache_aliased_tables[] = $table;
+					$this->qb_cache_exists[] = 'aliased_tables';
+				}
+			}
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Compile the SELECT statement
+	 *
+	 * Generates a query string based on which functions were used.
+	 * Should not be called directly.
+	 *
+	 * @param	bool	$select_override
+	 * @return	string
+	 */
+	protected function _compile_select($select_override = FALSE)
+	{
+		// Combine any cached components with the current statements
+		$this->_merge_cache();
+
+		// Write the "select" portion of the query
+		if ($select_override !== FALSE)
+		{
+			$sql = $select_override;
+		}
+		else
+		{
+			$sql = ( ! $this->qb_distinct) ? 'SELECT ' : 'SELECT DISTINCT ';
+
+			if (count($this->qb_select) === 0)
+			{
+				$sql .= '*';
+			}
+			else
+			{
+				// Cycle through the "select" portion of the query and prep each column name.
+				// The reason we protect identifiers here rather than in the select() function
+				// is because until the user calls the from() function we don't know if there are aliases
+				foreach ($this->qb_select as $key => $val)
+				{
+					$no_escape = isset($this->qb_no_escape[$key]) ? $this->qb_no_escape[$key] : NULL;
+					$this->qb_select[$key] = $this->protect_identifiers($val, FALSE, $no_escape);
+				}
+
+				$sql .= implode(', ', $this->qb_select);
+			}
+		}
+
+		// Write the "FROM" portion of the query
+		if (count($this->qb_from) > 0)
+		{
+			$sql .= "\nFROM ".$this->_from_tables();
+		}
+
+		// Write the "JOIN" portion of the query
+		if (count($this->qb_join) > 0)
+		{
+			$sql .= "\n".implode("\n", $this->qb_join);
+		}
+
+		$sql .= $this->_compile_wh('qb_where')
+			.$this->_compile_group_by()
+			.$this->_compile_wh('qb_having')
+			.$this->_compile_order_by(); // ORDER BY
+
+		// LIMIT
+		if ($this->qb_limit OR $this->qb_offset)
+		{
+			return $this->_limit($sql."\n");
+		}
+
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Compile WHERE, HAVING statements
+	 *
+	 * Escapes identifiers in WHERE and HAVING statements at execution time.
+	 *
+	 * Required so that aliases are tracked properly, regardless of whether
+	 * where(), or_where(), having(), or_having are called prior to from(),
+	 * join() and dbprefix is added only if needed.
+	 *
+	 * @param	string	$qb_key	'qb_where' or 'qb_having'
+	 * @return	string	SQL statement
+	 */
+	protected function _compile_wh($qb_key)
+	{
+		if (count($this->$qb_key) > 0)
+		{
+			for ($i = 0, $c = count($this->$qb_key); $i < $c; $i++)
+			{
+				// Is this condition already compiled?
+				if (is_string($this->{$qb_key}[$i]))
+				{
+					continue;
+				}
+				elseif ($this->{$qb_key}[$i]['escape'] === FALSE)
+				{
+					$this->{$qb_key}[$i] = $this->{$qb_key}[$i]['condition'];
+					continue;
+				}
+
+				// Split multiple conditions
+				$conditions = preg_split(
+					'/((?:^|\s+)AND\s+|(?:^|\s+)OR\s+)/i',
+					$this->{$qb_key}[$i]['condition'],
+					-1,
+					PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY
+				);
+
+				for ($ci = 0, $cc = count($conditions); $ci < $cc; $ci++)
+				{
+					if (($op = $this->_get_operator($conditions[$ci])) === FALSE
+						OR ! preg_match('/^(\(?)(.*)('.preg_quote($op, '/').')\s*(.*(?<!\)))?(\)?)$/i', $conditions[$ci], $matches))
+					{
+						continue;
+					}
+
+					// $matches = array(
+					//	0 => '(test <= foo)',	/* the whole thing */
+					//	1 => '(',		/* optional */
+					//	2 => 'test',		/* the field name */
+					//	3 => ' <= ',		/* $op */
+					//	4 => 'foo',		/* optional, if $op is e.g. 'IS NULL' */
+					//	5 => ')'		/* optional */
+					// );
+
+					if ( ! empty($matches[4]))
+					{
+						$this->_is_literal($matches[4]) OR $matches[4] = $this->protect_identifiers(trim($matches[4]));
+						$matches[4] = ' '.$matches[4];
+					}
+
+					$conditions[$ci] = $matches[1].$this->protect_identifiers(trim($matches[2]))
+						.' '.trim($matches[3]).$matches[4].$matches[5];
+				}
+
+				$this->{$qb_key}[$i] = implode('', $conditions);
+			}
+
+			return ($qb_key === 'qb_having' ? "\nHAVING " : "\nWHERE ")
+				.implode("\n", $this->$qb_key);
+		}
+
+		return '';
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Compile GROUP BY
+	 *
+	 * Escapes identifiers in GROUP BY statements at execution time.
+	 *
+	 * Required so that aliases are tracked properly, regardless of whether
+	 * group_by() is called prior to from(), join() and dbprefix is added
+	 * only if needed.
+	 *
+	 * @return	string	SQL statement
+	 */
+	protected function _compile_group_by()
+	{
+		if (count($this->qb_groupby) > 0)
+		{
+			for ($i = 0, $c = count($this->qb_groupby); $i < $c; $i++)
+			{
+				// Is it already compiled?
+				if (is_string($this->qb_groupby[$i]))
+				{
+					continue;
+				}
+
+				$this->qb_groupby[$i] = ($this->qb_groupby[$i]['escape'] === FALSE OR $this->_is_literal($this->qb_groupby[$i]['field']))
+					? $this->qb_groupby[$i]['field']
+					: $this->protect_identifiers($this->qb_groupby[$i]['field']);
+			}
+
+			return "\nGROUP BY ".implode(', ', $this->qb_groupby);
+		}
+
+		return '';
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Compile ORDER BY
+	 *
+	 * Escapes identifiers in ORDER BY statements at execution time.
+	 *
+	 * Required so that aliases are tracked properly, regardless of whether
+	 * order_by() is called prior to from(), join() and dbprefix is added
+	 * only if needed.
+	 *
+	 * @return	string	SQL statement
+	 */
+	protected function _compile_order_by()
+	{
+		if (empty($this->qb_orderby))
+		{
+			return '';
+		}
+
+		for ($i = 0, $c = count($this->qb_orderby); $i < $c; $i++)
+		{
+			if (is_string($this->qb_orderby[$i]))
+			{
+				continue;
+			}
+
+			if ($this->qb_orderby[$i]['escape'] !== FALSE && ! $this->_is_literal($this->qb_orderby[$i]['field']))
+			{
+				$this->qb_orderby[$i]['field'] = $this->protect_identifiers($this->qb_orderby[$i]['field']);
+			}
+
+			$this->qb_orderby[$i] = $this->qb_orderby[$i]['field'].$this->qb_orderby[$i]['direction'];
+		}
+
+		return "\nORDER BY ".implode(', ', $this->qb_orderby);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Object to Array
+	 *
+	 * Takes an object as input and converts the class variables to array key/vals
+	 *
+	 * @param	object
+	 * @return	array
+	 */
+	protected function _object_to_array($object)
+	{
+		if ( ! is_object($object))
+		{
+			return $object;
+		}
+
+		$array = array();
+		foreach (get_object_vars($object) as $key => $val)
+		{
+			// There are some built in keys we need to ignore for this conversion
+			if ( ! is_object($val) && ! is_array($val) && $key !== '_parent_name')
+			{
+				$array[$key] = $val;
+			}
+		}
+
+		return $array;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Object to Array
+	 *
+	 * Takes an object as input and converts the class variables to array key/vals
+	 *
+	 * @param	object
+	 * @return	array
+	 */
+	protected function _object_to_array_batch($object)
+	{
+		if ( ! is_object($object))
+		{
+			return $object;
+		}
+
+		$array = array();
+		$out = get_object_vars($object);
+		$fields = array_keys($out);
+
+		foreach ($fields as $val)
+		{
+			// There are some built in keys we need to ignore for this conversion
+			if ($val !== '_parent_name')
+			{
+				$i = 0;
+				foreach ($out[$val] as $data)
+				{
+					$array[$i++][$val] = $data;
+				}
+			}
+		}
+
+		return $array;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Start Cache
+	 *
+	 * Starts QB caching
+	 *
+	 * @return	CI_DB_query_builder
+	 */
+	public function start_cache()
+	{
+		$this->qb_caching = TRUE;
+		return $this;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Stop Cache
+	 *
+	 * Stops QB caching
+	 *
+	 * @return	CI_DB_query_builder
+	 */
+	public function stop_cache()
+	{
+		$this->qb_caching = FALSE;
+		return $this;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Flush Cache
+	 *
+	 * Empties the QB cache
+	 *
+	 * @return	CI_DB_query_builder
+	 */
+	public function flush_cache()
+	{
+		$this->_reset_run(array(
+			'qb_cache_select'		=> array(),
+			'qb_cache_from'			=> array(),
+			'qb_cache_join'			=> array(),
+			'qb_cache_where'		=> array(),
+			'qb_cache_groupby'		=> array(),
+			'qb_cache_having'		=> array(),
+			'qb_cache_orderby'		=> array(),
+			'qb_cache_set'			=> array(),
+			'qb_cache_exists'		=> array(),
+			'qb_cache_no_escape'	=> array(),
+			'qb_cache_aliased_tables'	=> array()
+		));
+
+		return $this;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Merge Cache
+	 *
+	 * When called, this function merges any cached QB arrays with
+	 * locally called ones.
+	 *
+	 * @return	void
+	 */
+	protected function _merge_cache()
+	{
+		if (count($this->qb_cache_exists) === 0)
+		{
+			return;
+		}
+		elseif (in_array('select', $this->qb_cache_exists, TRUE))
+		{
+			$qb_no_escape = $this->qb_cache_no_escape;
+		}
+
+		foreach (array_unique($this->qb_cache_exists) as $val) // select, from, etc.
+		{
+			$qb_variable	= 'qb_'.$val;
+			$qb_cache_var	= 'qb_cache_'.$val;
+			$qb_new 	= $this->$qb_cache_var;
+
+			for ($i = 0, $c = count($this->$qb_variable); $i < $c; $i++)
+			{
+				if ( ! in_array($this->{$qb_variable}[$i], $qb_new, TRUE))
+				{
+					$qb_new[] = $this->{$qb_variable}[$i];
+					if ($val === 'select')
+					{
+						$qb_no_escape[] = $this->qb_no_escape[$i];
+					}
+				}
+			}
+
+			$this->$qb_variable = $qb_new;
+			if ($val === 'select')
+			{
+				$this->qb_no_escape = $qb_no_escape;
+			}
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Is literal
+	 *
+	 * Determines if a string represents a literal value or a field name
+	 *
+	 * @param	string	$str
+	 * @return	bool
+	 */
+	protected function _is_literal($str)
+	{
+		$str = trim($str);
+
+		if (empty($str) OR ctype_digit($str) OR (string) (float) $str === $str OR in_array(strtoupper($str), array('TRUE', 'FALSE'), TRUE))
+		{
+			return TRUE;
+		}
+
+		static $_str;
+
+		if (empty($_str))
+		{
+			$_str = ($this->_escape_char !== '"')
+				? array('"', "'") : array("'");
+		}
+
+		return in_array($str[0], $_str, TRUE);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Reset Query Builder values.
+	 *
+	 * Publicly-visible method to reset the QB values.
+	 *
+	 * @return	CI_DB_query_builder
+	 */
+	public function reset_query()
+	{
+		$this->_reset_select();
+		$this->_reset_write();
+		return $this;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Resets the query builder values.  Called by the get() function
+	 *
+	 * @param	array	An array of fields to reset
+	 * @return	void
+	 */
+	protected function _reset_run($qb_reset_items)
+	{
+		foreach ($qb_reset_items as $item => $default_value)
+		{
+			$this->$item = $default_value;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Resets the query builder values.  Called by the get() function
+	 *
+	 * @return	void
+	 */
+	protected function _reset_select()
+	{
+		$this->_reset_run(array(
+			'qb_select'		=> array(),
+			'qb_from'		=> array(),
+			'qb_join'		=> array(),
+			'qb_where'		=> array(),
+			'qb_groupby'		=> array(),
+			'qb_having'		=> array(),
+			'qb_orderby'		=> array(),
+			'qb_aliased_tables'	=> array(),
+			'qb_no_escape'		=> array(),
+			'qb_distinct'		=> FALSE,
+			'qb_limit'		=> FALSE,
+			'qb_offset'		=> FALSE
+		));
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Resets the query builder "write" values.
+	 *
+	 * Called by the insert() update() insert_batch() update_batch() and delete() functions
+	 *
+	 * @return	void
+	 */
+	protected function _reset_write()
+	{
+		$this->_reset_run(array(
+			'qb_set'	=> array(),
+			'qb_set_ub'	=> array(),
+			'qb_from'	=> array(),
+			'qb_join'	=> array(),
+			'qb_where'	=> array(),
+			'qb_orderby'	=> array(),
+			'qb_keys'	=> array(),
+			'qb_limit'	=> FALSE
+		));
+	}
+
+}

+ 665 - 0
apps/system/database/DB_result.php

@@ -0,0 +1,665 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 1.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * Database Result Class
+ *
+ * This is the platform-independent result class.
+ * This class will not be called directly. Rather, the adapter
+ * class for the specific database will extend and instantiate it.
+ *
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/database/
+ */
+class CI_DB_result {
+
+	/**
+	 * Connection ID
+	 *
+	 * @var	resource|object
+	 */
+	public $conn_id;
+
+	/**
+	 * Result ID
+	 *
+	 * @var	resource|object
+	 */
+	public $result_id;
+
+	/**
+	 * Result Array
+	 *
+	 * @var	array[]
+	 */
+	public $result_array			= array();
+
+	/**
+	 * Result Object
+	 *
+	 * @var	object[]
+	 */
+	public $result_object			= array();
+
+	/**
+	 * Custom Result Object
+	 *
+	 * @var	object[]
+	 */
+	public $custom_result_object		= array();
+
+	/**
+	 * Current Row index
+	 *
+	 * @var	int
+	 */
+	public $current_row			= 0;
+
+	/**
+	 * Number of rows
+	 *
+	 * @var	int
+	 */
+	public $num_rows;
+
+	/**
+	 * Row data
+	 *
+	 * @var	array
+	 */
+	public $row_data;
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Constructor
+	 *
+	 * @param	object	$driver_object
+	 * @return	void
+	 */
+	public function __construct(&$driver_object)
+	{
+		$this->conn_id = $driver_object->conn_id;
+		$this->result_id = $driver_object->result_id;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Number of rows in the result set
+	 *
+	 * @return	int
+	 */
+	public function num_rows()
+	{
+		if (is_int($this->num_rows))
+		{
+			return $this->num_rows;
+		}
+		elseif (count($this->result_array) > 0)
+		{
+			return $this->num_rows = count($this->result_array);
+		}
+		elseif (count($this->result_object) > 0)
+		{
+			return $this->num_rows = count($this->result_object);
+		}
+
+		return $this->num_rows = count($this->result_array());
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Query result. Acts as a wrapper function for the following functions.
+	 *
+	 * @param	string	$type	'object', 'array' or a custom class name
+	 * @return	array
+	 */
+	public function result($type = 'object')
+	{
+		if ($type === 'array')
+		{
+			return $this->result_array();
+		}
+		elseif ($type === 'object')
+		{
+			return $this->result_object();
+		}
+
+		return $this->custom_result_object($type);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Custom query result.
+	 *
+	 * @param	string	$class_name
+	 * @return	array
+	 */
+	public function custom_result_object($class_name)
+	{
+		if (isset($this->custom_result_object[$class_name]))
+		{
+			return $this->custom_result_object[$class_name];
+		}
+		elseif ( ! $this->result_id OR $this->num_rows === 0)
+		{
+			return array();
+		}
+
+		// Don't fetch the result set again if we already have it
+		$_data = NULL;
+		if (($c = count($this->result_array)) > 0)
+		{
+			$_data = 'result_array';
+		}
+		elseif (($c = count($this->result_object)) > 0)
+		{
+			$_data = 'result_object';
+		}
+
+		if ($_data !== NULL)
+		{
+			for ($i = 0; $i < $c; $i++)
+			{
+				$this->custom_result_object[$class_name][$i] = new $class_name();
+
+				foreach ($this->{$_data}[$i] as $key => $value)
+				{
+					$this->custom_result_object[$class_name][$i]->$key = $value;
+				}
+			}
+
+			return $this->custom_result_object[$class_name];
+		}
+
+		is_null($this->row_data) OR $this->data_seek(0);
+		$this->custom_result_object[$class_name] = array();
+
+		while ($row = $this->_fetch_object($class_name))
+		{
+			$this->custom_result_object[$class_name][] = $row;
+		}
+
+		return $this->custom_result_object[$class_name];
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Query result. "object" version.
+	 *
+	 * @return	array
+	 */
+	public function result_object()
+	{
+		if (count($this->result_object) > 0)
+		{
+			return $this->result_object;
+		}
+
+		// In the event that query caching is on, the result_id variable
+		// will not be a valid resource so we'll simply return an empty
+		// array.
+		if ( ! $this->result_id OR $this->num_rows === 0)
+		{
+			return array();
+		}
+
+		if (($c = count($this->result_array)) > 0)
+		{
+			for ($i = 0; $i < $c; $i++)
+			{
+				$this->result_object[$i] = (object) $this->result_array[$i];
+			}
+
+			return $this->result_object;
+		}
+
+		is_null($this->row_data) OR $this->data_seek(0);
+		while ($row = $this->_fetch_object())
+		{
+			$this->result_object[] = $row;
+		}
+
+		return $this->result_object;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Query result. "array" version.
+	 *
+	 * @return	array
+	 */
+	public function result_array()
+	{
+		if (count($this->result_array) > 0)
+		{
+			return $this->result_array;
+		}
+
+		// In the event that query caching is on, the result_id variable
+		// will not be a valid resource so we'll simply return an empty
+		// array.
+		if ( ! $this->result_id OR $this->num_rows === 0)
+		{
+			return array();
+		}
+
+		if (($c = count($this->result_object)) > 0)
+		{
+			for ($i = 0; $i < $c; $i++)
+			{
+				$this->result_array[$i] = (array) $this->result_object[$i];
+			}
+
+			return $this->result_array;
+		}
+
+		is_null($this->row_data) OR $this->data_seek(0);
+		while ($row = $this->_fetch_assoc())
+		{
+			$this->result_array[] = $row;
+		}
+
+		return $this->result_array;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Row
+	 *
+	 * A wrapper method.
+	 *
+	 * @param	mixed	$n
+	 * @param	string	$type	'object' or 'array'
+	 * @return	mixed
+	 */
+	public function row($n = 0, $type = 'object')
+	{
+		if ( ! is_numeric($n))
+		{
+			// We cache the row data for subsequent uses
+			is_array($this->row_data) OR $this->row_data = $this->row_array(0);
+
+			// array_key_exists() instead of isset() to allow for NULL values
+			if (empty($this->row_data) OR ! array_key_exists($n, $this->row_data))
+			{
+				return NULL;
+			}
+
+			return $this->row_data[$n];
+		}
+
+		if ($type === 'object') return $this->row_object($n);
+		elseif ($type === 'array') return $this->row_array($n);
+
+		return $this->custom_row_object($n, $type);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Assigns an item into a particular column slot
+	 *
+	 * @param	mixed	$key
+	 * @param	mixed	$value
+	 * @return	void
+	 */
+	public function set_row($key, $value = NULL)
+	{
+		// We cache the row data for subsequent uses
+		if ( ! is_array($this->row_data))
+		{
+			$this->row_data = $this->row_array(0);
+		}
+
+		if (is_array($key))
+		{
+			foreach ($key as $k => $v)
+			{
+				$this->row_data[$k] = $v;
+			}
+			return;
+		}
+
+		if ($key !== '' && $value !== NULL)
+		{
+			$this->row_data[$key] = $value;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Returns a single result row - custom object version
+	 *
+	 * @param	int	$n
+	 * @param	string	$type
+	 * @return	object
+	 */
+	public function custom_row_object($n, $type)
+	{
+		isset($this->custom_result_object[$type]) OR $this->custom_result_object($type);
+
+		if (count($this->custom_result_object[$type]) === 0)
+		{
+			return NULL;
+		}
+
+		if ($n !== $this->current_row && isset($this->custom_result_object[$type][$n]))
+		{
+			$this->current_row = $n;
+		}
+
+		return $this->custom_result_object[$type][$this->current_row];
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Returns a single result row - object version
+	 *
+	 * @param	int	$n
+	 * @return	object
+	 */
+	public function row_object($n = 0)
+	{
+		$result = $this->result_object();
+		if (count($result) === 0)
+		{
+			return NULL;
+		}
+
+		if ($n !== $this->current_row && isset($result[$n]))
+		{
+			$this->current_row = $n;
+		}
+
+		return $result[$this->current_row];
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Returns a single result row - array version
+	 *
+	 * @param	int	$n
+	 * @return	array
+	 */
+	public function row_array($n = 0)
+	{
+		$result = $this->result_array();
+		if (count($result) === 0)
+		{
+			return NULL;
+		}
+
+		if ($n !== $this->current_row && isset($result[$n]))
+		{
+			$this->current_row = $n;
+		}
+
+		return $result[$this->current_row];
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Returns the "first" row
+	 *
+	 * @param	string	$type
+	 * @return	mixed
+	 */
+	public function first_row($type = 'object')
+	{
+		$result = $this->result($type);
+		return (count($result) === 0) ? NULL : $result[0];
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Returns the "last" row
+	 *
+	 * @param	string	$type
+	 * @return	mixed
+	 */
+	public function last_row($type = 'object')
+	{
+		$result = $this->result($type);
+		return (count($result) === 0) ? NULL : $result[count($result) - 1];
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Returns the "next" row
+	 *
+	 * @param	string	$type
+	 * @return	mixed
+	 */
+	public function next_row($type = 'object')
+	{
+		$result = $this->result($type);
+		if (count($result) === 0)
+		{
+			return NULL;
+		}
+
+		return isset($result[$this->current_row + 1])
+			? $result[++$this->current_row]
+			: NULL;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Returns the "previous" row
+	 *
+	 * @param	string	$type
+	 * @return	mixed
+	 */
+	public function previous_row($type = 'object')
+	{
+		$result = $this->result($type);
+		if (count($result) === 0)
+		{
+			return NULL;
+		}
+
+		if (isset($result[$this->current_row - 1]))
+		{
+			--$this->current_row;
+		}
+		return $result[$this->current_row];
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Returns an unbuffered row and move pointer to next row
+	 *
+	 * @param	string	$type	'array', 'object' or a custom class name
+	 * @return	mixed
+	 */
+	public function unbuffered_row($type = 'object')
+	{
+		if ($type === 'array')
+		{
+			return $this->_fetch_assoc();
+		}
+		elseif ($type === 'object')
+		{
+			return $this->_fetch_object();
+		}
+
+		return $this->_fetch_object($type);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * The following methods are normally overloaded by the identically named
+	 * methods in the platform-specific driver -- except when query caching
+	 * is used. When caching is enabled we do not load the other driver.
+	 * These functions are primarily here to prevent undefined function errors
+	 * when a cached result object is in use. They are not otherwise fully
+	 * operational due to the unavailability of the database resource IDs with
+	 * cached results.
+	 */
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Number of fields in the result set
+	 *
+	 * Overridden by driver result classes.
+	 *
+	 * @return	int
+	 */
+	public function num_fields()
+	{
+		return 0;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Fetch Field Names
+	 *
+	 * Generates an array of column names.
+	 *
+	 * Overridden by driver result classes.
+	 *
+	 * @return	array
+	 */
+	public function list_fields()
+	{
+		return array();
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field data
+	 *
+	 * Generates an array of objects containing field meta-data.
+	 *
+	 * Overridden by driver result classes.
+	 *
+	 * @return	array
+	 */
+	public function field_data()
+	{
+		return array();
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Free the result
+	 *
+	 * Overridden by driver result classes.
+	 *
+	 * @return	void
+	 */
+	public function free_result()
+	{
+		$this->result_id = FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Data Seek
+	 *
+	 * Moves the internal pointer to the desired offset. We call
+	 * this internally before fetching results to make sure the
+	 * result set starts at zero.
+	 *
+	 * Overridden by driver result classes.
+	 *
+	 * @param	int	$n
+	 * @return	bool
+	 */
+	public function data_seek($n = 0)
+	{
+		return FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Result - associative array
+	 *
+	 * Returns the result set as an array.
+	 *
+	 * Overridden by driver result classes.
+	 *
+	 * @return	array
+	 */
+	protected function _fetch_assoc()
+	{
+		return array();
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Result - object
+	 *
+	 * Returns the result set as an object.
+	 *
+	 * Overridden by driver result classes.
+	 *
+	 * @param	string	$class_name
+	 * @return	object
+	 */
+	protected function _fetch_object($class_name = 'stdClass')
+	{
+		return new $class_name();
+	}
+
+}

+ 424 - 0
apps/system/database/DB_utility.php

@@ -0,0 +1,424 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 1.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * Database Utility Class
+ *
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/database/
+ */
+abstract class CI_DB_utility {
+
+	/**
+	 * Database object
+	 *
+	 * @var	object
+	 */
+	protected $db;
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * List databases statement
+	 *
+	 * @var	string
+	 */
+	protected $_list_databases		= FALSE;
+
+	/**
+	 * OPTIMIZE TABLE statement
+	 *
+	 * @var	string
+	 */
+	protected $_optimize_table	= FALSE;
+
+	/**
+	 * REPAIR TABLE statement
+	 *
+	 * @var	string
+	 */
+	protected $_repair_table	= FALSE;
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * @param	object	&$db	Database object
+	 * @return	void
+	 */
+	public function __construct(&$db)
+	{
+		$this->db =& $db;
+		log_message('info', 'Database Utility Class Initialized');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * List databases
+	 *
+	 * @return	array
+	 */
+	public function list_databases()
+	{
+		// Is there a cached result?
+		if (isset($this->db->data_cache['db_names']))
+		{
+			return $this->db->data_cache['db_names'];
+		}
+		elseif ($this->_list_databases === FALSE)
+		{
+			return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
+		}
+
+		$this->db->data_cache['db_names'] = array();
+
+		$query = $this->db->query($this->_list_databases);
+		if ($query === FALSE)
+		{
+			return $this->db->data_cache['db_names'];
+		}
+
+		for ($i = 0, $query = $query->result_array(), $c = count($query); $i < $c; $i++)
+		{
+			$this->db->data_cache['db_names'][] = current($query[$i]);
+		}
+
+		return $this->db->data_cache['db_names'];
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Determine if a particular database exists
+	 *
+	 * @param	string	$database_name
+	 * @return	bool
+	 */
+	public function database_exists($database_name)
+	{
+		return in_array($database_name, $this->list_databases());
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Optimize Table
+	 *
+	 * @param	string	$table_name
+	 * @return	mixed
+	 */
+	public function optimize_table($table_name)
+	{
+		if ($this->_optimize_table === FALSE)
+		{
+			return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
+		}
+
+		$query = $this->db->query(sprintf($this->_optimize_table, $this->db->escape_identifiers($table_name)));
+		if ($query !== FALSE)
+		{
+			$query = $query->result_array();
+			return current($query);
+		}
+
+		return FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Optimize Database
+	 *
+	 * @return	mixed
+	 */
+	public function optimize_database()
+	{
+		if ($this->_optimize_table === FALSE)
+		{
+			return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
+		}
+
+		$result = array();
+		foreach ($this->db->list_tables() as $table_name)
+		{
+			$res = $this->db->query(sprintf($this->_optimize_table, $this->db->escape_identifiers($table_name)));
+			if (is_bool($res))
+			{
+				return $res;
+			}
+
+			// Build the result array...
+			$res = $res->result_array();
+			$res = current($res);
+			$key = str_replace($this->db->database.'.', '', current($res));
+			$keys = array_keys($res);
+			unset($res[$keys[0]]);
+
+			$result[$key] = $res;
+		}
+
+		return $result;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Repair Table
+	 *
+	 * @param	string	$table_name
+	 * @return	mixed
+	 */
+	public function repair_table($table_name)
+	{
+		if ($this->_repair_table === FALSE)
+		{
+			return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
+		}
+
+		$query = $this->db->query(sprintf($this->_repair_table, $this->db->escape_identifiers($table_name)));
+		if (is_bool($query))
+		{
+			return $query;
+		}
+
+		$query = $query->result_array();
+		return current($query);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Generate CSV from a query result object
+	 *
+	 * @param	object	$query		Query result object
+	 * @param	string	$delim		Delimiter (default: ,)
+	 * @param	string	$newline	Newline character (default: \n)
+	 * @param	string	$enclosure	Enclosure (default: ")
+	 * @return	string
+	 */
+	public function csv_from_result($query, $delim = ',', $newline = "\n", $enclosure = '"')
+	{
+		if ( ! is_object($query) OR ! method_exists($query, 'list_fields'))
+		{
+			show_error('You must submit a valid result object');
+		}
+
+		$out = '';
+		// First generate the headings from the table column names
+		foreach ($query->list_fields() as $name)
+		{
+			$out .= $enclosure.str_replace($enclosure, $enclosure.$enclosure, $name).$enclosure.$delim;
+		}
+
+		$out = substr($out, 0, -strlen($delim)).$newline;
+
+		// Next blast through the result array and build out the rows
+		while ($row = $query->unbuffered_row('array'))
+		{
+			$line = array();
+			foreach ($row as $item)
+			{
+				$line[] = $enclosure.str_replace($enclosure, $enclosure.$enclosure, $item).$enclosure;
+			}
+			$out .= implode($delim, $line).$newline;
+		}
+
+		return $out;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Generate XML data from a query result object
+	 *
+	 * @param	object	$query	Query result object
+	 * @param	array	$params	Any preferences
+	 * @return	string
+	 */
+	public function xml_from_result($query, $params = array())
+	{
+		if ( ! is_object($query) OR ! method_exists($query, 'list_fields'))
+		{
+			show_error('You must submit a valid result object');
+		}
+
+		// Set our default values
+		foreach (array('root' => 'root', 'element' => 'element', 'newline' => "\n", 'tab' => "\t") as $key => $val)
+		{
+			if ( ! isset($params[$key]))
+			{
+				$params[$key] = $val;
+			}
+		}
+
+		// Create variables for convenience
+		extract($params);
+
+		// Load the xml helper
+		get_instance()->load->helper('xml');
+
+		// Generate the result
+		$xml = '<'.$root.'>'.$newline;
+		while ($row = $query->unbuffered_row())
+		{
+			$xml .= $tab.'<'.$element.'>'.$newline;
+			foreach ($row as $key => $val)
+			{
+				$xml .= $tab.$tab.'<'.$key.'>'.xml_convert($val).'</'.$key.'>'.$newline;
+			}
+			$xml .= $tab.'</'.$element.'>'.$newline;
+		}
+
+		return $xml.'</'.$root.'>'.$newline;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Database Backup
+	 *
+	 * @param	array	$params
+	 * @return	string
+	 */
+	public function backup($params = array())
+	{
+		// If the parameters have not been submitted as an
+		// array then we know that it is simply the table
+		// name, which is a valid short cut.
+		if (is_string($params))
+		{
+			$params = array('tables' => $params);
+		}
+
+		// Set up our default preferences
+		$prefs = array(
+			'tables'		=> array(),
+			'ignore'		=> array(),
+			'filename'		=> '',
+			'format'		=> 'gzip', // gzip, zip, txt
+			'add_drop'		=> TRUE,
+			'add_insert'		=> TRUE,
+			'newline'		=> "\n",
+			'foreign_key_checks'	=> TRUE
+		);
+
+		// Did the user submit any preferences? If so set them....
+		if (count($params) > 0)
+		{
+			foreach ($prefs as $key => $val)
+			{
+				if (isset($params[$key]))
+				{
+					$prefs[$key] = $params[$key];
+				}
+			}
+		}
+
+		// Are we backing up a complete database or individual tables?
+		// If no table names were submitted we'll fetch the entire table list
+		if (count($prefs['tables']) === 0)
+		{
+			$prefs['tables'] = $this->db->list_tables();
+		}
+
+		// Validate the format
+		if ( ! in_array($prefs['format'], array('gzip', 'zip', 'txt'), TRUE))
+		{
+			$prefs['format'] = 'txt';
+		}
+
+		// Is the encoder supported? If not, we'll either issue an
+		// error or use plain text depending on the debug settings
+		if (($prefs['format'] === 'gzip' && ! function_exists('gzencode'))
+			OR ($prefs['format'] === 'zip' && ! function_exists('gzcompress')))
+		{
+			if ($this->db->db_debug)
+			{
+				return $this->db->display_error('db_unsupported_compression');
+			}
+
+			$prefs['format'] = 'txt';
+		}
+
+		// Was a Zip file requested?
+		if ($prefs['format'] === 'zip')
+		{
+			// Set the filename if not provided (only needed with Zip files)
+			if ($prefs['filename'] === '')
+			{
+				$prefs['filename'] = (count($prefs['tables']) === 1 ? $prefs['tables'] : $this->db->database)
+							.date('Y-m-d_H-i', time()).'.sql';
+			}
+			else
+			{
+				// If they included the .zip file extension we'll remove it
+				if (preg_match('|.+?\.zip$|', $prefs['filename']))
+				{
+					$prefs['filename'] = str_replace('.zip', '', $prefs['filename']);
+				}
+
+				// Tack on the ".sql" file extension if needed
+				if ( ! preg_match('|.+?\.sql$|', $prefs['filename']))
+				{
+					$prefs['filename'] .= '.sql';
+				}
+			}
+
+			// Load the Zip class and output it
+			$CI =& get_instance();
+			$CI->load->library('zip');
+			$CI->zip->add_data($prefs['filename'], $this->_backup($prefs));
+			return $CI->zip->get_zip();
+		}
+		elseif ($prefs['format'] === 'txt') // Was a text file requested?
+		{
+			return $this->_backup($prefs);
+		}
+		elseif ($prefs['format'] === 'gzip') // Was a Gzip file requested?
+		{
+			return gzencode($this->_backup($prefs));
+		}
+
+		return;
+	}
+
+}

+ 405 - 0
apps/system/database/drivers/cubrid/cubrid_driver.php

@@ -0,0 +1,405 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 2.1.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * CUBRID Database Adapter Class
+ *
+ * Note: _DB is an extender class that the app controller
+ * creates dynamically based on whether the query builder
+ * class is being used or not.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Drivers
+ * @category	Database
+ * @author		Esen Sagynov
+ * @link		https://codeigniter.com/user_guide/database/
+ */
+class CI_DB_cubrid_driver extends CI_DB {
+
+	/**
+	 * Database driver
+	 *
+	 * @var	string
+	 */
+	public $dbdriver = 'cubrid';
+
+	/**
+	 * Auto-commit flag
+	 *
+	 * @var	bool
+	 */
+	public $auto_commit = TRUE;
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Identifier escape character
+	 *
+	 * @var	string
+	 */
+	protected $_escape_char = '`';
+
+	/**
+	 * ORDER BY random keyword
+	 *
+	 * @var	array
+	 */
+	protected $_random_keyword = array('RANDOM()', 'RANDOM(%d)');
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * @param	array	$params
+	 * @return	void
+	 */
+	public function __construct($params)
+	{
+		parent::__construct($params);
+
+		if (preg_match('/^CUBRID:[^:]+(:[0-9][1-9]{0,4})?:[^:]+:[^:]*:[^:]*:(\?.+)?$/', $this->dsn, $matches))
+		{
+			if (stripos($matches[2], 'autocommit=off') !== FALSE)
+			{
+				$this->auto_commit = FALSE;
+			}
+		}
+		else
+		{
+			// If no port is defined by the user, use the default value
+			empty($this->port) OR $this->port = 33000;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Non-persistent database connection
+	 *
+	 * @param	bool	$persistent
+	 * @return	resource
+	 */
+	public function db_connect($persistent = FALSE)
+	{
+		if (preg_match('/^CUBRID:[^:]+(:[0-9][1-9]{0,4})?:[^:]+:([^:]*):([^:]*):(\?.+)?$/', $this->dsn, $matches))
+		{
+			$func = ($persistent !== TRUE) ? 'cubrid_connect_with_url' : 'cubrid_pconnect_with_url';
+			return ($matches[2] === '' && $matches[3] === '' && $this->username !== '' && $this->password !== '')
+				? $func($this->dsn, $this->username, $this->password)
+				: $func($this->dsn);
+		}
+
+		$func = ($persistent !== TRUE) ? 'cubrid_connect' : 'cubrid_pconnect';
+		return ($this->username !== '')
+			? $func($this->hostname, $this->port, $this->database, $this->username, $this->password)
+			: $func($this->hostname, $this->port, $this->database);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Reconnect
+	 *
+	 * Keep / reestablish the db connection if no queries have been
+	 * sent for a length of time exceeding the server's idle timeout
+	 *
+	 * @return	void
+	 */
+	public function reconnect()
+	{
+		if (cubrid_ping($this->conn_id) === FALSE)
+		{
+			$this->conn_id = FALSE;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Database version number
+	 *
+	 * @return	string
+	 */
+	public function version()
+	{
+		if (isset($this->data_cache['version']))
+		{
+			return $this->data_cache['version'];
+		}
+
+		return ( ! $this->conn_id OR ($version = cubrid_get_server_info($this->conn_id)) === FALSE)
+			? FALSE
+			: $this->data_cache['version'] = $version;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Execute the query
+	 *
+	 * @param	string	$sql	an SQL query
+	 * @return	resource
+	 */
+	protected function _execute($sql)
+	{
+		return cubrid_query($sql, $this->conn_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Begin Transaction
+	 *
+	 * @return	bool
+	 */
+	protected function _trans_begin()
+	{
+		if (($autocommit = cubrid_get_autocommit($this->conn_id)) === NULL)
+		{
+			return FALSE;
+		}
+		elseif ($autocommit === TRUE)
+		{
+			return cubrid_set_autocommit($this->conn_id, CUBRID_AUTOCOMMIT_FALSE);
+		}
+
+		return TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Commit Transaction
+	 *
+	 * @return	bool
+	 */
+	protected function _trans_commit()
+	{
+		if ( ! cubrid_commit($this->conn_id))
+		{
+			return FALSE;
+		}
+
+		if ($this->auto_commit && ! cubrid_get_autocommit($this->conn_id))
+		{
+			return cubrid_set_autocommit($this->conn_id, CUBRID_AUTOCOMMIT_TRUE);
+		}
+
+		return TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Rollback Transaction
+	 *
+	 * @return	bool
+	 */
+	protected function _trans_rollback()
+	{
+		if ( ! cubrid_rollback($this->conn_id))
+		{
+			return FALSE;
+		}
+
+		if ($this->auto_commit && ! cubrid_get_autocommit($this->conn_id))
+		{
+			cubrid_set_autocommit($this->conn_id, CUBRID_AUTOCOMMIT_TRUE);
+		}
+
+		return TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Platform-dependent string escape
+	 *
+	 * @param	string
+	 * @return	string
+	 */
+	protected function _escape_str($str)
+	{
+		return cubrid_real_escape_string($str, $this->conn_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Affected Rows
+	 *
+	 * @return	int
+	 */
+	public function affected_rows()
+	{
+		return cubrid_affected_rows();
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Insert ID
+	 *
+	 * @return	int
+	 */
+	public function insert_id()
+	{
+		return cubrid_insert_id($this->conn_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * List table query
+	 *
+	 * Generates a platform-specific query string so that the table names can be fetched
+	 *
+	 * @param	bool	$prefix_limit
+	 * @return	string
+	 */
+	protected function _list_tables($prefix_limit = FALSE)
+	{
+		$sql = 'SHOW TABLES';
+
+		if ($prefix_limit !== FALSE && $this->dbprefix !== '')
+		{
+			return $sql." LIKE '".$this->escape_like_str($this->dbprefix)."%'";
+		}
+
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Show column query
+	 *
+	 * Generates a platform-specific query string so that the column names can be fetched
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _list_columns($table = '')
+	{
+		return 'SHOW COLUMNS FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Returns an object with field data
+	 *
+	 * @param	string	$table
+	 * @return	array
+	 */
+	public function field_data($table)
+	{
+		if (($query = $this->query('SHOW COLUMNS FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE))) === FALSE)
+		{
+			return FALSE;
+		}
+		$query = $query->result_object();
+
+		$retval = array();
+		for ($i = 0, $c = count($query); $i < $c; $i++)
+		{
+			$retval[$i]			= new stdClass();
+			$retval[$i]->name		= $query[$i]->Field;
+
+			sscanf($query[$i]->Type, '%[a-z](%d)',
+				$retval[$i]->type,
+				$retval[$i]->max_length
+			);
+
+			$retval[$i]->default		= $query[$i]->Default;
+			$retval[$i]->primary_key	= (int) ($query[$i]->Key === 'PRI');
+		}
+
+		return $retval;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Error
+	 *
+	 * Returns an array containing code and message of the last
+	 * database error that has occurred.
+	 *
+	 * @return	array
+	 */
+	public function error()
+	{
+		return array('code' => cubrid_errno($this->conn_id), 'message' => cubrid_error($this->conn_id));
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * FROM tables
+	 *
+	 * Groups tables in FROM clauses if needed, so there is no confusion
+	 * about operator precedence.
+	 *
+	 * @return	string
+	 */
+	protected function _from_tables()
+	{
+		if ( ! empty($this->qb_join) && count($this->qb_from) > 1)
+		{
+			return '('.implode(', ', $this->qb_from).')';
+		}
+
+		return implode(', ', $this->qb_from);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Close DB Connection
+	 *
+	 * @return	void
+	 */
+	protected function _close()
+	{
+		cubrid_close($this->conn_id);
+	}
+
+}

+ 230 - 0
apps/system/database/drivers/cubrid/cubrid_forge.php

@@ -0,0 +1,230 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 2.1.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * CUBRID Forge Class
+ *
+ * @category	Database
+ * @author		Esen Sagynov
+ * @link		https://codeigniter.com/user_guide/database/
+ */
+class CI_DB_cubrid_forge extends CI_DB_forge {
+
+	/**
+	 * CREATE DATABASE statement
+	 *
+	 * @var	string
+	 */
+	protected $_create_database	= FALSE;
+
+	/**
+	 * CREATE TABLE keys flag
+	 *
+	 * Whether table keys are created from within the
+	 * CREATE TABLE statement.
+	 *
+	 * @var	bool
+	 */
+	protected $_create_table_keys	= TRUE;
+
+	/**
+	 * DROP DATABASE statement
+	 *
+	 * @var	string
+	 */
+	protected $_drop_database	= FALSE;
+
+	/**
+	 * CREATE TABLE IF statement
+	 *
+	 * @var	string
+	 */
+	protected $_create_table_if	= FALSE;
+
+	/**
+	 * UNSIGNED support
+	 *
+	 * @var	array
+	 */
+	protected $_unsigned		= array(
+		'SHORT'		=> 'INTEGER',
+		'SMALLINT'	=> 'INTEGER',
+		'INT'		=> 'BIGINT',
+		'INTEGER'	=> 'BIGINT',
+		'BIGINT'	=> 'NUMERIC',
+		'FLOAT'		=> 'DOUBLE',
+		'REAL'		=> 'DOUBLE'
+	);
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ALTER TABLE
+	 *
+	 * @param	string	$alter_type	ALTER type
+	 * @param	string	$table		Table name
+	 * @param	mixed	$field		Column definition
+	 * @return	string|string[]
+	 */
+	protected function _alter_table($alter_type, $table, $field)
+	{
+		if (in_array($alter_type, array('DROP', 'ADD'), TRUE))
+		{
+			return parent::_alter_table($alter_type, $table, $field);
+		}
+
+		$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table);
+		$sqls = array();
+		for ($i = 0, $c = count($field); $i < $c; $i++)
+		{
+			if ($field[$i]['_literal'] !== FALSE)
+			{
+				$sqls[] = $sql.' CHANGE '.$field[$i]['_literal'];
+			}
+			else
+			{
+				$alter_type = empty($field[$i]['new_name']) ? ' MODIFY ' : ' CHANGE ';
+				$sqls[] = $sql.$alter_type.$this->_process_column($field[$i]);
+			}
+		}
+
+		return $sqls;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Process column
+	 *
+	 * @param	array	$field
+	 * @return	string
+	 */
+	protected function _process_column($field)
+	{
+		$extra_clause = isset($field['after'])
+			? ' AFTER '.$this->db->escape_identifiers($field['after']) : '';
+
+		if (empty($extra_clause) && isset($field['first']) && $field['first'] === TRUE)
+		{
+			$extra_clause = ' FIRST';
+		}
+
+		return $this->db->escape_identifiers($field['name'])
+			.(empty($field['new_name']) ? '' : ' '.$this->db->escape_identifiers($field['new_name']))
+			.' '.$field['type'].$field['length']
+			.$field['unsigned']
+			.$field['null']
+			.$field['default']
+			.$field['auto_increment']
+			.$field['unique']
+			.$extra_clause;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute TYPE
+	 *
+	 * Performs a data type mapping between different databases.
+	 *
+	 * @param	array	&$attributes
+	 * @return	void
+	 */
+	protected function _attr_type(&$attributes)
+	{
+		switch (strtoupper($attributes['TYPE']))
+		{
+			case 'TINYINT':
+				$attributes['TYPE'] = 'SMALLINT';
+				$attributes['UNSIGNED'] = FALSE;
+				return;
+			case 'MEDIUMINT':
+				$attributes['TYPE'] = 'INTEGER';
+				$attributes['UNSIGNED'] = FALSE;
+				return;
+			case 'LONGTEXT':
+				$attributes['TYPE'] = 'STRING';
+				return;
+			default: return;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Process indexes
+	 *
+	 * @param	string	$table	(ignored)
+	 * @return	string
+	 */
+	protected function _process_indexes($table)
+	{
+		$sql = '';
+
+		for ($i = 0, $c = count($this->keys); $i < $c; $i++)
+		{
+			if (is_array($this->keys[$i]))
+			{
+				for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++)
+				{
+					if ( ! isset($this->fields[$this->keys[$i][$i2]]))
+					{
+						unset($this->keys[$i][$i2]);
+						continue;
+					}
+				}
+			}
+			elseif ( ! isset($this->fields[$this->keys[$i]]))
+			{
+				unset($this->keys[$i]);
+				continue;
+			}
+
+			is_array($this->keys[$i]) OR $this->keys[$i] = array($this->keys[$i]);
+
+			$sql .= ",\n\tKEY ".$this->db->escape_identifiers(implode('_', $this->keys[$i]))
+				.' ('.implode(', ', $this->db->escape_identifiers($this->keys[$i])).')';
+		}
+
+		$this->keys = array();
+
+		return $sql;
+	}
+
+}

+ 177 - 0
apps/system/database/drivers/cubrid/cubrid_result.php

@@ -0,0 +1,177 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 2.1.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * CUBRID Result Class
+ *
+ * This class extends the parent result class: CI_DB_result
+ *
+ * @category	Database
+ * @author		Esen Sagynov
+ * @link		https://codeigniter.com/user_guide/database/
+ */
+class CI_DB_cubrid_result extends CI_DB_result {
+
+	/**
+	 * Number of rows in the result set
+	 *
+	 * @return	int
+	 */
+	public function num_rows()
+	{
+		return is_int($this->num_rows)
+			? $this->num_rows
+			: $this->num_rows = cubrid_num_rows($this->result_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Number of fields in the result set
+	 *
+	 * @return	int
+	 */
+	public function num_fields()
+	{
+		return cubrid_num_fields($this->result_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Fetch Field Names
+	 *
+	 * Generates an array of column names
+	 *
+	 * @return	array
+	 */
+	public function list_fields()
+	{
+		return cubrid_column_names($this->result_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field data
+	 *
+	 * Generates an array of objects containing field meta-data
+	 *
+	 * @return	array
+	 */
+	public function field_data()
+	{
+		$retval = array();
+
+		for ($i = 0, $c = $this->num_fields(); $i < $c; $i++)
+		{
+			$retval[$i]			= new stdClass();
+			$retval[$i]->name		= cubrid_field_name($this->result_id, $i);
+			$retval[$i]->type		= cubrid_field_type($this->result_id, $i);
+			$retval[$i]->max_length		= cubrid_field_len($this->result_id, $i);
+			$retval[$i]->primary_key	= (int) (strpos(cubrid_field_flags($this->result_id, $i), 'primary_key') !== FALSE);
+		}
+
+		return $retval;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Free the result
+	 *
+	 * @return	void
+	 */
+	public function free_result()
+	{
+		if (is_resource($this->result_id) OR
+			(get_resource_type($this->result_id) === 'Unknown' && preg_match('/Resource id #/', strval($this->result_id))))
+		{
+			cubrid_close_request($this->result_id);
+			$this->result_id = FALSE;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Data Seek
+	 *
+	 * Moves the internal pointer to the desired offset. We call
+	 * this internally before fetching results to make sure the
+	 * result set starts at zero.
+	 *
+	 * @param	int	$n
+	 * @return	bool
+	 */
+	public function data_seek($n = 0)
+	{
+		return cubrid_data_seek($this->result_id, $n);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Result - associative array
+	 *
+	 * Returns the result set as an array
+	 *
+	 * @return	array
+	 */
+	protected function _fetch_assoc()
+	{
+		return cubrid_fetch_assoc($this->result_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Result - object
+	 *
+	 * Returns the result set as an object
+	 *
+	 * @param	string	$class_name
+	 * @return	object
+	 */
+	protected function _fetch_object($class_name = 'stdClass')
+	{
+		return cubrid_fetch_object($this->result_id, $class_name);
+	}
+
+}

+ 79 - 0
apps/system/database/drivers/cubrid/cubrid_utility.php

@@ -0,0 +1,79 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 2.1.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * CUBRID Utility Class
+ *
+ * @category	Database
+ * @author		Esen Sagynov
+ * @link		https://codeigniter.com/user_guide/database/
+ */
+class CI_DB_cubrid_utility extends CI_DB_utility {
+
+	/**
+	 * List databases
+	 *
+	 * @return	array
+	 */
+	public function list_databases()
+	{
+		if (isset($this->db->data_cache['db_names']))
+		{
+			return $this->db->data_cache['db_names'];
+		}
+
+		return $this->db->data_cache['db_names'] = cubrid_list_dbs($this->db->conn_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * CUBRID Export
+	 *
+	 * @param	array	Preferences
+	 * @return	mixed
+	 */
+	protected function _backup($params = array())
+	{
+		// No SQL based support in CUBRID as of version 8.4.0. Database or
+		// table backup can be performed using CUBRID Manager
+		// database administration tool.
+		return $this->db->display_error('db_unsupported_feature');
+	}
+}

+ 11 - 0
apps/system/database/drivers/cubrid/index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>403 Forbidden</title>
+</head>
+<body>
+
+<p>Directory access is forbidden.</p>
+
+</body>
+</html>

+ 413 - 0
apps/system/database/drivers/ibase/ibase_driver.php

@@ -0,0 +1,413 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 3.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * Firebird/Interbase Database Adapter Class
+ *
+ * Note: _DB is an extender class that the app controller
+ * creates dynamically based on whether the query builder
+ * class is being used or not.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Drivers
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/database/
+ */
+class CI_DB_ibase_driver extends CI_DB {
+
+	/**
+	 * Database driver
+	 *
+	 * @var	string
+	 */
+	public $dbdriver = 'ibase';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ORDER BY random keyword
+	 *
+	 * @var	array
+	 */
+	protected $_random_keyword = array('RAND()', 'RAND()');
+
+	/**
+	 * IBase Transaction status flag
+	 *
+	 * @var	resource
+	 */
+	protected $_ibase_trans;
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Non-persistent database connection
+	 *
+	 * @param	bool	$persistent
+	 * @return	resource
+	 */
+	public function db_connect($persistent = FALSE)
+	{
+		return ($persistent === TRUE)
+			? ibase_pconnect($this->hostname.':'.$this->database, $this->username, $this->password, $this->char_set)
+			: ibase_connect($this->hostname.':'.$this->database, $this->username, $this->password, $this->char_set);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Database version number
+	 *
+	 * @return	string
+	 */
+	public function version()
+	{
+		if (isset($this->data_cache['version']))
+		{
+			return $this->data_cache['version'];
+		}
+
+		if (($service = ibase_service_attach($this->hostname, $this->username, $this->password)))
+		{
+			$this->data_cache['version'] = ibase_server_info($service, IBASE_SVC_SERVER_VERSION);
+
+			// Don't keep the service open
+			ibase_service_detach($service);
+			return $this->data_cache['version'];
+		}
+
+		return FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Execute the query
+	 *
+	 * @param	string	$sql	an SQL query
+	 * @return	resource
+	 */
+	protected function _execute($sql)
+	{
+		return ibase_query(isset($this->_ibase_trans) ? $this->_ibase_trans : $this->conn_id, $sql);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Begin Transaction
+	 *
+	 * @return	bool
+	 */
+	protected function _trans_begin()
+	{
+		if (($trans_handle = ibase_trans($this->conn_id)) === FALSE)
+		{
+			return FALSE;
+		}
+
+		$this->_ibase_trans = $trans_handle;
+		return TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Commit Transaction
+	 *
+	 * @return	bool
+	 */
+	protected function _trans_commit()
+	{
+		if (ibase_commit($this->_ibase_trans))
+		{
+			$this->_ibase_trans = NULL;
+			return TRUE;
+		}
+
+		return FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Rollback Transaction
+	 *
+	 * @return	bool
+	 */
+	protected function _trans_rollback()
+	{
+		if (ibase_rollback($this->_ibase_trans))
+		{
+			$this->_ibase_trans = NULL;
+			return TRUE;
+		}
+
+		return FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Affected Rows
+	 *
+	 * @return	int
+	 */
+	public function affected_rows()
+	{
+		return ibase_affected_rows($this->conn_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Insert ID
+	 *
+	 * @param	string	$generator_name
+	 * @param	int	$inc_by
+	 * @return	int
+	 */
+	public function insert_id($generator_name, $inc_by = 0)
+	{
+		//If a generator hasn't been used before it will return 0
+		return ibase_gen_id('"'.$generator_name.'"', $inc_by);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * List table query
+	 *
+	 * Generates a platform-specific query string so that the table names can be fetched
+	 *
+	 * @param	bool	$prefix_limit
+	 * @return	string
+	 */
+	protected function _list_tables($prefix_limit = FALSE)
+	{
+		$sql = 'SELECT TRIM("RDB$RELATION_NAME") AS TABLE_NAME FROM "RDB$RELATIONS" WHERE "RDB$RELATION_NAME" NOT LIKE \'RDB$%\' AND "RDB$RELATION_NAME" NOT LIKE \'MON$%\'';
+
+		if ($prefix_limit !== FALSE && $this->dbprefix !== '')
+		{
+			return $sql.' AND TRIM("RDB$RELATION_NAME") AS TABLE_NAME LIKE \''.$this->escape_like_str($this->dbprefix)."%' "
+				.sprintf($this->_like_escape_str, $this->_like_escape_chr);
+		}
+
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Show column query
+	 *
+	 * Generates a platform-specific query string so that the column names can be fetched
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _list_columns($table = '')
+	{
+		return 'SELECT TRIM("RDB$FIELD_NAME") AS COLUMN_NAME FROM "RDB$RELATION_FIELDS" WHERE "RDB$RELATION_NAME" = '.$this->escape($table);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Returns an object with field data
+	 *
+	 * @param	string	$table
+	 * @return	array
+	 */
+	public function field_data($table)
+	{
+		$sql = 'SELECT "rfields"."RDB$FIELD_NAME" AS "name",
+				CASE "fields"."RDB$FIELD_TYPE"
+					WHEN 7 THEN \'SMALLINT\'
+					WHEN 8 THEN \'INTEGER\'
+					WHEN 9 THEN \'QUAD\'
+					WHEN 10 THEN \'FLOAT\'
+					WHEN 11 THEN \'DFLOAT\'
+					WHEN 12 THEN \'DATE\'
+					WHEN 13 THEN \'TIME\'
+					WHEN 14 THEN \'CHAR\'
+					WHEN 16 THEN \'INT64\'
+					WHEN 27 THEN \'DOUBLE\'
+					WHEN 35 THEN \'TIMESTAMP\'
+					WHEN 37 THEN \'VARCHAR\'
+					WHEN 40 THEN \'CSTRING\'
+					WHEN 261 THEN \'BLOB\'
+					ELSE NULL
+				END AS "type",
+				"fields"."RDB$FIELD_LENGTH" AS "max_length",
+				"rfields"."RDB$DEFAULT_VALUE" AS "default"
+			FROM "RDB$RELATION_FIELDS" "rfields"
+				JOIN "RDB$FIELDS" "fields" ON "rfields"."RDB$FIELD_SOURCE" = "fields"."RDB$FIELD_NAME"
+			WHERE "rfields"."RDB$RELATION_NAME" = '.$this->escape($table).'
+			ORDER BY "rfields"."RDB$FIELD_POSITION"';
+
+		return (($query = $this->query($sql)) !== FALSE)
+			? $query->result_object()
+			: FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Error
+	 *
+	 * Returns an array containing code and message of the last
+	 * database error that has occurred.
+	 *
+	 * @return	array
+	 */
+	public function error()
+	{
+		return array('code' => ibase_errcode(), 'message' => ibase_errmsg());
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Update statement
+	 *
+	 * Generates a platform-specific update string from the supplied data
+	 *
+	 * @param	string	$table
+	 * @param	array	$values
+	 * @return	string
+	 */
+	protected function _update($table, $values)
+	{
+		$this->qb_limit = FALSE;
+		return parent::_update($table, $values);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Truncate statement
+	 *
+	 * Generates a platform-specific truncate string from the supplied data
+	 *
+	 * If the database does not support the TRUNCATE statement,
+	 * then this method maps to 'DELETE FROM table'
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _truncate($table)
+	{
+		return 'DELETE FROM '.$table;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Delete statement
+	 *
+	 * Generates a platform-specific delete string from the supplied data
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _delete($table)
+	{
+		$this->qb_limit = FALSE;
+		return parent::_delete($table);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * LIMIT
+	 *
+	 * Generates a platform-specific LIMIT clause
+	 *
+	 * @param	string	$sql	SQL Query
+	 * @return	string
+	 */
+	protected function _limit($sql)
+	{
+		// Limit clause depends on if Interbase or Firebird
+		if (stripos($this->version(), 'firebird') !== FALSE)
+		{
+			$select = 'FIRST '.$this->qb_limit
+				.($this->qb_offset ? ' SKIP '.$this->qb_offset : '');
+		}
+		else
+		{
+			$select = 'ROWS '
+				.($this->qb_offset ? $this->qb_offset.' TO '.($this->qb_limit + $this->qb_offset) : $this->qb_limit);
+		}
+
+		return preg_replace('`SELECT`i', 'SELECT '.$select, $sql, 1);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Insert batch statement
+	 *
+	 * Generates a platform-specific insert string from the supplied data.
+	 *
+	 * @param	string	$table	Table name
+	 * @param	array	$keys	INSERT keys
+	 * @param	array	$values	INSERT values
+	 * @return	string|bool
+	 */
+	protected function _insert_batch($table, $keys, $values)
+	{
+		return ($this->db_debug) ? $this->display_error('db_unsupported_feature') : FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Close DB Connection
+	 *
+	 * @return	void
+	 */
+	protected function _close()
+	{
+		ibase_close($this->conn_id);
+	}
+
+}

+ 251 - 0
apps/system/database/drivers/ibase/ibase_forge.php

@@ -0,0 +1,251 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 3.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * Interbase/Firebird Forge Class
+ *
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/database/
+ */
+class CI_DB_ibase_forge extends CI_DB_forge {
+
+	/**
+	 * CREATE TABLE IF statement
+	 *
+	 * @var	string
+	 */
+	protected $_create_table_if	= FALSE;
+
+	/**
+	 * RENAME TABLE statement
+	 *
+	 * @var	string
+	 */
+	protected $_rename_table	= FALSE;
+
+	/**
+	 * DROP TABLE IF statement
+	 *
+	 * @var	string
+	 */
+	protected $_drop_table_if	= FALSE;
+
+	/**
+	 * UNSIGNED support
+	 *
+	 * @var	array
+	 */
+	protected $_unsigned		= array(
+		'SMALLINT'	=> 'INTEGER',
+		'INTEGER'	=> 'INT64',
+		'FLOAT'		=> 'DOUBLE PRECISION'
+	);
+
+	/**
+	 * NULL value representation in CREATE/ALTER TABLE statements
+	 *
+	 * @var	string
+	 */
+	protected $_null		= 'NULL';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Create database
+	 *
+	 * @param	string	$db_name
+	 * @return	bool
+	 */
+	public function create_database($db_name)
+	{
+		// Firebird databases are flat files, so a path is required
+
+		// Hostname is needed for remote access
+		empty($this->db->hostname) OR $db_name = $this->hostname.':'.$db_name;
+
+		return parent::create_database('"'.$db_name.'"');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Drop database
+	 *
+	 * @param	string	$db_name	(ignored)
+	 * @return	bool
+	 */
+	public function drop_database($db_name)
+	{
+		if ( ! ibase_drop_db($this->conn_id))
+		{
+			return ($this->db->db_debug) ? $this->db->display_error('db_unable_to_drop') : FALSE;
+		}
+		elseif ( ! empty($this->db->data_cache['db_names']))
+		{
+			$key = array_search(strtolower($this->db->database), array_map('strtolower', $this->db->data_cache['db_names']), TRUE);
+			if ($key !== FALSE)
+			{
+				unset($this->db->data_cache['db_names'][$key]);
+			}
+		}
+
+		return TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ALTER TABLE
+	 *
+	 * @param	string	$alter_type	ALTER type
+	 * @param	string	$table		Table name
+	 * @param	mixed	$field		Column definition
+	 * @return	string|string[]
+	 */
+	protected function _alter_table($alter_type, $table, $field)
+ 	{
+		if (in_array($alter_type, array('DROP', 'ADD'), TRUE))
+		{
+			return parent::_alter_table($alter_type, $table, $field);
+		}
+
+		$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table);
+		$sqls = array();
+		for ($i = 0, $c = count($field); $i < $c; $i++)
+		{
+			if ($field[$i]['_literal'] !== FALSE)
+			{
+				return FALSE;
+			}
+
+			if (isset($field[$i]['type']))
+			{
+				$sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identififers($field[$i]['name'])
+					.' TYPE '.$field[$i]['type'].$field[$i]['length'];
+			}
+
+			if ( ! empty($field[$i]['default']))
+			{
+				$sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identifiers($field[$i]['name'])
+					.' SET DEFAULT '.$field[$i]['default'];
+			}
+
+			if (isset($field[$i]['null']))
+			{
+				$sqls[] = 'UPDATE "RDB$RELATION_FIELDS" SET "RDB$NULL_FLAG" = '
+					.($field[$i]['null'] === TRUE ? 'NULL' : '1')
+					.' WHERE "RDB$FIELD_NAME" = '.$this->db->escape($field[$i]['name'])
+					.' AND "RDB$RELATION_NAME" = '.$this->db->escape($table);
+			}
+
+			if ( ! empty($field[$i]['new_name']))
+			{
+				$sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identifiers($field[$i]['name'])
+					.' TO '.$this->db->escape_identifiers($field[$i]['new_name']);
+			}
+		}
+
+		return $sqls;
+ 	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Process column
+	 *
+	 * @param	array	$field
+	 * @return	string
+	 */
+	protected function _process_column($field)
+	{
+		return $this->db->escape_identifiers($field['name'])
+			.' '.$field['type'].$field['length']
+			.$field['null']
+			.$field['unique']
+			.$field['default'];
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute TYPE
+	 *
+	 * Performs a data type mapping between different databases.
+	 *
+	 * @param	array	&$attributes
+	 * @return	void
+	 */
+	protected function _attr_type(&$attributes)
+	{
+		switch (strtoupper($attributes['TYPE']))
+		{
+			case 'TINYINT':
+				$attributes['TYPE'] = 'SMALLINT';
+				$attributes['UNSIGNED'] = FALSE;
+				return;
+			case 'MEDIUMINT':
+				$attributes['TYPE'] = 'INTEGER';
+				$attributes['UNSIGNED'] = FALSE;
+				return;
+			case 'INT':
+				$attributes['TYPE'] = 'INTEGER';
+				return;
+			case 'BIGINT':
+				$attributes['TYPE'] = 'INT64';
+				return;
+			default: return;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute AUTO_INCREMENT
+	 *
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
+	 */
+	protected function _attr_auto_increment(&$attributes, &$field)
+	{
+		// Not supported
+	}
+
+}

+ 161 - 0
apps/system/database/drivers/ibase/ibase_result.php

@@ -0,0 +1,161 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 3.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * Interbase/Firebird Result Class
+ *
+ * This class extends the parent result class: CI_DB_result
+ *
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/database/
+ */
+class CI_DB_ibase_result extends CI_DB_result {
+
+	/**
+	 * Number of fields in the result set
+	 *
+	 * @return	int
+	 */
+	public function num_fields()
+	{
+		return ibase_num_fields($this->result_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Fetch Field Names
+	 *
+	 * Generates an array of column names
+	 *
+	 * @return	array
+	 */
+	public function list_fields()
+	{
+		$field_names = array();
+		for ($i = 0, $num_fields = $this->num_fields(); $i < $num_fields; $i++)
+		{
+			$info = ibase_field_info($this->result_id, $i);
+			$field_names[] = $info['name'];
+		}
+
+		return $field_names;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field data
+	 *
+	 * Generates an array of objects containing field meta-data
+	 *
+	 * @return	array
+	 */
+	public function field_data()
+	{
+		$retval = array();
+		for ($i = 0, $c = $this->num_fields(); $i < $c; $i++)
+		{
+			$info = ibase_field_info($this->result_id, $i);
+
+			$retval[$i]			= new stdClass();
+			$retval[$i]->name		= $info['name'];
+			$retval[$i]->type		= $info['type'];
+			$retval[$i]->max_length		= $info['length'];
+		}
+
+		return $retval;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Free the result
+	 *
+	 * @return	void
+	 */
+	public function free_result()
+	{
+		ibase_free_result($this->result_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Result - associative array
+	 *
+	 * Returns the result set as an array
+	 *
+	 * @return	array
+	 */
+	protected function _fetch_assoc()
+	{
+		return ibase_fetch_assoc($this->result_id, IBASE_FETCH_BLOBS);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Result - object
+	 *
+	 * Returns the result set as an object
+	 *
+	 * @param	string	$class_name
+	 * @return	object
+	 */
+	protected function _fetch_object($class_name = 'stdClass')
+	{
+		$row = ibase_fetch_object($this->result_id, IBASE_FETCH_BLOBS);
+
+		if ($class_name === 'stdClass' OR ! $row)
+		{
+			return $row;
+		}
+
+		$class_name = new $class_name();
+		foreach ($row as $key => $value)
+		{
+			$class_name->$key = $value;
+		}
+
+		return $class_name;
+	}
+
+}

+ 69 - 0
apps/system/database/drivers/ibase/ibase_utility.php

@@ -0,0 +1,69 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 3.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * Interbase/Firebird Utility Class
+ *
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/database/
+ */
+class CI_DB_ibase_utility extends CI_DB_utility {
+
+	/**
+	 * Export
+	 *
+	 * @param	string	$filename
+	 * @return	mixed
+	 */
+	protected function _backup($filename)
+	{
+		if ($service = ibase_service_attach($this->db->hostname, $this->db->username, $this->db->password))
+		{
+			$res = ibase_backup($service, $this->db->database, $filename.'.fbk');
+
+			// Close the service connection
+			ibase_service_detach($service);
+			return $res;
+		}
+
+		return FALSE;
+	}
+
+}

+ 11 - 0
apps/system/database/drivers/ibase/index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>403 Forbidden</title>
+</head>
+<body>
+
+<p>Directory access is forbidden.</p>
+
+</body>
+</html>

+ 11 - 0
apps/system/database/drivers/index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>403 Forbidden</title>
+</head>
+<body>
+
+<p>Directory access is forbidden.</p>
+
+</body>
+</html>

+ 11 - 0
apps/system/database/drivers/mssql/index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>403 Forbidden</title>
+</head>
+<body>
+
+<p>Directory access is forbidden.</p>
+
+</body>
+</html>

+ 518 - 0
apps/system/database/drivers/mssql/mssql_driver.php

@@ -0,0 +1,518 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 1.3.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * MS SQL Database Adapter Class
+ *
+ * Note: _DB is an extender class that the app controller
+ * creates dynamically based on whether the query builder
+ * class is being used or not.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Drivers
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/database/
+ */
+class CI_DB_mssql_driver extends CI_DB {
+
+	/**
+	 * Database driver
+	 *
+	 * @var	string
+	 */
+	public $dbdriver = 'mssql';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ORDER BY random keyword
+	 *
+	 * @var	array
+	 */
+	protected $_random_keyword = array('NEWID()', 'RAND(%d)');
+
+	/**
+	 * Quoted identifier flag
+	 *
+	 * Whether to use SQL-92 standard quoted identifier
+	 * (double quotes) or brackets for identifier escaping.
+	 *
+	 * @var	bool
+	 */
+	protected $_quoted_identifier = TRUE;
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * Appends the port number to the hostname, if needed.
+	 *
+	 * @param	array	$params
+	 * @return	void
+	 */
+	public function __construct($params)
+	{
+		parent::__construct($params);
+
+		if ( ! empty($this->port))
+		{
+			$this->hostname .= (DIRECTORY_SEPARATOR === '\\' ? ',' : ':').$this->port;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Non-persistent database connection
+	 *
+	 * @param	bool	$persistent
+	 * @return	resource
+	 */
+	public function db_connect($persistent = FALSE)
+	{
+		$this->conn_id = ($persistent)
+				? mssql_pconnect($this->hostname, $this->username, $this->password)
+				: mssql_connect($this->hostname, $this->username, $this->password);
+
+		if ( ! $this->conn_id)
+		{
+			return FALSE;
+		}
+
+		// ----------------------------------------------------------------
+
+		// Select the DB... assuming a database name is specified in the config file
+		if ($this->database !== '' && ! $this->db_select())
+		{
+			log_message('error', 'Unable to select database: '.$this->database);
+
+			return ($this->db_debug === TRUE)
+				? $this->display_error('db_unable_to_select', $this->database)
+				: FALSE;
+		}
+
+		// Determine how identifiers are escaped
+		$query = $this->query('SELECT CASE WHEN (@@OPTIONS | 256) = @@OPTIONS THEN 1 ELSE 0 END AS qi');
+		$query = $query->row_array();
+		$this->_quoted_identifier = empty($query) ? FALSE : (bool) $query['qi'];
+		$this->_escape_char = ($this->_quoted_identifier) ? '"' : array('[', ']');
+
+		return $this->conn_id;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Select the database
+	 *
+	 * @param	string	$database
+	 * @return	bool
+	 */
+	public function db_select($database = '')
+	{
+		if ($database === '')
+		{
+			$database = $this->database;
+		}
+
+		// Note: Escaping is required in the event that the DB name
+		// contains reserved characters.
+		if (mssql_select_db('['.$database.']', $this->conn_id))
+		{
+			$this->database = $database;
+			$this->data_cache = array();
+			return TRUE;
+		}
+
+		return FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Execute the query
+	 *
+	 * @param	string	$sql	an SQL query
+	 * @return	mixed	resource if rows are returned, bool otherwise
+	 */
+	protected function _execute($sql)
+	{
+		return mssql_query($sql, $this->conn_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Begin Transaction
+	 *
+	 * @return	bool
+	 */
+	protected function _trans_begin()
+	{
+		return $this->simple_query('BEGIN TRAN');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Commit Transaction
+	 *
+	 * @return	bool
+	 */
+	protected function _trans_commit()
+	{
+		return $this->simple_query('COMMIT TRAN');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Rollback Transaction
+	 *
+	 * @return	bool
+	 */
+	protected function _trans_rollback()
+	{
+		return $this->simple_query('ROLLBACK TRAN');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Affected Rows
+	 *
+	 * @return	int
+	 */
+	public function affected_rows()
+	{
+		return mssql_rows_affected($this->conn_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Insert ID
+	 *
+	 * Returns the last id created in the Identity column.
+	 *
+	 * @return	string
+	 */
+	public function insert_id()
+	{
+		$query = version_compare($this->version(), '8', '>=')
+			? 'SELECT SCOPE_IDENTITY() AS last_id'
+			: 'SELECT @@IDENTITY AS last_id';
+
+		$query = $this->query($query);
+		$query = $query->row();
+		return $query->last_id;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Set client character set
+	 *
+	 * @param	string	$charset
+	 * @return	bool
+	 */
+	protected function _db_set_charset($charset)
+	{
+		return (ini_set('mssql.charset', $charset) !== FALSE);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Version number query string
+	 *
+	 * @return	string
+	 */
+	protected function _version()
+	{
+		return "SELECT SERVERPROPERTY('ProductVersion') AS ver";
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * List table query
+	 *
+	 * Generates a platform-specific query string so that the table names can be fetched
+	 *
+	 * @param	bool	$prefix_limit
+	 * @return	string
+	 */
+	protected function _list_tables($prefix_limit = FALSE)
+	{
+		$sql = 'SELECT '.$this->escape_identifiers('name')
+			.' FROM '.$this->escape_identifiers('sysobjects')
+			.' WHERE '.$this->escape_identifiers('type')." = 'U'";
+
+		if ($prefix_limit !== FALSE && $this->dbprefix !== '')
+		{
+			$sql .= ' AND '.$this->escape_identifiers('name')." LIKE '".$this->escape_like_str($this->dbprefix)."%' "
+				.sprintf($this->_like_escape_str, $this->_like_escape_chr);
+		}
+
+		return $sql.' ORDER BY '.$this->escape_identifiers('name');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * List column query
+	 *
+	 * Generates a platform-specific query string so that the column names can be fetched
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _list_columns($table = '')
+	{
+		return 'SELECT COLUMN_NAME
+			FROM INFORMATION_SCHEMA.Columns
+			WHERE UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table));
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Returns an object with field data
+	 *
+	 * @param	string	$table
+	 * @return	array
+	 */
+	public function field_data($table)
+	{
+		$sql = 'SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, COLUMN_DEFAULT
+			FROM INFORMATION_SCHEMA.Columns
+			WHERE UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table));
+
+		if (($query = $this->query($sql)) === FALSE)
+		{
+			return FALSE;
+		}
+		$query = $query->result_object();
+
+		$retval = array();
+		for ($i = 0, $c = count($query); $i < $c; $i++)
+		{
+			$retval[$i]			= new stdClass();
+			$retval[$i]->name		= $query[$i]->COLUMN_NAME;
+			$retval[$i]->type		= $query[$i]->DATA_TYPE;
+			$retval[$i]->max_length		= ($query[$i]->CHARACTER_MAXIMUM_LENGTH > 0) ? $query[$i]->CHARACTER_MAXIMUM_LENGTH : $query[$i]->NUMERIC_PRECISION;
+			$retval[$i]->default		= $query[$i]->COLUMN_DEFAULT;
+		}
+
+		return $retval;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Error
+	 *
+	 * Returns an array containing code and message of the last
+	 * database error that has occurred.
+	 *
+	 * @return	array
+	 */
+	public function error()
+	{
+		// We need this because the error info is discarded by the
+		// server the first time you request it, and query() already
+		// calls error() once for logging purposes when a query fails.
+		static $error = array('code' => 0, 'message' => NULL);
+
+		$message = mssql_get_last_message();
+		if ( ! empty($message))
+		{
+			$error['code']    = $this->query('SELECT @@ERROR AS code')->row()->code;
+			$error['message'] = $message;
+		}
+
+		return $error;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Update statement
+	 *
+	 * Generates a platform-specific update string from the supplied data
+	 *
+	 * @param	string	$table
+	 * @param	array	$values
+	 * @return	string
+	 */
+	protected function _update($table, $values)
+	{
+		$this->qb_limit = FALSE;
+		$this->qb_orderby = array();
+		return parent::_update($table, $values);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Truncate statement
+	 *
+	 * Generates a platform-specific truncate string from the supplied data
+	 *
+	 * If the database does not support the TRUNCATE statement,
+	 * then this method maps to 'DELETE FROM table'
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _truncate($table)
+	{
+		return 'TRUNCATE TABLE '.$table;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Delete statement
+	 *
+	 * Generates a platform-specific delete string from the supplied data
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _delete($table)
+	{
+		if ($this->qb_limit)
+		{
+			return 'WITH ci_delete AS (SELECT TOP '.$this->qb_limit.' * FROM '.$table.$this->_compile_wh('qb_where').') DELETE FROM ci_delete';
+		}
+
+		return parent::_delete($table);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * LIMIT
+	 *
+	 * Generates a platform-specific LIMIT clause
+	 *
+	 * @param	string	$sql	SQL Query
+	 * @return	string
+	 */
+	protected function _limit($sql)
+	{
+		$limit = $this->qb_offset + $this->qb_limit;
+
+		// As of SQL Server 2005 (9.0.*) ROW_NUMBER() is supported,
+		// however an ORDER BY clause is required for it to work
+		if (version_compare($this->version(), '9', '>=') && $this->qb_offset && ! empty($this->qb_orderby))
+		{
+			$orderby = $this->_compile_order_by();
+
+			// We have to strip the ORDER BY clause
+			$sql = trim(substr($sql, 0, strrpos($sql, $orderby)));
+
+			// Get the fields to select from our subquery, so that we can avoid CI_rownum appearing in the actual results
+			if (count($this->qb_select) === 0 OR strpos(implode(',', $this->qb_select), '*') !== FALSE)
+			{
+				$select = '*'; // Inevitable
+			}
+			else
+			{
+				// Use only field names and their aliases, everything else is out of our scope.
+				$select = array();
+				$field_regexp = ($this->_quoted_identifier)
+					? '("[^\"]+")' : '(\[[^\]]+\])';
+				for ($i = 0, $c = count($this->qb_select); $i < $c; $i++)
+				{
+					$select[] = preg_match('/(?:\s|\.)'.$field_regexp.'$/i', $this->qb_select[$i], $m)
+						? $m[1] : $this->qb_select[$i];
+				}
+				$select = implode(', ', $select);
+			}
+
+			return 'SELECT '.$select." FROM (\n\n"
+				.preg_replace('/^(SELECT( DISTINCT)?)/i', '\\1 ROW_NUMBER() OVER('.trim($orderby).') AS '.$this->escape_identifiers('CI_rownum').', ', $sql)
+				."\n\n) ".$this->escape_identifiers('CI_subquery')
+				."\nWHERE ".$this->escape_identifiers('CI_rownum').' BETWEEN '.($this->qb_offset + 1).' AND '.$limit;
+		}
+
+		return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.$limit.' ', $sql);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Insert batch statement
+	 *
+	 * Generates a platform-specific insert string from the supplied data.
+	 *
+	 * @param	string	$table	Table name
+	 * @param	array	$keys	INSERT keys
+	 * @param	array	$values	INSERT values
+	 * @return	string|bool
+	 */
+	protected function _insert_batch($table, $keys, $values)
+	{
+		// Multiple-value inserts are only supported as of SQL Server 2008
+		if (version_compare($this->version(), '10', '>='))
+		{
+			return parent::_insert_batch($table, $keys, $values);
+		}
+
+		return ($this->db_debug) ? $this->display_error('db_unsupported_feature') : FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Close DB Connection
+	 *
+	 * @return	void
+	 */
+	protected function _close()
+	{
+		mssql_close($this->conn_id);
+	}
+
+}

+ 151 - 0
apps/system/database/drivers/mssql/mssql_forge.php

@@ -0,0 +1,151 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 1.3.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * MS SQL Forge Class
+ *
+ * @package		CodeIgniter
+ * @subpackage	Drivers
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/database/
+ */
+class CI_DB_mssql_forge extends CI_DB_forge {
+
+	/**
+	 * CREATE TABLE IF statement
+	 *
+	 * @var	string
+	 */
+	protected $_create_table_if	= "IF NOT EXISTS (SELECT * FROM sysobjects WHERE ID = object_id(N'%s') AND OBJECTPROPERTY(id, N'IsUserTable') = 1)\nCREATE TABLE";
+
+	/**
+	 * DROP TABLE IF statement
+	 *
+	 * @var	string
+	 */
+	protected $_drop_table_if	= "IF EXISTS (SELECT * FROM sysobjects WHERE ID = object_id(N'%s') AND OBJECTPROPERTY(id, N'IsUserTable') = 1)\nDROP TABLE";
+
+	/**
+	 * UNSIGNED support
+	 *
+	 * @var	array
+	 */
+	protected $_unsigned		= array(
+		'TINYINT'	=> 'SMALLINT',
+		'SMALLINT'	=> 'INT',
+		'INT'		=> 'BIGINT',
+		'REAL'		=> 'FLOAT'
+	);
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ALTER TABLE
+	 *
+	 * @param	string	$alter_type	ALTER type
+	 * @param	string	$table		Table name
+	 * @param	mixed	$field		Column definition
+	 * @return	string|string[]
+	 */
+	protected function _alter_table($alter_type, $table, $field)
+	{
+		if (in_array($alter_type, array('ADD', 'DROP'), TRUE))
+		{
+			return parent::_alter_table($alter_type, $table, $field);
+		}
+
+		$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table).' ALTER COLUMN ';
+		$sqls = array();
+		for ($i = 0, $c = count($field); $i < $c; $i++)
+		{
+			$sqls[] = $sql.$this->_process_column($field[$i]);
+		}
+
+		return $sqls;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute TYPE
+	 *
+	 * Performs a data type mapping between different databases.
+	 *
+	 * @param	array	&$attributes
+	 * @return	void
+	 */
+	protected function _attr_type(&$attributes)
+	{
+		if (isset($attributes['CONSTRAINT']) && strpos($attributes['TYPE'], 'INT') !== FALSE)
+		{
+			unset($attributes['CONSTRAINT']);
+		}
+
+		switch (strtoupper($attributes['TYPE']))
+		{
+			case 'MEDIUMINT':
+				$attributes['TYPE'] = 'INTEGER';
+				$attributes['UNSIGNED'] = FALSE;
+				return;
+			case 'INTEGER':
+				$attributes['TYPE'] = 'INT';
+				return;
+			default: return;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute AUTO_INCREMENT
+	 *
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
+	 */
+	protected function _attr_auto_increment(&$attributes, &$field)
+	{
+		if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE && stripos($field['type'], 'int') !== FALSE)
+		{
+			$field['auto_increment'] = ' IDENTITY(1,1)';
+		}
+	}
+
+}

+ 198 - 0
apps/system/database/drivers/mssql/mssql_result.php

@@ -0,0 +1,198 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 1.3.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * MSSQL Result Class
+ *
+ * This class extends the parent result class: CI_DB_result
+ *
+ * @package		CodeIgniter
+ * @subpackage	Drivers
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/database/
+ */
+class CI_DB_mssql_result extends CI_DB_result {
+
+	/**
+	 * Number of rows in the result set
+	 *
+	 * @return	int
+	 */
+	public function num_rows()
+	{
+		return is_int($this->num_rows)
+			? $this->num_rows
+			: $this->num_rows = mssql_num_rows($this->result_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Number of fields in the result set
+	 *
+	 * @return	int
+	 */
+	public function num_fields()
+	{
+		return mssql_num_fields($this->result_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Fetch Field Names
+	 *
+	 * Generates an array of column names
+	 *
+	 * @return	array
+	 */
+	public function list_fields()
+	{
+		$field_names = array();
+		mssql_field_seek($this->result_id, 0);
+		while ($field = mssql_fetch_field($this->result_id))
+		{
+			$field_names[] = $field->name;
+		}
+
+		return $field_names;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field data
+	 *
+	 * Generates an array of objects containing field meta-data
+	 *
+	 * @return	array
+	 */
+	public function field_data()
+	{
+		$retval = array();
+		for ($i = 0, $c = $this->num_fields(); $i < $c; $i++)
+		{
+			$field = mssql_fetch_field($this->result_id, $i);
+
+			$retval[$i]		= new stdClass();
+			$retval[$i]->name	= $field->name;
+			$retval[$i]->type	= $field->type;
+			$retval[$i]->max_length	= $field->max_length;
+		}
+
+		return $retval;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Free the result
+	 *
+	 * @return	void
+	 */
+	public function free_result()
+	{
+		if (is_resource($this->result_id))
+		{
+			mssql_free_result($this->result_id);
+			$this->result_id = FALSE;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Data Seek
+	 *
+	 * Moves the internal pointer to the desired offset. We call
+	 * this internally before fetching results to make sure the
+	 * result set starts at zero.
+	 *
+	 * @param	int	$n
+	 * @return	bool
+	 */
+	public function data_seek($n = 0)
+	{
+		return mssql_data_seek($this->result_id, $n);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Result - associative array
+	 *
+	 * Returns the result set as an array
+	 *
+	 * @return	array
+	 */
+	protected function _fetch_assoc()
+	{
+		return mssql_fetch_assoc($this->result_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Result - object
+	 *
+	 * Returns the result set as an object
+	 *
+	 * @param	string	$class_name
+	 * @return	object
+	 */
+	protected function _fetch_object($class_name = 'stdClass')
+	{
+		$row = mssql_fetch_object($this->result_id);
+
+		if ($class_name === 'stdClass' OR ! $row)
+		{
+			return $row;
+		}
+
+		$class_name = new $class_name();
+		foreach ($row as $key => $value)
+		{
+			$class_name->$key = $value;
+		}
+
+		return $class_name;
+	}
+
+}

+ 77 - 0
apps/system/database/drivers/mssql/mssql_utility.php

@@ -0,0 +1,77 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license	http://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 1.3.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * MS SQL Utility Class
+ *
+ * @package		CodeIgniter
+ * @subpackage	Drivers
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		https://codeigniter.com/user_guide/database/
+ */
+class CI_DB_mssql_utility extends CI_DB_utility {
+
+	/**
+	 * List databases statement
+	 *
+	 * @var	string
+	 */
+	protected $_list_databases	= 'EXEC sp_helpdb'; // Can also be: EXEC sp_databases
+
+	/**
+	 * OPTIMIZE TABLE statement
+	 *
+	 * @var	string
+	 */
+	protected $_optimize_table	= 'ALTER INDEX all ON %s REORGANIZE';
+
+	/**
+	 * Export
+	 *
+	 * @param	array	$params	Preferences
+	 * @return	bool
+	 */
+	protected function _backup($params = array())
+	{
+		// Currently unsupported
+		return $this->db->display_error('db_unsupported_feature');
+	}
+
+}

Some files were not shown because too many files changed in this diff