From c10c9e3131011f8b98e3884da46aad8ef8b15a8d Mon Sep 17 00:00:00 2001 From: Ismo Vuorinen Date: Thu, 11 Jul 2013 07:42:03 +0300 Subject: [PATCH] Initial commit, Ecard system build with CodeIgniter PHP framework Currently working - Basic structure - Basic caching and gzip compression for speed - Admin authentication --- .gitignore | 2 + .htaccess | 20 + application/.htaccess | 1 + application/config/assets.php | 53 + application/config/autoload.php | 127 + application/config/config.php | 370 +++ application/config/constants.php | 41 + application/config/database.php | 69 + application/config/doctypes.php | 15 + application/config/foreign_chars.php | 64 + application/config/hooks.php | 16 + application/config/index.html | 10 + application/config/migration.php | 41 + application/config/mimes.php | 106 + application/config/profiler.php | 17 + application/config/routes.php | 58 + application/config/smileys.php | 66 + application/config/user_agents.php | 178 ++ application/controllers/index.html | 10 + application/controllers/welcome.php | 107 + application/controllers/yllapito.php | 107 + application/core/MY_Model.php | 917 ++++++++ application/core/index.html | 10 + application/errors/error_404.php | 30 + application/errors/error_db.php | 62 + application/errors/error_general.php | 62 + application/errors/error_php.php | 10 + application/errors/index.html | 10 + application/helpers/index.html | 10 + application/hooks/index.html | 10 + application/index.html | 10 + application/language/english/index.html | 10 + application/libraries/index.html | 10 + application/logs/index.html | 10 + application/models/ecard_model.php | 20 + application/models/erkanaauth_model.php | 87 + application/models/index.html | 10 + application/third_party/index.html | 10 + application/views/_footer.php | 25 + application/views/_header.php | 139 ++ application/views/_partial_cardlist.php | 33 + application/views/index.html | 10 + application/views/info.php | 58 + application/views/new.php | 197 ++ application/views/show_all.php | 26 + application/views/show_one.php | 22 + application/views/welcome_message.php | 32 + application/views/yllapito/dashboard.php | 15 + application/views/yllapito/login.php | 83 + assets/.htaccess | 1 + assets/css/foundation.min.css | 1 + assets/css/normalize.min.css | 1 + assets/css/style.css | 179 ++ assets/img/headerbg2.png | Bin 0 -> 129251 bytes assets/img/logo.png | Bin 0 -> 21281 bytes assets/img/tausta.jpg | Bin 0 -> 48008 bytes favicon.ico | Bin 0 -> 3794 bytes index.php | 213 ++ system/.htaccess | 1 + system/core/Benchmark.php | 118 + system/core/CodeIgniter.php | 402 ++++ system/core/Common.php | 564 +++++ system/core/Config.php | 379 +++ system/core/Controller.php | 64 + system/core/Exceptions.php | 193 ++ system/core/Hooks.php | 248 ++ system/core/Input.php | 849 +++++++ system/core/Lang.php | 160 ++ system/core/Loader.php | 1248 ++++++++++ system/core/Model.php | 57 + system/core/Output.php | 574 +++++ system/core/Router.php | 522 ++++ system/core/Security.php | 876 +++++++ system/core/URI.php | 654 ++++++ system/core/Utf8.php | 165 ++ system/core/index.html | 10 + system/database/DB.php | 162 ++ system/database/DB_active_rec.php | 2045 ++++++++++++++++ system/database/DB_cache.php | 195 ++ system/database/DB_driver.php | 1410 +++++++++++ system/database/DB_forge.php | 382 +++ system/database/DB_result.php | 410 ++++ system/database/DB_utility.php | 414 ++++ .../database/drivers/cubrid/cubrid_driver.php | 792 +++++++ .../database/drivers/cubrid/cubrid_forge.php | 288 +++ .../database/drivers/cubrid/cubrid_result.php | 202 ++ .../drivers/cubrid/cubrid_utility.php | 108 + system/database/drivers/cubrid/index.html | 10 + system/database/drivers/index.html | 10 + system/database/drivers/mssql/index.html | 10 + .../database/drivers/mssql/mssql_driver.php | 667 ++++++ system/database/drivers/mssql/mssql_forge.php | 248 ++ .../database/drivers/mssql/mssql_result.php | 169 ++ .../database/drivers/mssql/mssql_utility.php | 88 + system/database/drivers/mysql/index.html | 10 + .../database/drivers/mysql/mysql_driver.php | 779 ++++++ system/database/drivers/mysql/mysql_forge.php | 273 +++ .../database/drivers/mysql/mysql_result.php | 174 ++ .../database/drivers/mysql/mysql_utility.php | 210 ++ system/database/drivers/mysqli/index.html | 10 + .../database/drivers/mysqli/mysqli_driver.php | 776 ++++++ .../database/drivers/mysqli/mysqli_forge.php | 258 ++ .../database/drivers/mysqli/mysqli_result.php | 174 ++ .../drivers/mysqli/mysqli_utility.php | 87 + system/database/drivers/oci8/index.html | 10 + system/database/drivers/oci8/oci8_driver.php | 808 +++++++ system/database/drivers/oci8/oci8_forge.php | 248 ++ system/database/drivers/oci8/oci8_result.php | 217 ++ system/database/drivers/oci8/oci8_utility.php | 87 + system/database/drivers/odbc/index.html | 10 + system/database/drivers/odbc/odbc_driver.php | 637 +++++ system/database/drivers/odbc/odbc_forge.php | 266 +++ system/database/drivers/odbc/odbc_result.php | 228 ++ system/database/drivers/odbc/odbc_utility.php | 103 + system/database/drivers/pdo/index.html | 10 + system/database/drivers/pdo/pdo_driver.php | 812 +++++++ system/database/drivers/pdo/pdo_forge.php | 266 +++ system/database/drivers/pdo/pdo_result.php | 183 ++ system/database/drivers/pdo/pdo_utility.php | 103 + system/database/drivers/postgre/index.html | 10 + .../drivers/postgre/postgre_driver.php | 703 ++++++ .../drivers/postgre/postgre_forge.php | 299 +++ .../drivers/postgre/postgre_result.php | 169 ++ .../drivers/postgre/postgre_utility.php | 88 + system/database/drivers/sqlite/index.html | 10 + .../database/drivers/sqlite/sqlite_driver.php | 658 ++++++ .../database/drivers/sqlite/sqlite_forge.php | 265 +++ .../database/drivers/sqlite/sqlite_result.php | 179 ++ .../drivers/sqlite/sqlite_utility.php | 96 + system/database/drivers/sqlsrv/index.html | 10 + .../database/drivers/sqlsrv/sqlsrv_driver.php | 599 +++++ .../database/drivers/sqlsrv/sqlsrv_forge.php | 248 ++ .../database/drivers/sqlsrv/sqlsrv_result.php | 169 ++ .../drivers/sqlsrv/sqlsrv_utility.php | 88 + system/database/index.html | 10 + system/fonts/index.html | 10 + system/fonts/texb.ttf | Bin 0 -> 143830 bytes system/helpers/array_helper.php | 119 + system/helpers/captcha_helper.php | 246 ++ system/helpers/cookie_helper.php | 103 + system/helpers/date_helper.php | 611 +++++ system/helpers/directory_helper.php | 80 + system/helpers/download_helper.php | 107 + system/helpers/email_helper.php | 62 + system/helpers/file_helper.php | 479 ++++ system/helpers/form_helper.php | 1054 +++++++++ system/helpers/html_helper.php | 436 ++++ system/helpers/index.html | 10 + system/helpers/inflector_helper.php | 203 ++ system/helpers/language_helper.php | 58 + system/helpers/number_helper.php | 76 + system/helpers/path_helper.php | 72 + system/helpers/security_helper.php | 128 + system/helpers/smiley_helper.php | 281 +++ system/helpers/string_helper.php | 307 +++ system/helpers/text_helper.php | 535 +++++ system/helpers/typography_helper.php | 93 + system/helpers/url_helper.php | 594 +++++ system/helpers/xml_helper.php | 71 + system/index.html | 10 + system/language/english/calendar_lang.php | 51 + system/language/english/date_lang.php | 61 + system/language/english/db_lang.php | 29 + system/language/english/email_lang.php | 24 + .../language/english/form_validation_lang.php | 29 + system/language/english/ftp_lang.php | 18 + system/language/english/imglib_lang.php | 24 + system/language/english/index.html | 10 + system/language/english/migration_lang.php | 13 + system/language/english/number_lang.php | 10 + system/language/english/profiler_lang.php | 25 + system/language/english/unit_test_lang.php | 25 + system/language/english/upload_lang.php | 22 + system/language/index.html | 10 + system/libraries/Cache/Cache.php | 216 ++ system/libraries/Cache/drivers/Cache_apc.php | 151 ++ .../libraries/Cache/drivers/Cache_dummy.php | 129 + system/libraries/Cache/drivers/Cache_file.php | 195 ++ .../Cache/drivers/Cache_memcached.php | 218 ++ system/libraries/Calendar.php | 475 ++++ system/libraries/Cart.php | 552 +++++ system/libraries/Driver.php | 229 ++ system/libraries/Email.php | 2092 +++++++++++++++++ system/libraries/Encrypt.php | 547 +++++ system/libraries/Form_validation.php | 1382 +++++++++++ system/libraries/Ftp.php | 660 ++++++ system/libraries/Image_lib.php | 1537 ++++++++++++ system/libraries/Javascript.php | 871 +++++++ system/libraries/Log.php | 114 + system/libraries/Migration.php | 328 +++ system/libraries/Pagination.php | 340 +++ system/libraries/Parser.php | 212 ++ system/libraries/Profiler.php | 558 +++++ system/libraries/Session.php | 780 ++++++ system/libraries/Sha1.php | 251 ++ system/libraries/Table.php | 531 +++++ system/libraries/Trackback.php | 548 +++++ system/libraries/Typography.php | 410 ++++ system/libraries/Unit_test.php | 383 +++ system/libraries/Upload.php | 1136 +++++++++ system/libraries/User_agent.php | 549 +++++ system/libraries/Xmlrpc.php | 1423 +++++++++++ system/libraries/Xmlrpcs.php | 612 +++++ system/libraries/Zip.php | 423 ++++ system/libraries/index.html | 10 + system/libraries/javascript/Jquery.php | 1071 +++++++++ 206 files changed, 53864 insertions(+) create mode 100644 .htaccess create mode 100644 application/.htaccess create mode 100644 application/config/assets.php create mode 100644 application/config/autoload.php create mode 100644 application/config/config.php create mode 100644 application/config/constants.php create mode 100644 application/config/database.php create mode 100644 application/config/doctypes.php create mode 100644 application/config/foreign_chars.php create mode 100644 application/config/hooks.php create mode 100644 application/config/index.html create mode 100644 application/config/migration.php create mode 100644 application/config/mimes.php create mode 100644 application/config/profiler.php create mode 100644 application/config/routes.php create mode 100644 application/config/smileys.php create mode 100644 application/config/user_agents.php create mode 100644 application/controllers/index.html create mode 100644 application/controllers/welcome.php create mode 100644 application/controllers/yllapito.php create mode 100644 application/core/MY_Model.php create mode 100644 application/core/index.html create mode 100644 application/errors/error_404.php create mode 100644 application/errors/error_db.php create mode 100644 application/errors/error_general.php create mode 100644 application/errors/error_php.php create mode 100644 application/errors/index.html create mode 100644 application/helpers/index.html create mode 100644 application/hooks/index.html create mode 100644 application/index.html create mode 100644 application/language/english/index.html create mode 100644 application/libraries/index.html create mode 100644 application/logs/index.html create mode 100644 application/models/ecard_model.php create mode 100755 application/models/erkanaauth_model.php create mode 100644 application/models/index.html create mode 100644 application/third_party/index.html create mode 100644 application/views/_footer.php create mode 100644 application/views/_header.php create mode 100644 application/views/_partial_cardlist.php create mode 100644 application/views/index.html create mode 100644 application/views/info.php create mode 100644 application/views/new.php create mode 100644 application/views/show_all.php create mode 100644 application/views/show_one.php create mode 100644 application/views/welcome_message.php create mode 100644 application/views/yllapito/dashboard.php create mode 100644 application/views/yllapito/login.php create mode 100644 assets/.htaccess create mode 100644 assets/css/foundation.min.css create mode 100644 assets/css/normalize.min.css create mode 100644 assets/css/style.css create mode 100644 assets/img/headerbg2.png create mode 100644 assets/img/logo.png create mode 100644 assets/img/tausta.jpg create mode 100644 favicon.ico create mode 100644 index.php create mode 100644 system/.htaccess create mode 100755 system/core/Benchmark.php create mode 100755 system/core/CodeIgniter.php create mode 100644 system/core/Common.php create mode 100755 system/core/Config.php create mode 100644 system/core/Controller.php create mode 100755 system/core/Exceptions.php create mode 100755 system/core/Hooks.php create mode 100755 system/core/Input.php create mode 100755 system/core/Lang.php create mode 100644 system/core/Loader.php create mode 100755 system/core/Model.php create mode 100755 system/core/Output.php create mode 100755 system/core/Router.php create mode 100755 system/core/Security.php create mode 100755 system/core/URI.php create mode 100644 system/core/Utf8.php create mode 100644 system/core/index.html create mode 100755 system/database/DB.php create mode 100644 system/database/DB_active_rec.php create mode 100644 system/database/DB_cache.php create mode 100644 system/database/DB_driver.php create mode 100644 system/database/DB_forge.php create mode 100644 system/database/DB_result.php create mode 100644 system/database/DB_utility.php create mode 100644 system/database/drivers/cubrid/cubrid_driver.php create mode 100644 system/database/drivers/cubrid/cubrid_forge.php create mode 100644 system/database/drivers/cubrid/cubrid_result.php create mode 100644 system/database/drivers/cubrid/cubrid_utility.php create mode 100644 system/database/drivers/cubrid/index.html create mode 100644 system/database/drivers/index.html create mode 100644 system/database/drivers/mssql/index.html create mode 100644 system/database/drivers/mssql/mssql_driver.php create mode 100644 system/database/drivers/mssql/mssql_forge.php create mode 100644 system/database/drivers/mssql/mssql_result.php create mode 100644 system/database/drivers/mssql/mssql_utility.php create mode 100644 system/database/drivers/mysql/index.html create mode 100644 system/database/drivers/mysql/mysql_driver.php create mode 100644 system/database/drivers/mysql/mysql_forge.php create mode 100644 system/database/drivers/mysql/mysql_result.php create mode 100644 system/database/drivers/mysql/mysql_utility.php create mode 100644 system/database/drivers/mysqli/index.html create mode 100644 system/database/drivers/mysqli/mysqli_driver.php create mode 100644 system/database/drivers/mysqli/mysqli_forge.php create mode 100644 system/database/drivers/mysqli/mysqli_result.php create mode 100644 system/database/drivers/mysqli/mysqli_utility.php create mode 100644 system/database/drivers/oci8/index.html create mode 100644 system/database/drivers/oci8/oci8_driver.php create mode 100644 system/database/drivers/oci8/oci8_forge.php create mode 100644 system/database/drivers/oci8/oci8_result.php create mode 100644 system/database/drivers/oci8/oci8_utility.php create mode 100644 system/database/drivers/odbc/index.html create mode 100644 system/database/drivers/odbc/odbc_driver.php create mode 100644 system/database/drivers/odbc/odbc_forge.php create mode 100644 system/database/drivers/odbc/odbc_result.php create mode 100644 system/database/drivers/odbc/odbc_utility.php create mode 100644 system/database/drivers/pdo/index.html create mode 100644 system/database/drivers/pdo/pdo_driver.php create mode 100644 system/database/drivers/pdo/pdo_forge.php create mode 100644 system/database/drivers/pdo/pdo_result.php create mode 100644 system/database/drivers/pdo/pdo_utility.php create mode 100644 system/database/drivers/postgre/index.html create mode 100644 system/database/drivers/postgre/postgre_driver.php create mode 100644 system/database/drivers/postgre/postgre_forge.php create mode 100644 system/database/drivers/postgre/postgre_result.php create mode 100644 system/database/drivers/postgre/postgre_utility.php create mode 100644 system/database/drivers/sqlite/index.html create mode 100644 system/database/drivers/sqlite/sqlite_driver.php create mode 100644 system/database/drivers/sqlite/sqlite_forge.php create mode 100644 system/database/drivers/sqlite/sqlite_result.php create mode 100644 system/database/drivers/sqlite/sqlite_utility.php create mode 100644 system/database/drivers/sqlsrv/index.html create mode 100644 system/database/drivers/sqlsrv/sqlsrv_driver.php create mode 100644 system/database/drivers/sqlsrv/sqlsrv_forge.php create mode 100644 system/database/drivers/sqlsrv/sqlsrv_result.php create mode 100644 system/database/drivers/sqlsrv/sqlsrv_utility.php create mode 100644 system/database/index.html create mode 100644 system/fonts/index.html create mode 100644 system/fonts/texb.ttf create mode 100644 system/helpers/array_helper.php create mode 100644 system/helpers/captcha_helper.php create mode 100644 system/helpers/cookie_helper.php create mode 100644 system/helpers/date_helper.php create mode 100644 system/helpers/directory_helper.php create mode 100644 system/helpers/download_helper.php create mode 100644 system/helpers/email_helper.php create mode 100644 system/helpers/file_helper.php create mode 100644 system/helpers/form_helper.php create mode 100644 system/helpers/html_helper.php create mode 100644 system/helpers/index.html create mode 100644 system/helpers/inflector_helper.php create mode 100644 system/helpers/language_helper.php create mode 100644 system/helpers/number_helper.php create mode 100644 system/helpers/path_helper.php create mode 100644 system/helpers/security_helper.php create mode 100644 system/helpers/smiley_helper.php create mode 100644 system/helpers/string_helper.php create mode 100644 system/helpers/text_helper.php create mode 100644 system/helpers/typography_helper.php create mode 100644 system/helpers/url_helper.php create mode 100644 system/helpers/xml_helper.php create mode 100644 system/index.html create mode 100644 system/language/english/calendar_lang.php create mode 100644 system/language/english/date_lang.php create mode 100644 system/language/english/db_lang.php create mode 100644 system/language/english/email_lang.php create mode 100644 system/language/english/form_validation_lang.php create mode 100644 system/language/english/ftp_lang.php create mode 100644 system/language/english/imglib_lang.php create mode 100644 system/language/english/index.html create mode 100644 system/language/english/migration_lang.php create mode 100644 system/language/english/number_lang.php create mode 100644 system/language/english/profiler_lang.php create mode 100644 system/language/english/unit_test_lang.php create mode 100644 system/language/english/upload_lang.php create mode 100644 system/language/index.html create mode 100644 system/libraries/Cache/Cache.php create mode 100644 system/libraries/Cache/drivers/Cache_apc.php create mode 100644 system/libraries/Cache/drivers/Cache_dummy.php create mode 100644 system/libraries/Cache/drivers/Cache_file.php create mode 100644 system/libraries/Cache/drivers/Cache_memcached.php create mode 100644 system/libraries/Calendar.php create mode 100644 system/libraries/Cart.php create mode 100644 system/libraries/Driver.php create mode 100644 system/libraries/Email.php create mode 100644 system/libraries/Encrypt.php create mode 100644 system/libraries/Form_validation.php create mode 100644 system/libraries/Ftp.php create mode 100644 system/libraries/Image_lib.php create mode 100644 system/libraries/Javascript.php create mode 100644 system/libraries/Log.php create mode 100644 system/libraries/Migration.php create mode 100644 system/libraries/Pagination.php create mode 100644 system/libraries/Parser.php create mode 100644 system/libraries/Profiler.php create mode 100644 system/libraries/Session.php create mode 100644 system/libraries/Sha1.php create mode 100644 system/libraries/Table.php create mode 100644 system/libraries/Trackback.php create mode 100644 system/libraries/Typography.php create mode 100644 system/libraries/Unit_test.php create mode 100644 system/libraries/Upload.php create mode 100644 system/libraries/User_agent.php create mode 100644 system/libraries/Xmlrpc.php create mode 100644 system/libraries/Xmlrpcs.php create mode 100644 system/libraries/Zip.php create mode 100644 system/libraries/index.html create mode 100644 system/libraries/javascript/Jquery.php diff --git a/.gitignore b/.gitignore index e84c641..9048853 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ +*/config/production */config/development */logs/log-*.php */logs/!index.html */cache/* */cache/!index.html +*.sublime-* diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..89959fc --- /dev/null +++ b/.htaccess @@ -0,0 +1,20 @@ + + ExpiresActive On + ############################################ + ## Add default Expires header + ## http://developer.yahoo.com/performance/rules.html#expires + + ExpiresDefault "access plus 1 year" + ExpiresByType image/gif A2592000 + ExpiresByType image/jpeg A2592000 + ExpiresByType text/plain A2592000 + ExpiresByType text/css A2592000 + + + +AddOutputFilterByType DEFLATE text/html text/plain text/xml application/x-javascript text/css + +RewriteEngine On +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule ^(.*)$ index.php/$1 [QSA,L] \ No newline at end of file diff --git a/application/.htaccess b/application/.htaccess new file mode 100644 index 0000000..14249c5 --- /dev/null +++ b/application/.htaccess @@ -0,0 +1 @@ +Deny from all \ No newline at end of file diff --git a/application/config/assets.php b/application/config/assets.php new file mode 100644 index 0000000..2418381 --- /dev/null +++ b/application/config/assets.php @@ -0,0 +1,53 @@ + '', + 'xhtml1-strict' => '', + 'xhtml1-trans' => '', + 'xhtml1-frame' => '', + 'html5' => '', + 'html4-strict' => '', + 'html4-trans' => '', + 'html4-frame' => '' + ); + +/* End of file doctypes.php */ +/* Location: ./application/config/doctypes.php */ \ No newline at end of file diff --git a/application/config/foreign_chars.php b/application/config/foreign_chars.php new file mode 100644 index 0000000..14b0d73 --- /dev/null +++ b/application/config/foreign_chars.php @@ -0,0 +1,64 @@ + 'ae', + '/ö|œ/' => 'oe', + '/ü/' => 'ue', + '/Ä/' => 'Ae', + '/Ü/' => 'Ue', + '/Ö/' => 'Oe', + '/À|Á|Â|Ã|Ä|Å|Ǻ|Ā|Ă|Ą|Ǎ/' => 'A', + '/à|á|â|ã|å|ǻ|ā|ă|ą|ǎ|ª/' => 'a', + '/Ç|Ć|Ĉ|Ċ|Č/' => 'C', + '/ç|ć|ĉ|ċ|č/' => 'c', + '/Ð|Ď|Đ/' => 'D', + '/ð|ď|đ/' => 'd', + '/È|É|Ê|Ë|Ē|Ĕ|Ė|Ę|Ě/' => 'E', + '/è|é|ê|ë|ē|ĕ|ė|ę|ě/' => 'e', + '/Ĝ|Ğ|Ġ|Ģ/' => 'G', + '/ĝ|ğ|ġ|ģ/' => 'g', + '/Ĥ|Ħ/' => 'H', + '/ĥ|ħ/' => 'h', + '/Ì|Í|Î|Ï|Ĩ|Ī|Ĭ|Ǐ|Į|İ/' => 'I', + '/ì|í|î|ï|ĩ|ī|ĭ|ǐ|į|ı/' => 'i', + '/Ĵ/' => 'J', + '/ĵ/' => 'j', + '/Ķ/' => 'K', + '/ķ/' => 'k', + '/Ĺ|Ļ|Ľ|Ŀ|Ł/' => 'L', + '/ĺ|ļ|ľ|ŀ|ł/' => 'l', + '/Ñ|Ń|Ņ|Ň/' => 'N', + '/ñ|ń|ņ|ň|ʼn/' => 'n', + '/Ò|Ó|Ô|Õ|Ō|Ŏ|Ǒ|Ő|Ơ|Ø|Ǿ/' => 'O', + '/ò|ó|ô|õ|ō|ŏ|ǒ|ő|ơ|ø|ǿ|º/' => 'o', + '/Ŕ|Ŗ|Ř/' => 'R', + '/ŕ|ŗ|ř/' => 'r', + '/Ś|Ŝ|Ş|Š/' => 'S', + '/ś|ŝ|ş|š|ſ/' => 's', + '/Ţ|Ť|Ŧ/' => 'T', + '/ţ|ť|ŧ/' => 't', + '/Ù|Ú|Û|Ũ|Ū|Ŭ|Ů|Ű|Ų|Ư|Ǔ|Ǖ|Ǘ|Ǚ|Ǜ/' => 'U', + '/ù|ú|û|ũ|ū|ŭ|ů|ű|ų|ư|ǔ|ǖ|ǘ|ǚ|ǜ/' => 'u', + '/Ý|Ÿ|Ŷ/' => 'Y', + '/ý|ÿ|ŷ/' => 'y', + '/Ŵ/' => 'W', + '/ŵ/' => 'w', + '/Ź|Ż|Ž/' => 'Z', + '/ź|ż|ž/' => 'z', + '/Æ|Ǽ/' => 'AE', + '/ß/'=> 'ss', + '/IJ/' => 'IJ', + '/ij/' => 'ij', + '/Œ/' => 'OE', + '/ƒ/' => 'f' +); + +/* End of file foreign_chars.php */ +/* Location: ./application/config/foreign_chars.php */ \ No newline at end of file diff --git a/application/config/hooks.php b/application/config/hooks.php new file mode 100644 index 0000000..a4ad2be --- /dev/null +++ b/application/config/hooks.php @@ -0,0 +1,16 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/application/config/migration.php b/application/config/migration.php new file mode 100644 index 0000000..1c547c8 --- /dev/null +++ b/application/config/migration.php @@ -0,0 +1,41 @@ +migration->latest() this is the version that schema will +| be upgraded / downgraded to. +| +*/ +$config['migration_version'] = 1; + + +/* +|-------------------------------------------------------------------------- +| 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/'; + + +/* End of file migration.php */ +/* Location: ./application/config/migration.php */ \ No newline at end of file diff --git a/application/config/mimes.php b/application/config/mimes.php new file mode 100644 index 0000000..100f7d4 --- /dev/null +++ b/application/config/mimes.php @@ -0,0 +1,106 @@ + 'application/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'), + 'bin' => 'application/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' => 'application/x-photoshop', + 'so' => 'application/octet-stream', + 'sea' => 'application/octet-stream', + 'dll' => 'application/octet-stream', + 'oda' => 'application/oda', + 'pdf' => array('application/pdf', 'application/x-download'), + 'ai' => 'application/postscript', + 'eps' => 'application/postscript', + 'ps' => 'application/postscript', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'mif' => 'application/vnd.mif', + 'xls' => array('application/excel', 'application/vnd.ms-excel', 'application/msexcel'), + 'ppt' => array('application/powerpoint', 'application/vnd.ms-powerpoint'), + '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', + 'php' => 'application/x-httpd-php', + 'php4' => 'application/x-httpd-php', + 'php3' => 'application/x-httpd-php', + 'phtml' => 'application/x-httpd-php', + 'phps' => 'application/x-httpd-php-source', + 'js' => 'application/x-javascript', + 'swf' => 'application/x-shockwave-flash', + 'sit' => 'application/x-stuffit', + 'tar' => 'application/x-tar', + 'tgz' => array('application/x-tar', 'application/x-gzip-compressed'), + 'xhtml' => 'application/xhtml+xml', + 'xht' => 'application/xhtml+xml', + 'zip' => array('application/x-zip', 'application/zip', 'application/x-zip-compressed'), + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mpga' => 'audio/mpeg', + 'mp2' => 'audio/mpeg', + 'mp3' => array('audio/mpeg', 'audio/mpg', 'audio/mpeg3', 'audio/mp3'), + 'aif' => 'audio/x-aiff', + 'aiff' => 'audio/x-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-windows-bmp'), + 'gif' => 'image/gif', + 'jpeg' => array('image/jpeg', 'image/pjpeg'), + 'jpg' => array('image/jpeg', 'image/pjpeg'), + 'jpe' => array('image/jpeg', 'image/pjpeg'), + 'png' => array('image/png', 'image/x-png'), + 'tiff' => 'image/tiff', + 'tif' => 'image/tiff', + 'css' => 'text/css', + 'html' => 'text/html', + 'htm' => 'text/html', + 'shtml' => 'text/html', + 'txt' => 'text/plain', + 'text' => 'text/plain', + 'log' => array('text/plain', 'text/x-log'), + 'rtx' => 'text/richtext', + 'rtf' => 'text/rtf', + 'xml' => 'text/xml', + 'xsl' => 'text/xml', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpe' => 'video/mpeg', + 'qt' => 'video/quicktime', + 'mov' => 'video/quicktime', + 'avi' => 'video/x-msvideo', + 'movie' => 'video/x-sgi-movie', + 'doc' => 'application/msword', + 'docx' => array('application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/zip'), + 'xlsx' => array('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/zip'), + 'word' => array('application/msword', 'application/octet-stream'), + 'xl' => 'application/excel', + 'eml' => 'message/rfc822', + 'json' => array('application/json', 'text/json') + ); + + +/* End of file mimes.php */ +/* Location: ./application/config/mimes.php */ diff --git a/application/config/profiler.php b/application/config/profiler.php new file mode 100644 index 0000000..f8a5b1a --- /dev/null +++ b/application/config/profiler.php @@ -0,0 +1,17 @@ + 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', 'excaim'), + ':question:' => array('question.gif', '19', '19', 'question') // no comma after last item + + ); + +/* End of file smileys.php */ +/* Location: ./application/config/smileys.php */ \ No newline at end of file diff --git a/application/config/user_agents.php b/application/config/user_agents.php new file mode 100644 index 0000000..e2d3c3a --- /dev/null +++ b/application/config/user_agents.php @@ -0,0 +1,178 @@ + 'Windows Longhorn', + 'windows nt 5.2' => 'Windows 2003', + 'windows nt 5.0' => 'Windows 2000', + 'windows nt 5.1' => 'Windows XP', + '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' => 'Unknown Windows OS', + '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' + ); + + +// 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( + 'Flock' => 'Flock', + 'Chrome' => 'Chrome', + 'Opera' => 'Opera', + 'MSIE' => 'Internet Explorer', + 'Internet Explorer' => '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' + ); + +$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", + 'hiptop' => "Danger Hiptop", + 'nec-' => "NEC", + 'panasonic' => "Panasonic", + 'philips' => "Philips", + 'sagem' => "Sagem", + 'sanyo' => "Sanyo", + 'spv' => "SPV", + 'zte' => "ZTE", + 'sendo' => "Sendo", + + // Operating Systems + 'symbian' => "Symbian", + 'SymbianOS' => "SymbianOS", + 'elaine' => "Palm", + 'palm' => "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", + + // 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', + 'slurp' => 'Inktomi Slurp', + 'yahoo' => 'Yahoo', + 'askjeeves' => 'AskJeeves', + 'fastcrawler' => 'FastCrawler', + 'infoseek' => 'InfoSeek Robot 1.0', + 'lycos' => 'Lycos' + ); + +/* End of file user_agents.php */ +/* Location: ./application/config/user_agents.php */ \ No newline at end of file diff --git a/application/controllers/index.html b/application/controllers/index.html new file mode 100644 index 0000000..c942a79 --- /dev/null +++ b/application/controllers/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/application/controllers/welcome.php b/application/controllers/welcome.php new file mode 100644 index 0000000..eb9a33a --- /dev/null +++ b/application/controllers/welcome.php @@ -0,0 +1,107 @@ +load->model('ecard_model', 'ecard'); + } + + public function index() + { + + $data = array( + 'page_title' => array( 'Etusivu', 'Ystäväkylä eKortti' ), + 'page_classes' => array( 'frontpage' ) + ); + + $this->load->view('_header', $data); + $this->load->view('welcome_message', $data); + $this->load->view('_footer', $data); + } + + public function info() + { + $data = array( + 'page_title' => array( 'Tietoa', 'Ystäväkylä eKortti' ), + 'page_classes' => array( 'info' ) + ); + + $this->load->view('_header', $data); + $this->load->view('info', $data); + $this->load->view('_footer', $data); + } + + public function newCard() + { + + $data = array( + 'page_title' => array( 'Uusi eKortti', 'Ystäväkylä eKortti' ), + 'page_classes' => array( 'new_card' ), + + 'images' => array( + "http://placekitten.com/800/550", + "http://placekitten.com/g/800/550", + "http://placekitten.com/800/551", + "http://placekitten.com/g/800/551", + "http://placekitten.com/800/552", + "http://placekitten.com/g/800/552" + ) + ); + + $this->load->view('_header', $data); + $this->load->view('new', $data); + $this->load->view('_footer', $data); + } + + public function ecards($card_id = null) + { + $data = array( + 'page_classes' => array( 'ecards' ) + ); + + if (empty($card_id)) { + $data['ecards'] = $this->ecard->get_all(); + $data['page_title'] = array( 'Listaa kaikki kortit', 'Ystäväkylä eKortti' ); + $data['page_classes'][] = 'show_all'; + + $this->load->view('_header', $data); + $this->load->view('show_all', $data); + $this->load->view('_footer', $data); + + } else { + + if (strlen($card_id) != 32) { + redirect("ecards"); + } + + $data['ecard'] = $this->ecard->get_by('hash', $card_id); + $data['page_title'] = array( 'eKortti', 'Ystäväkylä eKortti' ); + + if (empty($data['ecard'])) { + $data['ecard'] = new stdClass(); + $data['ecard']->id = $card_id; + $data['ecard']->response = "error"; + $data['ecard']->response_text = "No card found with that id"; + } + + $data['page_classes'][] = 'show_one'; + + $this->load->view('_header', $data); + $this->load->view('show_one', $data); + $this->load->view('_footer', $data); + + } + # code... + } + + public function upload() + { + # code... + } +} + +/* End of file welcome.php */ +/* Location: ./application/controllers/welcome.php */ diff --git a/application/controllers/yllapito.php b/application/controllers/yllapito.php new file mode 100644 index 0000000..0a1848b --- /dev/null +++ b/application/controllers/yllapito.php @@ -0,0 +1,107 @@ +load->model('ecard_model', 'ecard'); + $this->load->model('erkanaauth_model', 'erkana'); + + $this->user = $this->erkana->getUser(); + } + + public function index() + { + if (empty($this->user)) { + redirect("/yllapito/kirjaudu"); + } + + $data = array( + 'page_title' => array( 'Etusivu', 'Ystäväkylä eKortti' ), + 'page_classes' => array( 'frontpage' ), + 'user' => $this->user, + 'messages' => $this->session->flashdata('messages') + ); + + $this->load->view('_header', $data); + $this->load->view('yllapito/dashboard', $data); + $this->load->view('_footer', $data); + } + + public function kirjaudu() + { + // POST + $login = $this->input->post(); + if ($login) { + $user = $this->input->post('username'); + $pass = $this->input->post('password'); + + // Hash the password + $pass = $this->passwordhash($pass); + + $test = array( + 'username' => $user, + 'password' => $pass // Hashed password + ); + $this->erkana->try_login($test); + + if (($user = $this->erkana->getUser())) { + $this->db->update( + 'users', + array( + 'last_login' => date("Y-m-d H:i:s") + ), + "id = ". $user->id + ); + redirect("yllapito"); + } else { + $this->session->set_flashdata( + 'error', + 'Kirjautuminen epäonnistui' + ); + redirect("yllapito/kirjaudu"); + } + } + + // GET + if (! empty($this->user)) { + redirect("yllapito"); + } + + $data = array( + 'page_title' => array( 'Kirjaudu', 'Ystäväkylä eKortti' ), + 'page_classes' => array( 'login' ), + 'user' => $this->user, + 'error' => $this->session->flashdata('error') + ); + + $this->load->view('_header', $data); + $this->load->view('yllapito/login', $data); + $this->load->view('_footer', $data); + } + + + public function logout() + { + $this->erkana->logout(); + redirect("yllapito"); + } + + public function makePassword($password = null) + { + echo $this->passwordhash($password); + } + + + + private function passwordhash($password = null) + { + return hash( + 'ripemd160', + $password . $this->config->item('encryption_key') + ); + } +} diff --git a/application/core/MY_Model.php b/application/core/MY_Model.php new file mode 100644 index 0000000..0120372 --- /dev/null +++ b/application/core/MY_Model.php @@ -0,0 +1,917 @@ + + */ + +class MY_Model extends CI_Model +{ + + /* -------------------------------------------------------------- + * VARIABLES + * ------------------------------------------------------------ */ + + /** + * This model's default database table. Automatically + * guessed by pluralising the model name. + */ + protected $_table; + + /** + * Specify a database group to manually connect this model + * to the specified DB. You can pass either the group name + * as defined in application/config/database.php, or a + * config array of the same format (basically the same thing + * you can pass to $this->load->database()). If left empty, + * the default DB will be used. + */ + protected $_db_group; + + /** + * The database connection object. Will be set to the default + * connection unless $this->_db_group is specified. This allows + * individual models to use different DBs without overwriting + * CI's global $this->db connection. + */ + public $_database; + + /** + * This model's default primary key or unique identifier. + * Used by the get(), update() and delete() functions. + */ + protected $primary_key = 'id'; + + /** + * Support for soft deletes and this model's 'deleted' key + */ + protected $soft_delete = FALSE; + protected $soft_delete_key = 'deleted'; + protected $_temporary_with_deleted = FALSE; + + /** + * The various callbacks available to the model. Each are + * simple lists of method names (methods will be run on $this). + */ + protected $before_create = array(); + protected $after_create = array(); + protected $before_update = array(); + protected $after_update = array(); + protected $before_get = array(); + protected $after_get = array(); + protected $before_delete = array(); + protected $after_delete = array(); + + protected $callback_parameters = array(); + + /** + * Protected, non-modifiable attributes + */ + protected $protected_attributes = array(); + + /** + * Relationship arrays. Use flat strings for defaults or string + * => array to customise the class name and primary key + */ + protected $belongs_to = array(); + protected $has_many = array(); + + protected $_with = array(); + + /** + * An array of validation rules. This needs to be the same format + * as validation rules passed to the Form_validation library. + */ + protected $validate = array(); + + /** + * Optionally skip the validation. Used in conjunction with + * skip_validation() to skip data validation for any future calls. + */ + protected $skip_validation = FALSE; + + /** + * By default we return our results as objects. If we need to override + * this, we can, or, we could use the `as_array()` and `as_object()` scopes. + */ + protected $return_type = 'object'; + protected $_temporary_return_type = NULL; + + /* -------------------------------------------------------------- + * GENERIC METHODS + * ------------------------------------------------------------ */ + + /** + * Initialise the model, tie into the CodeIgniter superobject and + * try our best to guess the table name. + */ + public function __construct() + { + parent::__construct(); + + $this->load->helper('inflector'); + + $this->_set_database(); + $this->_fetch_table(); + + array_unshift($this->before_create, 'protect_attributes'); + array_unshift($this->before_update, 'protect_attributes'); + + $this->_temporary_return_type = $this->return_type; + } + + /* -------------------------------------------------------------- + * CRUD INTERFACE + * ------------------------------------------------------------ */ + + /** + * Fetch a single record based on the primary key. Returns an object. + */ + public function get($primary_value) + { + $this->trigger('before_get'); + + if ($this->soft_delete && $this->_temporary_with_deleted !== TRUE) + { + $this->_database->where($this->soft_delete_key, FALSE); + } + + $row = $this->_database->where($this->primary_key, $primary_value) + ->get($this->_table) + ->{$this->_return_type()}(); + $this->_temporary_return_type = $this->return_type; + + $row = $this->trigger('after_get', $row); + + $this->_with = array(); + return $row; + } + + /** + * Fetch a single record based on an arbitrary WHERE call. Can be + * any valid value to $this->_database->where(). + */ + public function get_by() + { + $where = func_get_args(); + $this->_set_where($where); + + if ($this->soft_delete && $this->_temporary_with_deleted !== TRUE) + { + $this->_database->where($this->soft_delete_key, FALSE); + } + + $this->trigger('before_get'); + + $row = $this->_database->get($this->_table) + ->{$this->_return_type()}(); + $this->_temporary_return_type = $this->return_type; + + $row = $this->trigger('after_get', $row); + + $this->_with = array(); + return $row; + } + + /** + * Fetch an array of records based on an array of primary values. + */ + public function get_many($values) + { + if ($this->soft_delete && $this->_temporary_with_deleted !== TRUE) + { + $this->_database->where($this->soft_delete_key, FALSE); + } + + $this->_database->where_in($this->primary_key, $values); + + return $this->get_all(); + } + + /** + * Fetch an array of records based on an arbitrary WHERE call. + */ + public function get_many_by() + { + $where = func_get_args(); + $this->_set_where($where); + + if ($this->soft_delete && $this->_temporary_with_deleted !== TRUE) + { + $this->_database->where($this->soft_delete_key, FALSE); + } + + return $this->get_all(); + } + + /** + * Fetch all the records in the table. Can be used as a generic call + * to $this->_database->get() with scoped methods. + */ + public function get_all() + { + $this->trigger('before_get'); + + if ($this->soft_delete && $this->_temporary_with_deleted !== TRUE) + { + $this->_database->where($this->soft_delete_key, FALSE); + } + + $result = $this->_database->get($this->_table) + ->{$this->_return_type(1)}(); + $this->_temporary_return_type = $this->return_type; + + foreach ($result as $key => &$row) + { + $row = $this->trigger('after_get', $row, ($key == count($result) - 1)); + } + + $this->_with = array(); + return $result; + } + + /** + * Insert a new row into the table. $data should be an associative array + * of data to be inserted. Returns newly created ID. + */ + public function insert($data, $skip_validation = FALSE) + { + $valid = TRUE; + + if ($skip_validation === FALSE) + { + $data = $this->validate($data); + } + + if ($data !== FALSE) + { + $data = $this->trigger('before_create', $data); + + $this->_database->insert($this->_table, $data); + $insert_id = $this->_database->insert_id(); + + $this->trigger('after_create', $insert_id); + + return $insert_id; + } + else + { + return FALSE; + } + } + + /** + * Insert multiple rows into the table. Returns an array of multiple IDs. + */ + public function insert_many($data, $skip_validation = FALSE) + { + $ids = array(); + + foreach ($data as $key => $row) + { + $ids[] = $this->insert($row, $skip_validation, ($key == count($data) - 1)); + } + + return $ids; + } + + /** + * Updated a record based on the primary value. + */ + public function update($primary_value, $data, $skip_validation = FALSE) + { + $valid = TRUE; + + $data = $this->trigger('before_update', $data); + + if ($skip_validation === FALSE) + { + $data = $this->validate($data); + } + + if ($data !== FALSE) + { + $result = $this->_database->where($this->primary_key, $primary_value) + ->set($data) + ->update($this->_table); + + $this->trigger('after_update', array($data, $result)); + + return $result; + } + else + { + return FALSE; + } + } + + /** + * Update many records, based on an array of primary values. + */ + public function update_many($primary_values, $data, $skip_validation = FALSE) + { + $data = $this->trigger('before_update', $data); + + if ($skip_validation === FALSE) + { + $data = $this->validate($data); + } + + if ($data !== FALSE) + { + $result = $this->_database->where_in($this->primary_key, $primary_values) + ->set($data) + ->update($this->_table); + + $this->trigger('after_update', array($data, $result)); + + return $result; + } + else + { + return FALSE; + } + } + + /** + * Updated a record based on an arbitrary WHERE clause. + */ + public function update_by() + { + $args = func_get_args(); + $data = array_pop($args); + $this->_set_where($args); + + $data = $this->trigger('before_update', $data); + + if ($this->validate($data) !== FALSE) + { + $result = $this->_database->set($data) + ->update($this->_table); + $this->trigger('after_update', array($data, $result)); + + return $result; + } + else + { + return FALSE; + } + } + + /** + * Update all records + */ + public function update_all($data) + { + $data = $this->trigger('before_update', $data); + $result = $this->_database->set($data) + ->update($this->_table); + $this->trigger('after_update', array($data, $result)); + + return $result; + } + + /** + * Delete a row from the table by the primary value + */ + public function delete($id) + { + $this->trigger('before_delete', $id); + + $this->_database->where($this->primary_key, $id); + + if ($this->soft_delete) + { + $result = $this->_database->update($this->_table, array( $this->soft_delete_key => TRUE )); + } + else + { + $result = $this->_database->delete($this->_table); + } + + $this->trigger('after_delete', $result); + + return $result; + } + + /** + * Delete a row from the database table by an arbitrary WHERE clause + */ + public function delete_by() + { + $where = func_get_args(); + $this->_set_where($where); + + $where = $this->trigger('before_delete', $where); + + if ($this->soft_delete) + { + $result = $this->_database->update($this->_table, array( $this->soft_delete_key => TRUE )); + } + else + { + $result = $this->_database->delete($this->_table); + } + + $this->trigger('after_delete', $result); + + return $result; + } + + /** + * Delete many rows from the database table by multiple primary values + */ + public function delete_many($primary_values) + { + $primary_values = $this->trigger('before_delete', $primary_values); + + $this->_database->where_in($this->primary_key, $primary_values); + + if ($this->soft_delete) + { + $result = $this->_database->update($this->_table, array( $this->soft_delete_key => TRUE )); + } + else + { + $result = $this->_database->delete($this->_table); + } + + $this->trigger('after_delete', $result); + + return $result; + } + + + /** + * Truncates the table + */ + public function truncate() + { + $result = $this->_database->truncate($this->_table); + + return $result; + } + + /* -------------------------------------------------------------- + * RELATIONSHIPS + * ------------------------------------------------------------ */ + + public function with($relationship) + { + $this->_with[] = $relationship; + + if (!in_array('relate', $this->after_get)) + { + $this->after_get[] = 'relate'; + } + + return $this; + } + + public function relate($row) + { + foreach ($this->belongs_to as $key => $value) + { + if (is_string($value)) + { + $relationship = $value; + $options = array( 'primary_key' => $value . '_id', 'model' => $value . '_model' ); + } + else + { + $relationship = $key; + $options = $value; + } + + if (in_array($relationship, $this->_with)) + { + $this->load->model($options['model']); + if (is_object($row)) + { + $row->{$relationship} = $this->{$options['model']}->get($row->{$options['primary_key']}); + } + else + { + $row[$relationship] = $this->{$options['model']}->get($row[$options['primary_key']]); + } + } + } + + foreach ($this->has_many as $key => $value) + { + if (is_string($value)) + { + $relationship = $value; + $options = array( 'primary_key' => singular($this->_table) . '_id', 'model' => singular($value) . '_model' ); + } + else + { + $relationship = $key; + $options = $value; + } + + if (in_array($relationship, $this->_with)) + { + $this->load->model($options['model']); + if (is_object($row)) + { + $row->{$relationship} = $this->{$options['model']}->get_many_by($options['primary_key'], $row->{$this->primary_key}); + } + else + { + $row[$relationship] = $this->{$options['model']}->get_many_by($options['primary_key'], $row[$this->primary_key]); + } + } + } + + return $row; + } + + /* -------------------------------------------------------------- + * UTILITY METHODS + * ------------------------------------------------------------ */ + + /** + * Retrieve and generate a form_dropdown friendly array + */ + function dropdown() + { + $args = func_get_args(); + + if(count($args) == 2) + { + list($key, $value) = $args; + } + else + { + $key = $this->primary_key; + $value = $args[0]; + } + + $this->trigger('before_dropdown', array( $key, $value )); + + if ($this->soft_delete && $this->_temporary_with_deleted !== TRUE) + { + $this->_database->where($this->soft_delete_key, FALSE); + } + + $result = $this->_database->select(array($key, $value)) + ->get($this->_table) + ->result(); + + $options = array(); + + foreach ($result as $row) + { + $options[$row->{$key}] = $row->{$value}; + } + + $options = $this->trigger('after_dropdown', $options); + + return $options; + } + + /** + * Fetch a count of rows based on an arbitrary WHERE call. + */ + public function count_by() + { + $where = func_get_args(); + $this->_set_where($where); + + return $this->_database->count_all_results($this->_table); + } + + /** + * Fetch a total count of rows, disregarding any previous conditions + */ + public function count_all() + { + return $this->_database->count_all($this->_table); + } + + /** + * Tell the class to skip the insert validation + */ + public function skip_validation() + { + $this->skip_validation = TRUE; + return $this; + } + + /** + * Get the skip validation status + */ + public function get_skip_validation() + { + return $this->skip_validation; + } + + /** + * Return the next auto increment of the table. Only tested on MySQL. + */ + public function get_next_id() + { + return (int) $this->_database->select('AUTO_INCREMENT') + ->from('information_schema.TABLES') + ->where('TABLE_NAME', $this->_table) + ->where('TABLE_SCHEMA', $this->_database->database)->get()->row()->AUTO_INCREMENT; + } + + /** + * Getter for the table name + */ + public function table() + { + return $this->_table; + } + + /* -------------------------------------------------------------- + * GLOBAL SCOPES + * ------------------------------------------------------------ */ + + /** + * Return the next call as an array rather than an object + */ + public function as_array() + { + $this->_temporary_return_type = 'array'; + return $this; + } + + /** + * Return the next call as an object rather than an array + */ + public function as_object() + { + $this->_temporary_return_type = 'object'; + return $this; + } + + /** + * Don't care about soft deleted rows on the next call + */ + public function with_deleted() + { + $this->_temporary_with_deleted = TRUE; + return $this; + } + + /* -------------------------------------------------------------- + * OBSERVERS + * ------------------------------------------------------------ */ + + /** + * MySQL DATETIME created_at and updated_at + */ + public function created_at($row) + { + if (is_object($row)) + { + $row->created_at = date('Y-m-d H:i:s'); + } + else + { + $row['created_at'] = date('Y-m-d H:i:s'); + } + + return $row; + } + + public function updated_at($row) + { + if (is_object($row)) + { + $row->updated_at = date('Y-m-d H:i:s'); + } + else + { + $row['updated_at'] = date('Y-m-d H:i:s'); + } + + return $row; + } + + /** + * Serialises data for you automatically, allowing you to pass + * through objects and let it handle the serialisation in the background + */ + public function serialize($row) + { + foreach ($this->callback_parameters as $column) + { + $row[$column] = serialize($row[$column]); + } + + return $row; + } + + public function unserialize($row) + { + foreach ($this->callback_parameters as $column) + { + if (is_array($row)) + { + $row[$column] = unserialize($row[$column]); + } + else + { + $row->$column = unserialize($row->$column); + } + } + + return $row; + } + + /** + * Protect attributes by removing them from $row array + */ + public function protect_attributes($row) + { + foreach ($this->protected_attributes as $attr) + { + if (is_object($row)) + { + unset($row->$attr); + } + else + { + unset($row[$attr]); + } + } + + return $row; + } + + /* -------------------------------------------------------------- + * QUERY BUILDER DIRECT ACCESS METHODS + * ------------------------------------------------------------ */ + + /** + * A wrapper to $this->_database->order_by() + */ + public function order_by($criteria, $order = 'ASC') + { + if ( is_array($criteria) ) + { + foreach ($criteria as $key => $value) + { + $this->_database->order_by($key, $value); + } + } + else + { + $this->_database->order_by($criteria, $order); + } + return $this; + } + + /** + * A wrapper to $this->_database->limit() + */ + public function limit($limit, $offset = 0) + { + $this->_database->limit($limit, $offset); + return $this; + } + + /* -------------------------------------------------------------- + * INTERNAL METHODS + * ------------------------------------------------------------ */ + + /** + * Trigger an event and call its observers. Pass through the event name + * (which looks for an instance variable $this->event_name), an array of + * parameters to pass through and an optional 'last in interation' boolean + */ + public function trigger($event, $data = FALSE, $last = TRUE) + { + if (isset($this->$event) && is_array($this->$event)) + { + foreach ($this->$event as $method) + { + if (strpos($method, '(')) + { + preg_match('/([a-zA-Z0-9\_\-]+)(\(([a-zA-Z0-9\_\-\., ]+)\))?/', $method, $matches); + + $method = $matches[1]; + $this->callback_parameters = explode(',', $matches[3]); + } + + $data = call_user_func_array(array($this, $method), array($data, $last)); + } + } + + return $data; + } + + /** + * Run validation on the passed data + */ + public function validate($data) + { + if($this->skip_validation) + { + return $data; + } + + if(!empty($this->validate)) + { + foreach($data as $key => $val) + { + $_POST[$key] = $val; + } + + $this->load->library('form_validation'); + + if(is_array($this->validate)) + { + $this->form_validation->set_rules($this->validate); + + if ($this->form_validation->run() === TRUE) + { + return $data; + } + else + { + return FALSE; + } + } + else + { + if ($this->form_validation->run($this->validate) === TRUE) + { + return $data; + } + else + { + return FALSE; + } + } + } + else + { + return $data; + } + } + + /** + * Guess the table name by pluralising the model name + */ + private function _fetch_table() + { + if ($this->_table == NULL) + { + $this->_table = plural(preg_replace('/(_m|_model)?$/', '', strtolower(get_class($this)))); + } + } + + /** + * Establish the database connection. + */ + private function _set_database() + { + // Was a DB group specified by the user? + if ($this->_db_group !== NULL) + { + $this->_database = $this->load->database($this->_db_group, TRUE, TRUE); + } + // No DB group specified, use the default connection. + else + { + // Has the default connection been loaded yet? + if ( ! isset($this->db) OR ! is_object($this->db)) + { + $this->load->database('', FALSE, TRUE); + } + + $this->_database = $this->db; + } + } + + /** + * Set WHERE parameters, cleverly + */ + protected function _set_where($params) + { + if (count($params) == 1) + { + $this->_database->where($params[0]); + } + else + { + $this->_database->where($params[0], $params[1]); + } + } + + /** + * Return the method name for the current return type + */ + protected function _return_type($multi = FALSE) + { + $method = ($multi) ? 'result' : 'row'; + return $this->_temporary_return_type == 'array' ? $method . '_array' : $method; + } +} \ No newline at end of file diff --git a/application/core/index.html b/application/core/index.html new file mode 100644 index 0000000..c942a79 --- /dev/null +++ b/application/core/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/application/errors/error_404.php b/application/errors/error_404.php new file mode 100644 index 0000000..fc90c5b --- /dev/null +++ b/application/errors/error_404.php @@ -0,0 +1,30 @@ + $ci->erkana->getUser() +); + +$ci->load->view("_header", $data); + +if (!isset($heading)) { + $heading = "Otsikko"; +} +if (!isset($message)) { + $message = "Viesti"; +} + +?> + +
+
+
+ +

+ +
+
+
+ +load->view("_footer", $data); \ No newline at end of file diff --git a/application/errors/error_db.php b/application/errors/error_db.php new file mode 100644 index 0000000..b396cda --- /dev/null +++ b/application/errors/error_db.php @@ -0,0 +1,62 @@ + + + +Database Error + + + +
+

+ +
+ + \ No newline at end of file diff --git a/application/errors/error_general.php b/application/errors/error_general.php new file mode 100644 index 0000000..fd63ce2 --- /dev/null +++ b/application/errors/error_general.php @@ -0,0 +1,62 @@ + + + +Error + + + +
+

+ +
+ + \ No newline at end of file diff --git a/application/errors/error_php.php b/application/errors/error_php.php new file mode 100644 index 0000000..f085c20 --- /dev/null +++ b/application/errors/error_php.php @@ -0,0 +1,10 @@ +
+ +

A PHP Error was encountered

+ +

Severity:

+

Message:

+

Filename:

+

Line Number:

+ +
\ No newline at end of file diff --git a/application/errors/index.html b/application/errors/index.html new file mode 100644 index 0000000..c942a79 --- /dev/null +++ b/application/errors/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/application/helpers/index.html b/application/helpers/index.html new file mode 100644 index 0000000..c942a79 --- /dev/null +++ b/application/helpers/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/application/hooks/index.html b/application/hooks/index.html new file mode 100644 index 0000000..c942a79 --- /dev/null +++ b/application/hooks/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/application/index.html b/application/index.html new file mode 100644 index 0000000..c942a79 --- /dev/null +++ b/application/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/application/language/english/index.html b/application/language/english/index.html new file mode 100644 index 0000000..c942a79 --- /dev/null +++ b/application/language/english/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/application/libraries/index.html b/application/libraries/index.html new file mode 100644 index 0000000..c942a79 --- /dev/null +++ b/application/libraries/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/application/logs/index.html b/application/logs/index.html new file mode 100644 index 0000000..c942a79 --- /dev/null +++ b/application/logs/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/application/models/ecard_model.php b/application/models/ecard_model.php new file mode 100644 index 0000000..573ae9d --- /dev/null +++ b/application/models/ecard_model.php @@ -0,0 +1,20 @@ +$email, + * 'password'=>dohash($password) + * ); + * $this->erkanaauth->try_login($conditions)); + * + * @access public + * @param array login conditions + * @return mixed boolean:false or object with user record + */ + function try_login($condition = array()) + { + $query = $this->db->get_where($this->db_table, $condition, 1, 0); + + if ($query->num_rows != 1) return FALSE; + + $row = $query->row(); + $this->session->set_userdata(array('user_id'=>$row->id)); + + return $row; + } + + /** + * Multipurpose: Check logged state and have the current user info + * + * Copied from http://codeigniter.com/forums/viewthread/63423/P30/: + * getUser() now returns a user's record and can be + * used to determine login status as well as retrieving + * user information. Right now it doesn’t support roles + * (so if you use that system, add in a JOIN to the method below) + * but it will when I actually release this version + * + * @access public + * @param int the user id, defaults to session user_id + * @return mixed boolean:false or object with user record + */ + function getUser($id = FALSE) + { + if ($id == FALSE) $id = $this->session->userdata('user_id'); + + if ($id == FALSE) return FALSE; + + $condition = array(($this->db_table .'.' .$this->db_userid) =>$id); + + $query = $this->db->get_where($this->db_table, $condition, 1, 0); + + $row = ($query->num_rows() == 1) ? $query->row() : FALSE; + + return $row; + } + + /** + * Logs a user out + * + * Example: $this->erkanaauth->logout() + * + * @access public + * @return void + */ + function logout() + { + $this->session->set_userdata(array('user_id'=>FALSE)); + } + } + + ?> \ No newline at end of file diff --git a/application/models/index.html b/application/models/index.html new file mode 100644 index 0000000..c942a79 --- /dev/null +++ b/application/models/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/application/third_party/index.html b/application/third_party/index.html new file mode 100644 index 0000000..c942a79 --- /dev/null +++ b/application/third_party/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/application/views/_footer.php b/application/views/_footer.php new file mode 100644 index 0000000..df9436b --- /dev/null +++ b/application/views/_footer.php @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + diff --git a/application/views/_header.php b/application/views/_header.php new file mode 100644 index 0000000..ed030e5 --- /dev/null +++ b/application/views/_header.php @@ -0,0 +1,139 @@ + + + + + + + + <?php echo implode(" » ", $page_title); ?> + + + + + + +
+ + +
+ + + + diff --git a/application/views/_partial_cardlist.php b/application/views/_partial_cardlist.php new file mode 100644 index 0000000..c4e9050 --- /dev/null +++ b/application/views/_partial_cardlist.php @@ -0,0 +1,33 @@ + +
+
+
    +'; + + ?> +
  • + + placeholder+image + + +
  • + + +
+
+
+ diff --git a/application/views/index.html b/application/views/index.html new file mode 100644 index 0000000..c942a79 --- /dev/null +++ b/application/views/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/application/views/info.php b/application/views/info.php new file mode 100644 index 0000000..ba3bfb0 --- /dev/null +++ b/application/views/info.php @@ -0,0 +1,58 @@ + +
+
+
+ +

Tietoa eKortista

+ +

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod + tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, + quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse + cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non + proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+ + +

Yhteystiedot

+ +

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod + tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, + quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse + cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non + proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+ +

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod + tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, + quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse + cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non + proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+ +

Rekisteriseloste

+ +

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod + tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, + quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse + cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non + proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+ +

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod + tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, + quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse + cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non + proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+ +

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod + tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, + quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse + cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non + proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+ + +
+
+
\ No newline at end of file diff --git a/application/views/new.php b/application/views/new.php new file mode 100644 index 0000000..c6b893e --- /dev/null +++ b/application/views/new.php @@ -0,0 +1,197 @@ + +
+
+
+ +

Luo uusi eKortti!

+ +

+ Suunnittele oma eKorttisi ja lähetä se ystävällesi! + Samalla voit osallistua arvontaan josta voit voittaa huikean hienoja palkintoja. +

+ + +
+
+
+ +
+
+
+
+ +

Tiedot

+ + +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ + +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ + + +
+
+ +
+
+ +
+
+ + + +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+ + + + + + + + + + + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
 
+
+ +
+
+ + + + +
+ + + + +
+
+ +
+
+

Esikatselu

+
+
Moikka!
+
Terveisiä täältä internetistä!
+ +
+
+
+

Tiedot kerätään vain postikorttien lähettämiseen ja halutessasi arvontaan osallistumista varten.

+
+
+ + +
diff --git a/application/views/show_all.php b/application/views/show_all.php new file mode 100644 index 0000000..17ad9a6 --- /dev/null +++ b/application/views/show_all.php @@ -0,0 +1,26 @@ + + +
+ +
+
+

Tässä kaikki Ystäväkylän sähköpostikortit!

+

Postikortit ovat järjestetty luomisjärjestykseen, uusimmat ensimmäiseksi. Tällä hetkellä postikortteja on kaikkiaan kappaletta joista näytetään julkisesti kappaletta. Yksityisiä kortteja on kappaletta.

+
+
+ +load->view('_partial_cardlist.php', array('amount' => $public)); ?> + + +
+ diff --git a/application/views/show_one.php b/application/views/show_one.php new file mode 100644 index 0000000..bfc5b0b --- /dev/null +++ b/application/views/show_one.php @@ -0,0 +1,22 @@ + +
+
+
+response == "200" ) { +?> +

Postikortti #id;?>

+ +

Tunnuksella ei löytynyt ainuttakaan korttia

+

Sähköpostikortti on joko poistettu järjestelmästä, tai sitten seurasit rikkinäistä linkkiä.

+

+ +
+ +
+
+
\ No newline at end of file diff --git a/application/views/welcome_message.php b/application/views/welcome_message.php new file mode 100644 index 0000000..c89a96e --- /dev/null +++ b/application/views/welcome_message.php @@ -0,0 +1,32 @@ + + +
+ +
+
+

Tervetuloa Ystäväkylän ePostikorttiin!

+ +

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod + tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, + quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse + cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non + proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+ + +

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod + tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, + quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse + cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non + proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+ +
+
+ +load->view('_partial_cardlist.php', array('amount' => 5)); ?> + + + +
+ diff --git a/application/views/yllapito/dashboard.php b/application/views/yllapito/dashboard.php new file mode 100644 index 0000000..9a8ba30 --- /dev/null +++ b/application/views/yllapito/dashboard.php @@ -0,0 +1,15 @@ + +
+
+
+
+ +
+
+
diff --git a/application/views/yllapito/login.php b/application/views/yllapito/login.php new file mode 100644 index 0000000..091d4ee --- /dev/null +++ b/application/views/yllapito/login.php @@ -0,0 +1,83 @@ + + +
+
+
+ +
+
+ +

Kirjaudu sisään

+ + +
+ +
+ + +
+
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
 
+
+ +
+
+
+ + +
+
+ + +
+
+
+ + + diff --git a/assets/.htaccess b/assets/.htaccess new file mode 100644 index 0000000..d61f4cb --- /dev/null +++ b/assets/.htaccess @@ -0,0 +1 @@ +ExpiresDefault "access plus 1 year" \ No newline at end of file diff --git a/assets/css/foundation.min.css b/assets/css/foundation.min.css new file mode 100644 index 0000000..0bedfea --- /dev/null +++ b/assets/css/foundation.min.css @@ -0,0 +1 @@ +*,*:before,*:after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}html,body{font-size:100%}body{background:#fff;color:#222;padding:0;margin:0;font-family:"Helvetica Neue","Helvetica",Helvetica,Arial,sans-serif;font-weight:normal;font-style:normal;line-height:1;position:relative}a:focus{outline:none}img,object,embed{max-width:100%;height:auto}object,embed{height:100%}img{-ms-interpolation-mode:bicubic}#map_canvas img,#map_canvas embed,#map_canvas object,.map_canvas img,.map_canvas embed,.map_canvas object{max-width:none !important}.left{float:left !important}.right{float:right !important}.text-left{text-align:left !important}.text-right{text-align:right !important}.text-center{text-align:center !important}.text-justify{text-align:justify !important}.hide{display:none}.antialiased{-webkit-font-smoothing:antialiased}img{display:inline-block;vertical-align:middle}textarea{height:auto;min-height:50px}select{width:100%}.row{width:100%;margin-left:auto;margin-right:auto;margin-top:0;margin-bottom:0;max-width:62.5em;*zoom:1}.row:before,.row:after{content:" ";display:table}.row:after{clear:both}.row.collapse .column,.row.collapse .columns{position:relative;padding-left:0;padding-right:0;float:left}.row .row{width:auto;margin-left:-0.9375em;margin-right:-0.9375em;margin-top:0;margin-bottom:0;max-width:none;*zoom:1}.row .row:before,.row .row:after{content:" ";display:table}.row .row:after{clear:both}.row .row.collapse{width:auto;margin:0;max-width:none;*zoom:1}.row .row.collapse:before,.row .row.collapse:after{content:" ";display:table}.row .row.collapse:after{clear:both}.column,.columns{position:relative;padding-left:0.9375em;padding-right:0.9375em;width:100%;float:left}@media only screen{.column,.columns{position:relative;padding-left:0.9375em;padding-right:0.9375em;float:left}.small-1{position:relative;width:8.33333%}.small-2{position:relative;width:16.66667%}.small-3{position:relative;width:25%}.small-4{position:relative;width:33.33333%}.small-5{position:relative;width:41.66667%}.small-6{position:relative;width:50%}.small-7{position:relative;width:58.33333%}.small-8{position:relative;width:66.66667%}.small-9{position:relative;width:75%}.small-10{position:relative;width:83.33333%}.small-11{position:relative;width:91.66667%}.small-12{position:relative;width:100%}.small-offset-1{position:relative;margin-left:8.33333%}.small-offset-2{position:relative;margin-left:16.66667%}.small-offset-3{position:relative;margin-left:25%}.small-offset-4{position:relative;margin-left:33.33333%}.small-offset-5{position:relative;margin-left:41.66667%}.small-offset-6{position:relative;margin-left:50%}.small-offset-7{position:relative;margin-left:58.33333%}.small-offset-8{position:relative;margin-left:66.66667%}.small-offset-9{position:relative;margin-left:75%}.small-offset-10{position:relative;margin-left:83.33333%}[class*="column"]+[class*="column"]:last-child{float:right}[class*="column"]+[class*="column"].end{float:left}.column.small-centered,.columns.small-centered{position:relative;margin-left:auto;margin-right:auto;float:none !important}}@media only screen and (min-width: 48em){.large-1{position:relative;width:8.33333%}.large-2{position:relative;width:16.66667%}.large-3{position:relative;width:25%}.large-4{position:relative;width:33.33333%}.large-5{position:relative;width:41.66667%}.large-6{position:relative;width:50%}.large-7{position:relative;width:58.33333%}.large-8{position:relative;width:66.66667%}.large-9{position:relative;width:75%}.large-10{position:relative;width:83.33333%}.large-11{position:relative;width:91.66667%}.large-12{position:relative;width:100%}.row .large-offset-1{position:relative;margin-left:8.33333%}.row .large-offset-2{position:relative;margin-left:16.66667%}.row .large-offset-3{position:relative;margin-left:25%}.row .large-offset-4{position:relative;margin-left:33.33333%}.row .large-offset-5{position:relative;margin-left:41.66667%}.row .large-offset-6{position:relative;margin-left:50%}.row .large-offset-7{position:relative;margin-left:58.33333%}.row .large-offset-8{position:relative;margin-left:66.66667%}.row .large-offset-9{position:relative;margin-left:75%}.row .large-offset-10{position:relative;margin-left:83.33333%}.row .large-offset-11{position:relative;margin-left:91.66667%}.push-1{position:relative;left:8.33333%;right:auto}.pull-1{position:relative;right:8.33333%;left:auto}.push-2{position:relative;left:16.66667%;right:auto}.pull-2{position:relative;right:16.66667%;left:auto}.push-3{position:relative;left:25%;right:auto}.pull-3{position:relative;right:25%;left:auto}.push-4{position:relative;left:33.33333%;right:auto}.pull-4{position:relative;right:33.33333%;left:auto}.push-5{position:relative;left:41.66667%;right:auto}.pull-5{position:relative;right:41.66667%;left:auto}.push-6{position:relative;left:50%;right:auto}.pull-6{position:relative;right:50%;left:auto}.push-7{position:relative;left:58.33333%;right:auto}.pull-7{position:relative;right:58.33333%;left:auto}.push-8{position:relative;left:66.66667%;right:auto}.pull-8{position:relative;right:66.66667%;left:auto}.push-9{position:relative;left:75%;right:auto}.pull-9{position:relative;right:75%;left:auto}.push-10{position:relative;left:83.33333%;right:auto}.pull-10{position:relative;right:83.33333%;left:auto}.push-11{position:relative;left:91.66667%;right:auto}.pull-11{position:relative;right:91.66667%;left:auto}.small-push-1{left:inherit}.small-pull-1{right:inherit}.small-push-2{left:inherit}.small-pull-2{right:inherit}.small-push-3{left:inherit}.small-pull-3{right:inherit}.small-push-4{left:inherit}.small-pull-4{right:inherit}.small-push-5{left:inherit}.small-pull-5{right:inherit}.small-push-6{left:inherit}.small-pull-6{right:inherit}.small-push-7{left:inherit}.small-pull-7{right:inherit}.small-push-8{left:inherit}.small-pull-8{right:inherit}.small-push-9{left:inherit}.small-pull-9{right:inherit}.small-push-10{left:inherit}.small-pull-10{right:inherit}.small-push-11{left:inherit}.small-pull-11{right:inherit}.column.large-centered,.columns.large-centered{position:relative;margin-left:auto;margin-right:auto;float:none !important}.column.large-uncentered,.columns.large-uncentered{margin-left:0;margin-right:0;float:none}}.show-for-small,.show-for-medium-down,.show-for-large-down{display:inherit !important}.show-for-medium,.show-for-medium-up,.show-for-large,.show-for-large-up,.show-for-xlarge{display:none !important}.hide-for-medium,.hide-for-medium-up,.hide-for-large,.hide-for-large-up,.hide-for-xlarge{display:inherit !important}.hide-for-small,.hide-for-medium-down,.hide-for-large-down{display:none !important}table.show-for-small,table.show-for-medium-down,table.show-for-large-down,table.hide-for-medium,table.hide-for-medium-up,table.hide-for-large,table.hide-for-large-up,table.hide-for-xlarge{display:table}thead.show-for-small,thead.show-for-medium-down,thead.show-for-large-down,thead.hide-for-medium,thead.hide-for-medium-up,thead.hide-for-large,thead.hide-for-large-up,thead.hide-for-xlarge{display:table-header-group !important}tbody.show-for-small,tbody.show-for-medium-down,tbody.show-for-large-down,tbody.hide-for-medium,tbody.hide-for-medium-up,tbody.hide-for-large,tbody.hide-for-large-up,tbody.hide-for-xlarge{display:table-row-group !important}tr.show-for-small,tr.show-for-medium-down,tr.show-for-large-down,tr.hide-for-medium,tr.hide-for-medium-up,tr.hide-for-large,tr.hide-for-large-up,tr.hide-for-xlarge{display:table-row !important}td.show-for-small,td.show-for-medium-down,td.show-for-large-down,td.hide-for-medium,td.hide-for-medium-up,td.hide-for-large,td.hide-for-large-up,td.hide-for-xlarge,th.show-for-small,th.show-for-medium-down,th.show-for-large-down,th.hide-for-medium,th.hide-for-medium-up,th.hide-for-large,th.hide-for-large-up,th.hide-for-xlarge{display:table-cell !important}@media only screen and (min-width: 48em){.show-for-medium,.show-for-medium-up{display:inherit !important}.show-for-small{display:none !important}.hide-for-small{display:inherit !important}.hide-for-medium,.hide-for-medium-up{display:none !important}table.show-for-medium,table.show-for-medium-up,table.hide-for-small{display:table}thead.show-for-medium,thead.show-for-medium-up,thead.hide-for-small{display:table-header-group !important}tbody.show-for-medium,tbody.show-for-medium-up,tbody.hide-for-small{display:table-row-group !important}tr.show-for-medium,tr.show-for-medium-up,tr.hide-for-small{display:table-row !important}td.show-for-medium,td.show-for-medium-up,td.hide-for-small,th.show-for-medium,th.show-for-medium-up,th.hide-for-small{display:table-cell !important}}@media only screen and (min-width: 80em){.show-for-large,.show-for-large-up{display:inherit !important}.show-for-medium,.show-for-medium-down{display:none !important}.hide-for-medium,.hide-for-medium-down{display:inherit !important}.hide-for-large,.hide-for-large-up{display:none !important}table.show-for-large,table.show-for-large-up,table.hide-for-medium,table.hide-for-medium-down{display:table}thead.show-for-large,thead.show-for-large-up,thead.hide-for-medium,thead.hide-for-medium-down{display:table-header-group !important}tbody.show-for-large,tbody.show-for-large-up,tbody.hide-for-medium,tbody.hide-for-medium-down{display:table-row-group !important}tr.show-for-large,tr.show-for-large-up,tr.hide-for-medium,tr.hide-for-medium-down{display:table-row !important}td.show-for-large,td.show-for-large-up,td.hide-for-medium,td.hide-for-medium-down,th.show-for-large,th.show-for-large-up,th.hide-for-medium,th.hide-for-medium-down{display:table-cell !important}}@media only screen and (min-width: 90em){.show-for-xlarge{display:inherit !important}.show-for-large,.show-for-large-down{display:none !important}.hide-for-large,.hide-for-large-down{display:inherit !important}.hide-for-xlarge{display:none !important}table.show-for-xlarge,table.hide-for-large,table.hide-for-large-down{display:table}thead.show-for-xlarge,thead.hide-for-large,thead.hide-for-large-down{display:table-header-group !important}tbody.show-for-xlarge,tbody.hide-for-large,tbody.hide-for-large-down{display:table-row-group !important}tr.show-for-xlarge,tr.hide-for-large,tr.hide-for-large-down{display:table-row !important}td.show-for-xlarge,td.hide-for-large,td.hide-for-large-down,th.show-for-xlarge,th.hide-for-large,th.hide-for-large-down{display:table-cell !important}}.show-for-landscape,.hide-for-portrait{display:inherit !important}.hide-for-landscape,.show-for-portrait{display:none !important}table.hide-for-landscape,table.show-for-portrait{display:table}thead.hide-for-landscape,thead.show-for-portrait{display:table-header-group !important}tbody.hide-for-landscape,tbody.show-for-portrait{display:table-row-group !important}tr.hide-for-landscape,tr.show-for-portrait{display:table-row !important}td.hide-for-landscape,td.show-for-portrait,th.hide-for-landscape,th.show-for-portrait{display:table-cell !important}@media only screen and (orientation: landscape){.show-for-landscape,.hide-for-portrait{display:inherit !important}.hide-for-landscape,.show-for-portrait{display:none !important}table.show-for-landscape,table.hide-for-portrait{display:table}thead.show-for-landscape,thead.hide-for-portrait{display:table-header-group !important}tbody.show-for-landscape,tbody.hide-for-portrait{display:table-row-group !important}tr.show-for-landscape,tr.hide-for-portrait{display:table-row !important}td.show-for-landscape,td.hide-for-portrait,th.show-for-landscape,th.hide-for-portrait{display:table-cell !important}}@media only screen and (orientation: portrait){.show-for-portrait,.hide-for-landscape{display:inherit !important}.hide-for-portrait,.show-for-landscape{display:none !important}table.show-for-portrait,table.hide-for-landscape{display:table}thead.show-for-portrait,thead.hide-for-landscape{display:table-header-group !important}tbody.show-for-portrait,tbody.hide-for-landscape{display:table-row-group !important}tr.show-for-portrait,tr.hide-for-landscape{display:table-row !important}td.show-for-portrait,td.hide-for-landscape,th.show-for-portrait,th.hide-for-landscape{display:table-cell !important}}.show-for-touch{display:none !important}.hide-for-touch{display:inherit !important}.touch .show-for-touch{display:inherit !important}.touch .hide-for-touch{display:none !important}table.hide-for-touch{display:table}.touch table.show-for-touch{display:table}thead.hide-for-touch{display:table-header-group !important}.touch thead.show-for-touch{display:table-header-group !important}tbody.hide-for-touch{display:table-row-group !important}.touch tbody.show-for-touch{display:table-row-group !important}tr.hide-for-touch{display:table-row !important}.touch tr.show-for-touch{display:table-row !important}td.hide-for-touch{display:table-cell !important}.touch td.show-for-touch{display:table-cell !important}th.hide-for-touch{display:table-cell !important}.touch th.show-for-touch{display:table-cell !important}@media only screen{[class*="block-grid-"]{display:block;padding:0;margin:0 -10px;*zoom:1}[class*="block-grid-"]:before,[class*="block-grid-"]:after{content:" ";display:table}[class*="block-grid-"]:after{clear:both}[class*="block-grid-"]>li{display:inline;height:auto;float:left;padding:0 10px 10px}.small-block-grid-1>li{width:100%;padding:0 10px 10px}.small-block-grid-1>li:nth-of-type(n){clear:none}.small-block-grid-1>li:nth-of-type(1n+1){clear:both}.small-block-grid-2>li{width:50%;padding:0 10px 10px}.small-block-grid-2>li:nth-of-type(n){clear:none}.small-block-grid-2>li:nth-of-type(2n+1){clear:both}.small-block-grid-3>li{width:33.33333%;padding:0 10px 10px}.small-block-grid-3>li:nth-of-type(n){clear:none}.small-block-grid-3>li:nth-of-type(3n+1){clear:both}.small-block-grid-4>li{width:25%;padding:0 10px 10px}.small-block-grid-4>li:nth-of-type(n){clear:none}.small-block-grid-4>li:nth-of-type(4n+1){clear:both}.small-block-grid-5>li{width:20%;padding:0 10px 10px}.small-block-grid-5>li:nth-of-type(n){clear:none}.small-block-grid-5>li:nth-of-type(5n+1){clear:both}.small-block-grid-6>li{width:16.66667%;padding:0 10px 10px}.small-block-grid-6>li:nth-of-type(n){clear:none}.small-block-grid-6>li:nth-of-type(6n+1){clear:both}.small-block-grid-7>li{width:14.28571%;padding:0 10px 10px}.small-block-grid-7>li:nth-of-type(n){clear:none}.small-block-grid-7>li:nth-of-type(7n+1){clear:both}.small-block-grid-8>li{width:12.5%;padding:0 10px 10px}.small-block-grid-8>li:nth-of-type(n){clear:none}.small-block-grid-8>li:nth-of-type(8n+1){clear:both}.small-block-grid-9>li{width:11.11111%;padding:0 10px 10px}.small-block-grid-9>li:nth-of-type(n){clear:none}.small-block-grid-9>li:nth-of-type(9n+1){clear:both}.small-block-grid-10>li{width:10%;padding:0 10px 10px}.small-block-grid-10>li:nth-of-type(n){clear:none}.small-block-grid-10>li:nth-of-type(10n+1){clear:both}.small-block-grid-11>li{width:9.09091%;padding:0 10px 10px}.small-block-grid-11>li:nth-of-type(n){clear:none}.small-block-grid-11>li:nth-of-type(11n+1){clear:both}.small-block-grid-12>li{width:8.33333%;padding:0 10px 10px}.small-block-grid-12>li:nth-of-type(n){clear:none}.small-block-grid-12>li:nth-of-type(12n+1){clear:both}}@media only screen and (min-width: 48em){.small-block-grid-1>li:nth-of-type(1n+1){clear:none}.small-block-grid-2>li:nth-of-type(2n+1){clear:none}.small-block-grid-3>li:nth-of-type(3n+1){clear:none}.small-block-grid-4>li:nth-of-type(4n+1){clear:none}.small-block-grid-5>li:nth-of-type(5n+1){clear:none}.small-block-grid-6>li:nth-of-type(6n+1){clear:none}.small-block-grid-7>li:nth-of-type(7n+1){clear:none}.small-block-grid-8>li:nth-of-type(8n+1){clear:none}.small-block-grid-9>li:nth-of-type(9n+1){clear:none}.small-block-grid-10>li:nth-of-type(10n+1){clear:none}.small-block-grid-11>li:nth-of-type(11n+1){clear:none}.small-block-grid-12>li:nth-of-type(12n+1){clear:none}.large-block-grid-1>li{width:100%;padding:0 10px 10px}.large-block-grid-1>li:nth-of-type(n){clear:none}.large-block-grid-1>li:nth-of-type(1n+1){clear:both}.large-block-grid-2>li{width:50%;padding:0 10px 10px}.large-block-grid-2>li:nth-of-type(n){clear:none}.large-block-grid-2>li:nth-of-type(2n+1){clear:both}.large-block-grid-3>li{width:33.33333%;padding:0 10px 10px}.large-block-grid-3>li:nth-of-type(n){clear:none}.large-block-grid-3>li:nth-of-type(3n+1){clear:both}.large-block-grid-4>li{width:25%;padding:0 10px 10px}.large-block-grid-4>li:nth-of-type(n){clear:none}.large-block-grid-4>li:nth-of-type(4n+1){clear:both}.large-block-grid-5>li{width:20%;padding:0 10px 10px}.large-block-grid-5>li:nth-of-type(n){clear:none}.large-block-grid-5>li:nth-of-type(5n+1){clear:both}.large-block-grid-6>li{width:16.66667%;padding:0 10px 10px}.large-block-grid-6>li:nth-of-type(n){clear:none}.large-block-grid-6>li:nth-of-type(6n+1){clear:both}.large-block-grid-7>li{width:14.28571%;padding:0 10px 10px}.large-block-grid-7>li:nth-of-type(n){clear:none}.large-block-grid-7>li:nth-of-type(7n+1){clear:both}.large-block-grid-8>li{width:12.5%;padding:0 10px 10px}.large-block-grid-8>li:nth-of-type(n){clear:none}.large-block-grid-8>li:nth-of-type(8n+1){clear:both}.large-block-grid-9>li{width:11.11111%;padding:0 10px 10px}.large-block-grid-9>li:nth-of-type(n){clear:none}.large-block-grid-9>li:nth-of-type(9n+1){clear:both}.large-block-grid-10>li{width:10%;padding:0 10px 10px}.large-block-grid-10>li:nth-of-type(n){clear:none}.large-block-grid-10>li:nth-of-type(10n+1){clear:both}.large-block-grid-11>li{width:9.09091%;padding:0 10px 10px}.large-block-grid-11>li:nth-of-type(n){clear:none}.large-block-grid-11>li:nth-of-type(11n+1){clear:both}.large-block-grid-12>li{width:8.33333%;padding:0 10px 10px}.large-block-grid-12>li:nth-of-type(n){clear:none}.large-block-grid-12>li:nth-of-type(12n+1){clear:both}}p.lead{font-size:1.21875em;line-height:1.6}.subheader{line-height:1.4;color:#6f6f6f;font-weight:300;margin-top:0.2em;margin-bottom:0.5em}div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0;direction:ltr}a{color:#2ba6cb;text-decoration:none;line-height:inherit}a:hover,a:focus{color:#2795b6}a img{border:none}p{font-family:inherit;font-weight:normal;font-size:1em;line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}p aside{font-size:0.875em;line-height:1.35;font-style:italic}h1,h2,h3,h4,h5,h6{font-family:"Helvetica Neue","Helvetica",Helvetica,Arial,sans-serif;font-weight:bold;font-style:normal;color:#222;text-rendering:optimizeLegibility;margin-top:0.2em;margin-bottom:0.5em;line-height:1.2125em}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-size:60%;color:#6f6f6f;line-height:0}h1{font-size:2.125em}h2{font-size:1.6875em}h3{font-size:1.375em}h4{font-size:1.125em}h5{font-size:1.125em}h6{font-size:1em}hr{border:solid #ddd;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em;height:0}em,i{font-style:italic;line-height:inherit}strong,b{font-weight:bold;line-height:inherit}small{font-size:60%;line-height:inherit}code{font-family:Consolas,"Liberation Mono",Courier,monospace;font-weight:bold;color:#7f0a0c}ul,ol,dl{font-size:1em;line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0;font-size:1em}ul.square li ul,ul.circle li ul,ul.disc li ul{list-style:inherit}ul.square{list-style-type:square}ul.circle{list-style-type:circle}ul.disc{list-style-type:disc}ul.no-bullet{list-style:none}ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}dl dt{margin-bottom:0.3em;font-weight:bold}dl dd{margin-bottom:0.75em}abbr,acronym{text-transform:uppercase;font-size:90%;color:#222;border-bottom:1px dotted #ddd;cursor:help}abbr{text-transform:none}blockquote{margin:0 0 1.25em;padding:0.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}blockquote cite{display:block;font-size:0.8125em;color:#555}blockquote cite:before{content:"\2014 \0020"}blockquote cite a,blockquote cite a:visited{color:#555}blockquote,blockquote p{line-height:1.6;color:#6f6f6f}.vcard{display:inline-block;margin:0 0 1.25em 0;border:1px solid #ddd;padding:0.625em 0.75em}.vcard li{margin:0;display:block}.vcard .fn{font-weight:bold;font-size:0.9375em}.vevent .summary{font-weight:bold}.vevent abbr{cursor:default;text-decoration:none;font-weight:bold;border:none;padding:0 0.0625em}@media only screen and (min-width: 48em){h1,h2,h3,h4,h5,h6{line-height:1.4}h1{font-size:2.75em}h2{font-size:2.3125em}h3{font-size:1.6875em}h4{font-size:1.4375em}}.print-only{display:none !important}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}.hide-on-print{display:none !important}.print-only{display:block !important}.hide-for-print{display:none !important}.show-for-print{display:inherit !important}}button,.button{border-style:solid;border-width:1px;cursor:pointer;font-family:inherit;font-weight:bold;line-height:1;margin:0 0 1.25em;position:relative;text-decoration:none;text-align:center;display:inline-block;padding-top:0.75em;padding-right:1.5em;padding-bottom:0.8125em;padding-left:1.5em;font-size:1em;background-color:#2ba6cb;border-color:#2284a1;color:#fff}button:hover,button:focus,.button:hover,.button:focus{background-color:#2284a1}button:hover,button:focus,.button:hover,.button:focus{color:#fff}button.secondary,.button.secondary{background-color:#e9e9e9;border-color:#d0d0d0;color:#333}button.secondary:hover,button.secondary:focus,.button.secondary:hover,.button.secondary:focus{background-color:#d0d0d0}button.secondary:hover,button.secondary:focus,.button.secondary:hover,.button.secondary:focus{color:#333}button.success,.button.success{background-color:#5da423;border-color:#457a1a;color:#fff}button.success:hover,button.success:focus,.button.success:hover,.button.success:focus{background-color:#457a1a}button.success:hover,button.success:focus,.button.success:hover,.button.success:focus{color:#fff}button.alert,.button.alert{background-color:#c60f13;border-color:#970b0e;color:#fff}button.alert:hover,button.alert:focus,.button.alert:hover,.button.alert:focus{background-color:#970b0e}button.alert:hover,button.alert:focus,.button.alert:hover,.button.alert:focus{color:#fff}button.large,.button.large{padding-top:1em;padding-right:2em;padding-bottom:1.0625em;padding-left:2em;font-size:1.25em}button.small,.button.small{padding-top:0.5625em;padding-right:1.125em;padding-bottom:0.625em;padding-left:1.125em;font-size:0.8125em}button.tiny,.button.tiny{padding-top:0.4375em;padding-right:0.875em;padding-bottom:0.5em;padding-left:0.875em;font-size:0.6875em}button.expand,.button.expand{padding-right:0px;padding-left:0px;width:100%}button.left-align,.button.left-align{text-align:left;text-indent:0.75em}button.right-align,.button.right-align{text-align:right;padding-right:0.75em}button.disabled,button[disabled],.button.disabled,.button[disabled]{background-color:#2ba6cb;border-color:#2284a1;color:#fff;cursor:default;opacity:0.6;-webkit-box-shadow:none;box-shadow:none}button.disabled:hover,button.disabled:focus,button[disabled]:hover,button[disabled]:focus,.button.disabled:hover,.button.disabled:focus,.button[disabled]:hover,.button[disabled]:focus{background-color:#2284a1}button.disabled:hover,button.disabled:focus,button[disabled]:hover,button[disabled]:focus,.button.disabled:hover,.button.disabled:focus,.button[disabled]:hover,.button[disabled]:focus{color:#fff}button.disabled:hover,button.disabled:focus,button[disabled]:hover,button[disabled]:focus,.button.disabled:hover,.button.disabled:focus,.button[disabled]:hover,.button[disabled]:focus{background-color:#2ba6cb}button.disabled.secondary,button[disabled].secondary,.button.disabled.secondary,.button[disabled].secondary{background-color:#e9e9e9;border-color:#d0d0d0;color:#333;cursor:default;opacity:0.6;-webkit-box-shadow:none;box-shadow:none}button.disabled.secondary:hover,button.disabled.secondary:focus,button[disabled].secondary:hover,button[disabled].secondary:focus,.button.disabled.secondary:hover,.button.disabled.secondary:focus,.button[disabled].secondary:hover,.button[disabled].secondary:focus{background-color:#d0d0d0}button.disabled.secondary:hover,button.disabled.secondary:focus,button[disabled].secondary:hover,button[disabled].secondary:focus,.button.disabled.secondary:hover,.button.disabled.secondary:focus,.button[disabled].secondary:hover,.button[disabled].secondary:focus{color:#333}button.disabled.secondary:hover,button.disabled.secondary:focus,button[disabled].secondary:hover,button[disabled].secondary:focus,.button.disabled.secondary:hover,.button.disabled.secondary:focus,.button[disabled].secondary:hover,.button[disabled].secondary:focus{background-color:#e9e9e9}button.disabled.success,button[disabled].success,.button.disabled.success,.button[disabled].success{background-color:#5da423;border-color:#457a1a;color:#fff;cursor:default;opacity:0.6;-webkit-box-shadow:none;box-shadow:none}button.disabled.success:hover,button.disabled.success:focus,button[disabled].success:hover,button[disabled].success:focus,.button.disabled.success:hover,.button.disabled.success:focus,.button[disabled].success:hover,.button[disabled].success:focus{background-color:#457a1a}button.disabled.success:hover,button.disabled.success:focus,button[disabled].success:hover,button[disabled].success:focus,.button.disabled.success:hover,.button.disabled.success:focus,.button[disabled].success:hover,.button[disabled].success:focus{color:#fff}button.disabled.success:hover,button.disabled.success:focus,button[disabled].success:hover,button[disabled].success:focus,.button.disabled.success:hover,.button.disabled.success:focus,.button[disabled].success:hover,.button[disabled].success:focus{background-color:#5da423}button.disabled.alert,button[disabled].alert,.button.disabled.alert,.button[disabled].alert{background-color:#c60f13;border-color:#970b0e;color:#fff;cursor:default;opacity:0.6;-webkit-box-shadow:none;box-shadow:none}button.disabled.alert:hover,button.disabled.alert:focus,button[disabled].alert:hover,button[disabled].alert:focus,.button.disabled.alert:hover,.button.disabled.alert:focus,.button[disabled].alert:hover,.button[disabled].alert:focus{background-color:#970b0e}button.disabled.alert:hover,button.disabled.alert:focus,button[disabled].alert:hover,button[disabled].alert:focus,.button.disabled.alert:hover,.button.disabled.alert:focus,.button[disabled].alert:hover,.button[disabled].alert:focus{color:#fff}button.disabled.alert:hover,button.disabled.alert:focus,button[disabled].alert:hover,button[disabled].alert:focus,.button.disabled.alert:hover,.button.disabled.alert:focus,.button[disabled].alert:hover,.button[disabled].alert:focus{background-color:#c60f13}button,.button{padding-top:0.8125em;padding-bottom:0.75em}button.tiny,.button.tiny{padding-top:0.5em;padding-bottom:0.4375em}button.small,.button.small{padding-top:0.625em;padding-bottom:0.5625em}button.large,.button.large{padding-top:1.03125em;padding-bottom:1.03125em}@media only screen{button,.button{-webkit-box-shadow:0 1px 0 rgba(255,255,255,0.5) inset;box-shadow:0 1px 0 rgba(255,255,255,0.5) inset;-webkit-transition:background-color 300ms ease-out;-moz-transition:background-color 300ms ease-out;transition:background-color 300ms ease-out}button:active,.button:active{-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.2) inset;box-shadow:0 1px 0 rgba(0,0,0,0.2) inset}button.radius,.button.radius{-webkit-border-radius:3px;border-radius:3px}button.round,.button.round{-webkit-border-radius:1000px;border-radius:1000px}}@media only screen and (min-width: 48em){button,.button{display:inline-block}}form{margin:0 0 1em}form .row .row{margin:0 -0.5em}form .row .row .column,form .row .row .columns{padding:0 0.5em}form .row .row.collapse{margin:0}form .row .row.collapse .column,form .row .row.collapse .columns{padding:0}form .row input.column,form .row input.columns,form .row textarea.column,form .row textarea.columns{padding-left:0.5em}label{font-size:0.875em;color:#4d4d4d;cursor:pointer;display:block;font-weight:500;margin-bottom:0.1875em}label.right{float:none;text-align:right}label.inline{margin:0 0 1em 0;padding:0.625em 0}.prefix,.postfix{display:block;position:relative;z-index:2;text-align:center;width:100%;padding-top:0;padding-bottom:0;border-style:solid;border-width:1px;overflow:hidden;font-size:0.875em;height:2.3125em;line-height:2.3125em}.postfix.button{padding-left:0;padding-right:0;padding-top:0;padding-bottom:0;text-align:center;line-height:2.125em}.prefix.button{padding-left:0;padding-right:0;padding-top:0;padding-bottom:0;text-align:center;line-height:2.125em}.prefix.button.radius{-webkit-border-radius:0;border-radius:0;-moz-border-radius-bottomleft:3px;-moz-border-radius-topleft:3px;-webkit-border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-bottom-left-radius:3px;border-top-left-radius:3px}.postfix.button.radius{-webkit-border-radius:0;border-radius:0;-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px;-webkit-border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-top-right-radius:3px;border-bottom-right-radius:3px}.prefix.button.round{-webkit-border-radius:0;border-radius:0;-moz-border-radius-bottomleft:1000px;-moz-border-radius-topleft:1000px;-webkit-border-bottom-left-radius:1000px;-webkit-border-top-left-radius:1000px;border-bottom-left-radius:1000px;border-top-left-radius:1000px}.postfix.button.round{-webkit-border-radius:0;border-radius:0;-moz-border-radius-topright:1000px;-moz-border-radius-bottomright:1000px;-webkit-border-top-right-radius:1000px;-webkit-border-bottom-right-radius:1000px;border-top-right-radius:1000px;border-bottom-right-radius:1000px}span.prefix{background:#f2f2f2;border-color:#d9d9d9;border-right:none;color:#333}span.prefix.radius{-webkit-border-radius:0;border-radius:0;-moz-border-radius-bottomleft:3px;-moz-border-radius-topleft:3px;-webkit-border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-bottom-left-radius:3px;border-top-left-radius:3px}span.postfix{background:#f2f2f2;border-color:#ccc;border-left:none;color:#333}span.postfix.radius{-webkit-border-radius:0;border-radius:0;-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px;-webkit-border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-top-right-radius:3px;border-bottom-right-radius:3px}.input-group.radius>*:first-child,.input-group.radius>*:first-child *{-moz-border-radius-bottomleft:3px;-moz-border-radius-topleft:3px;-webkit-border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-bottom-left-radius:3px;border-top-left-radius:3px}.input-group.radius>*:last-child,.input-group.radius>*:last-child *{-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px;-webkit-border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-top-right-radius:3px;border-bottom-right-radius:3px}.input-group.round>*:first-child,.input-group.round>*:first-child *{-moz-border-radius-bottomleft:1000px;-moz-border-radius-topleft:1000px;-webkit-border-bottom-left-radius:1000px;-webkit-border-top-left-radius:1000px;border-bottom-left-radius:1000px;border-top-left-radius:1000px}.input-group.round>*:last-child,.input-group.round>*:last-child *{-moz-border-radius-topright:1000px;-moz-border-radius-bottomright:1000px;-webkit-border-top-right-radius:1000px;-webkit-border-bottom-right-radius:1000px;border-top-right-radius:1000px;border-bottom-right-radius:1000px}input[type="text"],input[type="password"],input[type="date"],input[type="datetime"],input[type="datetime-local"],input[type="month"],input[type="week"],input[type="email"],input[type="number"],input[type="search"],input[type="tel"],input[type="time"],input[type="url"],textarea{background-color:#fff;font-family:inherit;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);color:rgba(0,0,0,0.75);display:block;font-size:0.875em;margin:0 0 1em 0;padding:0.5em;height:2.3125em;width:100%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-transition:-webkit-box-shadow 0.45s,border-color 0.45s ease-in-out;-moz-transition:-moz-box-shadow 0.45s,border-color 0.45s ease-in-out;transition:box-shadow 0.45s,border-color 0.45s ease-in-out}input[type="text"]:focus,input[type="password"]:focus,input[type="date"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="month"]:focus,input[type="week"]:focus,input[type="email"]:focus,input[type="number"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="time"]:focus,input[type="url"]:focus,textarea:focus{-webkit-box-shadow:0 0 5px #999;-moz-box-shadow:0 0 5px #999;box-shadow:0 0 5px #999;border-color:#999}input[type="text"]:focus,input[type="password"]:focus,input[type="date"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="month"]:focus,input[type="week"]:focus,input[type="email"]:focus,input[type="number"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="time"]:focus,input[type="url"]:focus,textarea:focus{background:#fafafa;border-color:#999;outline:none}input[type="text"][disabled],input[type="password"][disabled],input[type="date"][disabled],input[type="datetime"][disabled],input[type="datetime-local"][disabled],input[type="month"][disabled],input[type="week"][disabled],input[type="email"][disabled],input[type="number"][disabled],input[type="search"][disabled],input[type="tel"][disabled],input[type="time"][disabled],input[type="url"][disabled],textarea[disabled]{background-color:#ddd}input[type="file"],input[type="checkbox"],input[type="radio"],select{margin:0 0 1em 0}input[type="file"]{width:100%}fieldset{border:solid 1px #ddd;padding:1.25em;margin:1.125em 0}fieldset legend{font-weight:bold;background:#fff;padding:0 0.1875em;margin:0;margin-left:-0.1875em}.error input,input.error,.error textarea,textarea.error{border-color:#c60f13;background-color:rgba(198,15,19,0.1)}.error input:focus,input.error:focus,.error textarea:focus,textarea.error:focus{background:#fafafa;border-color:#999}.error label,label.error{color:#c60f13}.error small,small.error{display:block;padding:0.375em 0.25em;margin-top:-1.3125em;margin-bottom:1em;font-size:0.75em;font-weight:bold;background:#c60f13;color:#fff}form.custom .hidden-field{margin-left:-99999px;position:absolute;visibility:hidden}form.custom .custom{display:inline-block;width:16px;height:16px;position:relative;vertical-align:middle;border:solid 1px #ccc;background:#fff}form.custom .custom.checkbox{-webkit-border-radius:0px;border-radius:0px;padding:-3px}form.custom .custom.radio{-webkit-border-radius:1000px;border-radius:1000px;padding:3px}form.custom .custom.checkbox:before{content:"";display:block;font-size:20px;color:#fff}form.custom .custom.radio.checked:before{content:"";display:block;width:8px;height:8px;-webkit-border-radius:1000px;border-radius:1000px;background:#222;position:relative}form.custom .custom.checkbox.checked:before{content:"\2A2F";color:#222;margin-top:-8px;margin-left:2px}form.custom .custom.dropdown{display:block;position:relative;top:0;height:2.3125em;margin-bottom:1.25em;margin-top:0px;padding:0px;width:100%;background:#fff;background:-moz-linear-gradient(top, #fff 0%, #f3f3f3 100%);background:-webkit-linear-gradient(top, #fff 0%, #f3f3f3 100%);background:linear-gradient(to bottom, #fff 0%, #f3f3f3 100%);-webkit-box-shadow:none;box-shadow:none;font-size:0.875em;vertical-align:top}form.custom .custom.dropdown ul{overflow-y:auto;max-height:200px}form.custom .custom.dropdown .current{cursor:default;white-space:nowrap;line-height:2.25em;color:rgba(0,0,0,0.75);text-decoration:none;overflow:hidden;display:block;margin-left:0.5em;margin-right:2.3125em}form.custom .custom.dropdown .selector{cursor:default;position:absolute;width:2.5em;height:2.3125em;display:block;right:0;top:0}form.custom .custom.dropdown .selector:after{content:"";display:block;content:"";display:block;width:0;height:0;border:inset 5px;border-color:#aaa transparent transparent transparent;border-top-style:solid;position:absolute;left:0.9375em;top:50%;margin-top:-3px}form.custom .custom.dropdown:hover a.selector:after,form.custom .custom.dropdown.open a.selector:after{content:"";display:block;width:0;height:0;border:inset 5px;border-color:#222 transparent transparent transparent;border-top-style:solid}form.custom .custom.dropdown .disabled{color:#888}form.custom .custom.dropdown .disabled:hover{background:transparent;color:#888}form.custom .custom.dropdown .disabled:hover:after{display:none}form.custom .custom.dropdown.open ul{display:block;z-index:10;min-width:100%;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}form.custom .custom.dropdown.small{max-width:134px}form.custom .custom.dropdown.medium{max-width:254px}form.custom .custom.dropdown.large{max-width:434px}form.custom .custom.dropdown.expand{width:100% !important}form.custom .custom.dropdown.open.small ul{min-width:134px;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}form.custom .custom.dropdown.open.medium ul{min-width:254px;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}form.custom .custom.dropdown.open.large ul{min-width:434px;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}form.custom .custom.dropdown ul{position:absolute;width:auto;display:none;margin:0;left:-1px;top:auto;-webkit-box-shadow:0 2px 2px 0px rgba(0,0,0,0.1);box-shadow:0 2px 2px 0px rgba(0,0,0,0.1);margin:0;padding:0;background:#fff;border:solid 1px #ccc;font-size:16px}form.custom .custom.dropdown ul li{color:#555;font-size:0.875em;cursor:default;padding-top:0.25em;padding-bottom:0.25em;padding-left:0.375em;padding-right:2.375em;min-height:1.5em;line-height:1.5em;margin:0;white-space:nowrap;list-style:none}form.custom .custom.dropdown ul li.selected{background:#eee;color:#000}form.custom .custom.dropdown ul li:hover{background-color:#e4e4e4;color:#000}form.custom .custom.dropdown ul li.selected:hover{background:#eee;cursor:default;color:#000}form.custom .custom.dropdown ul.show{display:block}form.custom .custom.disabled{background:#ddd}.button-group{list-style:none;margin:0;*zoom:1}.button-group:before,.button-group:after{content:" ";display:table}.button-group:after{clear:both}.button-group>*{margin:0 0 0 -1px;float:left}.button-group>*:first-child{margin-left:0}.button-group.radius>*:first-child,.button-group.radius>*:first-child>a,.button-group.radius>*:first-child>button,.button-group.radius>*:first-child>.button{-moz-border-radius-bottomleft:3px;-moz-border-radius-topleft:3px;-webkit-border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-bottom-left-radius:3px;border-top-left-radius:3px}.button-group.radius>*:last-child,.button-group.radius>*:last-child>a,.button-group.radius>*:last-child>button,.button-group.radius>*:last-child>.button{-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px;-webkit-border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-top-right-radius:3px;border-bottom-right-radius:3px}.button-group.round>*:first-child,.button-group.round>*:first-child>a,.button-group.round>*:first-child>button,.button-group.round>*:first-child>.button{-moz-border-radius-bottomleft:1000px;-moz-border-radius-topleft:1000px;-webkit-border-bottom-left-radius:1000px;-webkit-border-top-left-radius:1000px;border-bottom-left-radius:1000px;border-top-left-radius:1000px}.button-group.round>*:last-child,.button-group.round>*:last-child>a,.button-group.round>*:last-child>button,.button-group.round>*:last-child>.button{-moz-border-radius-topright:1000px;-moz-border-radius-bottomright:1000px;-webkit-border-top-right-radius:1000px;-webkit-border-bottom-right-radius:1000px;border-top-right-radius:1000px;border-bottom-right-radius:1000px}.button-group.even-2 li{width:50%}.button-group.even-2 li button,.button-group.even-2 li .button{width:100%}.button-group.even-3 li{width:33.33333%}.button-group.even-3 li button,.button-group.even-3 li .button{width:100%}.button-group.even-4 li{width:25%}.button-group.even-4 li button,.button-group.even-4 li .button{width:100%}.button-group.even-5 li{width:20%}.button-group.even-5 li button,.button-group.even-5 li .button{width:100%}.button-group.even-6 li{width:16.66667%}.button-group.even-6 li button,.button-group.even-6 li .button{width:100%}.button-group.even-7 li{width:14.28571%}.button-group.even-7 li button,.button-group.even-7 li .button{width:100%}.button-group.even-8 li{width:12.5%}.button-group.even-8 li button,.button-group.even-8 li .button{width:100%}.button-bar{*zoom:1}.button-bar:before,.button-bar:after{content:" ";display:table}.button-bar:after{clear:both}.button-bar .button-group{float:left;margin-right:0.625em}.button-bar .button-group div{overflow:hidden}.dropdown.button{position:relative;padding-right:3.1875em}.dropdown.button:before{position:absolute;content:"";width:0;height:0;display:block;border-style:solid;border-color:#fff transparent transparent transparent;top:50%}.dropdown.button:before{border-width:0.5625em;right:1.5em;margin-top:-0.25em}.dropdown.button:before{border-color:#fff transparent transparent transparent}.dropdown.button.tiny{padding-right:2.1875em}.dropdown.button.tiny:before{border-width:0.4375em;right:0.875em;margin-top:-0.15625em}.dropdown.button.tiny:before{border-color:#fff transparent transparent transparent}.dropdown.button.small{padding-right:2.8125em}.dropdown.button.small:before{border-width:0.5625em;right:1.125em;margin-top:-0.21875em}.dropdown.button.small:before{border-color:#fff transparent transparent transparent}.dropdown.button.large{padding-right:4em}.dropdown.button.large:before{border-width:0.625em;right:1.75em;margin-top:-0.3125em}.dropdown.button.large:before{border-color:#fff transparent transparent transparent}.dropdown.button.secondary:before{border-color:#333 transparent transparent transparent}.split.button{position:relative;padding-right:4.8em}.split.button span{display:block;height:100%;position:absolute;right:0;top:0;border-left:solid 1px}.split.button span:before{position:absolute;content:"";width:0;height:0;display:block;border-style:inset;left:50%}.split.button span:active{background-color:rgba(0,0,0,0.1)}.split.button span{border-left-color:#1e728c}.split.button span{width:3em}.split.button span:before{border-top-style:solid;border-width:0.5625em;top:1.125em;margin-left:-0.5625em}.split.button span:before{border-color:#fff transparent transparent transparent}.split.button.secondary span{border-left-color:#c3c3c3}.split.button.secondary span:before{border-color:#fff transparent transparent transparent}.split.button.alert span{border-left-color:#7f0a0c}.split.button.success span{border-left-color:#396516}.split.button.tiny{padding-right:3.9375em}.split.button.tiny span{width:2.84375em}.split.button.tiny span:before{border-top-style:solid;border-width:0.4375em;top:0.875em;margin-left:-0.3125em}.split.button.small{padding-right:3.9375em}.split.button.small span{width:2.8125em}.split.button.small span:before{border-top-style:solid;border-width:0.5625em;top:0.84375em;margin-left:-0.5625em}.split.button.large{padding-right:6em}.split.button.large span{width:3.75em}.split.button.large span:before{border-top-style:solid;border-width:0.625em;top:1.3125em;margin-left:-0.5625em}.split.button.expand{padding-left:2em}.split.button.secondary span:before{border-color:#333 transparent transparent transparent}.split.button.radius span{-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px;-webkit-border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-top-right-radius:3px;border-bottom-right-radius:3px}.split.button.round span{-moz-border-radius-topright:1000px;-moz-border-radius-bottomright:1000px;-webkit-border-top-right-radius:1000px;-webkit-border-bottom-right-radius:1000px;border-top-right-radius:1000px;border-bottom-right-radius:1000px}.flex-video{position:relative;padding-top:1.5625em;padding-bottom:67.5%;height:0;margin-bottom:1em;overflow:hidden}.flex-video.widescreen{padding-bottom:57.25%}.flex-video.vimeo{padding-top:0}.flex-video iframe,.flex-video object,.flex-video embed,.flex-video video{position:absolute;top:0;left:0;width:100%;height:100%}.section-container,.section-container.auto{width:100%;display:block;margin-bottom:1.25em;border:1px solid #ccc;border-top:none}.section-container>section,.section-container>.section,.section-container.auto>section,.section-container.auto>.section{position:relative}.section-container>section>.title,.section-container>.section>.title,.section-container.auto>section>.title,.section-container.auto>.section>.title{background-color:#efefef;cursor:pointer;margin-bottom:0}.section-container>section>.title a,.section-container>.section>.title a,.section-container.auto>section>.title a,.section-container.auto>.section>.title a{padding:0.9375em;display:inline-block;color:#333;font-size:0.875em;white-space:nowrap}.section-container>section>.title:hover,.section-container>.section>.title:hover,.section-container.auto>section>.title:hover,.section-container.auto>.section>.title:hover{background-color:#e2e2e2}.section-container>section .content,.section-container>.section .content,.section-container.auto>section .content,.section-container.auto>.section .content{display:none;padding:0.9375em;background-color:#fff}.section-container>section .content>*:last-child,.section-container>.section .content>*:last-child,.section-container.auto>section .content>*:last-child,.section-container.auto>.section .content>*:last-child{margin-bottom:0}.section-container>section .content>*:first-child,.section-container>.section .content>*:first-child,.section-container.auto>section .content>*:first-child,.section-container.auto>.section .content>*:first-child{padding-top:0}.section-container>section .content>*:last-child,.section-container>.section .content>*:last-child,.section-container.auto>section .content>*:last-child,.section-container.auto>.section .content>*:last-child{padding-bottom:0}.section-container>section.active>.content,.section-container>.section.active>.content,.section-container.auto>section.active>.content,.section-container.auto>.section.active>.content{display:block}.section-container>section.active>.title,.section-container>.section.active>.title,.section-container.auto>section.active>.title,.section-container.auto>.section.active>.title{background:#d5d5d5}.section-container>section.active>.title a,.section-container>.section.active>.title a,.section-container.auto>section.active>.title a,.section-container.auto>.section.active>.title a{color:#333}.section-container>section>.title,.section-container>.section>.title,.section-container.auto>section>.title,.section-container.auto>.section>.title{top:0;width:100%;margin:0;border-top:solid 1px #ccc}.section-container>section>.title a,.section-container>.section>.title a,.section-container.auto>section>.title a,.section-container.auto>.section>.title a{width:100%}.section-container.tabs{border:0;position:relative}.section-container.tabs>section,.section-container.tabs>.section{border:0;position:static}.section-container.tabs>section>.title,.section-container.tabs>.section>.title{background-color:#efefef;cursor:pointer;margin-bottom:0}.section-container.tabs>section>.title a,.section-container.tabs>.section>.title a{padding:0.9375em;display:inline-block;color:#333;font-size:0.875em;white-space:nowrap}.section-container.tabs>section>.title:hover,.section-container.tabs>.section>.title:hover{background-color:#e2e2e2}.section-container.tabs>section .content,.section-container.tabs>.section .content{display:none;padding:0.9375em;background-color:#fff}.section-container.tabs>section .content>*:last-child,.section-container.tabs>.section .content>*:last-child{margin-bottom:0}.section-container.tabs>section .content>*:first-child,.section-container.tabs>.section .content>*:first-child{padding-top:0}.section-container.tabs>section .content>*:last-child,.section-container.tabs>.section .content>*:last-child{padding-bottom:0}.section-container.tabs>section.active>.content,.section-container.tabs>.section.active>.content{display:block}.section-container.tabs>section.active>.title,.section-container.tabs>.section.active>.title{background:#fff}.section-container.tabs>section.active>.title a,.section-container.tabs>.section.active>.title a{color:#333}.section-container.tabs>section>.title,.section-container.tabs>.section>.title{width:auto;border:solid 1px #ccc;border-right:0;border-bottom:0;position:absolute;top:0;z-index:1}.section-container.tabs>section>.title a,.section-container.tabs>.section>.title a{width:100%}.section-container.tabs>section:last-child .title,.section-container.tabs>.section:last-child .title{border-right:solid 1px #ccc}.section-container.tabs>section .content,.section-container.tabs>.section .content{border:solid 1px #ccc;position:absolute;z-index:10;display:none;top:-1px}.section-container.tabs>section.active>.title,.section-container.tabs>.section.active>.title{z-index:11;border-bottom:0;background-color:#fff}.section-container.tabs>section.active>.content,.section-container.tabs>.section.active>.content{position:relative}@media only screen and (min-width: 48em){.section-container.auto{border:0;position:relative}.section-container.auto>section,.section-container.auto>.section{border:0;position:static}.section-container.auto>section>.title,.section-container.auto>.section>.title{background-color:#efefef;cursor:pointer;margin-bottom:0}.section-container.auto>section>.title a,.section-container.auto>.section>.title a{padding:0.9375em;display:inline-block;color:#333;font-size:0.875em;white-space:nowrap}.section-container.auto>section>.title:hover,.section-container.auto>.section>.title:hover{background-color:#e2e2e2}.section-container.auto>section .content,.section-container.auto>.section .content{display:none;padding:0.9375em;background-color:#fff}.section-container.auto>section .content>*:last-child,.section-container.auto>.section .content>*:last-child{margin-bottom:0}.section-container.auto>section .content>*:first-child,.section-container.auto>.section .content>*:first-child{padding-top:0}.section-container.auto>section .content>*:last-child,.section-container.auto>.section .content>*:last-child{padding-bottom:0}.section-container.auto>section.active>.content,.section-container.auto>.section.active>.content{display:block}.section-container.auto>section.active>.title,.section-container.auto>.section.active>.title{background:#fff}.section-container.auto>section.active>.title a,.section-container.auto>.section.active>.title a{color:#333}.section-container.auto>section>.title,.section-container.auto>.section>.title{width:auto;border:solid 1px #ccc;border-right:0;border-bottom:0;position:absolute;top:0;z-index:1}.section-container.auto>section>.title a,.section-container.auto>.section>.title a{width:100%}.section-container.auto>section:last-child .title,.section-container.auto>.section:last-child .title{border-right:solid 1px #ccc}.section-container.auto>section .content,.section-container.auto>.section .content{border:solid 1px #ccc;position:absolute;z-index:10;display:none;top:-1px}.section-container.auto>section.active>.title,.section-container.auto>.section.active>.title{z-index:11;border-bottom:0;background-color:#fff}.section-container.auto>section.active>.content,.section-container.auto>.section.active>.content{position:relative}.section-container.accordion .section{padding-top:0 !important}.section-container.vertical-tabs{border:1px solid #ccc;position:relative}.section-container.vertical-tabs section,.section-container.vertical-tabs .section{padding-top:0 !important;border:0;position:static}.section-container.vertical-tabs section>.title,.section-container.vertical-tabs .section>.title{background-color:#efefef;cursor:pointer;margin-bottom:0}.section-container.vertical-tabs section>.title a,.section-container.vertical-tabs .section>.title a{padding:0.9375em;display:inline-block;color:#333;font-size:0.875em;white-space:nowrap}.section-container.vertical-tabs section>.title:hover,.section-container.vertical-tabs .section>.title:hover{background-color:#e2e2e2}.section-container.vertical-tabs section .content,.section-container.vertical-tabs .section .content{display:none;padding:0.9375em;background-color:#fff}.section-container.vertical-tabs section .content>*:last-child,.section-container.vertical-tabs .section .content>*:last-child{margin-bottom:0}.section-container.vertical-tabs section .content>*:first-child,.section-container.vertical-tabs .section .content>*:first-child{padding-top:0}.section-container.vertical-tabs section .content>*:last-child,.section-container.vertical-tabs .section .content>*:last-child{padding-bottom:0}.section-container.vertical-tabs section.active>.content,.section-container.vertical-tabs .section.active>.content{display:block}.section-container.vertical-tabs section.active>.title,.section-container.vertical-tabs .section.active>.title{background:#d5d5d5}.section-container.vertical-tabs section.active>.title a,.section-container.vertical-tabs .section.active>.title a{color:#333}.section-container.vertical-tabs section>.title,.section-container.vertical-tabs .section>.title{position:absolute;border-top:solid 1px #ccc;width:12.5em}.section-container.vertical-tabs section:first-child .title,.section-container.vertical-tabs .section:first-child .title{border-top:0}.section-container.vertical-tabs section .content,.section-container.vertical-tabs .section .content{display:block;position:relative;left:12.5em;border-left:solid 1px #ccc;z-index:10}.section-container.vertical-tabs section.active>.title,.section-container.vertical-tabs .section.active>.title{background-color:#d5d5d5;width:12.5625em;border-right:solid 0 transparent;z-index:11}.section-container.vertical-tabs section.active:last-child .title,.section-container.vertical-tabs .section.active:last-child .title{border-bottom:0}.section-container.vertical-nav{border:0;position:relative}.section-container.vertical-nav>section,.section-container.vertical-nav>.section{padding-top:0 !important;position:relative}.section-container.vertical-nav>section>.title,.section-container.vertical-nav>.section>.title{background-color:#efefef;cursor:pointer;margin-bottom:0}.section-container.vertical-nav>section>.title a,.section-container.vertical-nav>.section>.title a{padding:0.9375em;display:inline-block;color:#333;font-size:0.875em;white-space:nowrap}.section-container.vertical-nav>section>.title:hover,.section-container.vertical-nav>.section>.title:hover{background-color:#e2e2e2}.section-container.vertical-nav>section .content,.section-container.vertical-nav>.section .content{display:none;padding:0.9375em;background-color:#fff}.section-container.vertical-nav>section .content>*:last-child,.section-container.vertical-nav>.section .content>*:last-child{margin-bottom:0}.section-container.vertical-nav>section .content>*:first-child,.section-container.vertical-nav>.section .content>*:first-child{padding-top:0}.section-container.vertical-nav>section .content>*:last-child,.section-container.vertical-nav>.section .content>*:last-child{padding-bottom:0}.section-container.vertical-nav>section.active>.content,.section-container.vertical-nav>.section.active>.content{display:block}.section-container.vertical-nav>section.active>.title,.section-container.vertical-nav>.section.active>.title{background:#d5d5d5}.section-container.vertical-nav>section.active>.title a,.section-container.vertical-nav>.section.active>.title a{color:#333}.section-container.vertical-nav>section>.title,.section-container.vertical-nav>.section>.title{border-top:none;border:solid 1px #ccc}.section-container.vertical-nav>section>.title a,.section-container.vertical-nav>.section>.title a{display:block;width:100%}.section-container.vertical-nav>section .content,.section-container.vertical-nav>.section .content{display:none}.section-container.vertical-nav>section:first-child .title,.section-container.vertical-nav>.section:first-child .title{border-bottom:none}.section-container.vertical-nav>section.active>.content,.section-container.vertical-nav>.section.active>.content{display:block;position:absolute;left:100%;top:0px;z-index:999;min-width:12.5em;border:solid 1px #ccc}.section-container.horizontal-nav{position:relative;background:#efefef;border:1px solid #ccc}.section-container.horizontal-nav>section,.section-container.horizontal-nav>.section{padding-top:0;border:0;position:static}.section-container.horizontal-nav>section>.title,.section-container.horizontal-nav>.section>.title{background-color:#efefef;cursor:pointer;margin-bottom:0}.section-container.horizontal-nav>section>.title a,.section-container.horizontal-nav>.section>.title a{padding:0.9375em;display:inline-block;color:#333;font-size:0.875em;white-space:nowrap}.section-container.horizontal-nav>section>.title:hover,.section-container.horizontal-nav>.section>.title:hover{background-color:#e2e2e2}.section-container.horizontal-nav>section .content,.section-container.horizontal-nav>.section .content{display:none;padding:0.9375em;background-color:#fff}.section-container.horizontal-nav>section .content>*:last-child,.section-container.horizontal-nav>.section .content>*:last-child{margin-bottom:0}.section-container.horizontal-nav>section .content>*:first-child,.section-container.horizontal-nav>.section .content>*:first-child{padding-top:0}.section-container.horizontal-nav>section .content>*:last-child,.section-container.horizontal-nav>.section .content>*:last-child{padding-bottom:0}.section-container.horizontal-nav>section.active>.content,.section-container.horizontal-nav>.section.active>.content{display:block}.section-container.horizontal-nav>section.active>.title,.section-container.horizontal-nav>.section.active>.title{background:#d5d5d5}.section-container.horizontal-nav>section.active>.title a,.section-container.horizontal-nav>.section.active>.title a{color:#333}.section-container.horizontal-nav>section>.title,.section-container.horizontal-nav>.section>.title{width:auto;border:solid 1px #ccc;border-left:0;top:-1px;position:absolute;z-index:1}.section-container.horizontal-nav>section>.title a,.section-container.horizontal-nav>.section>.title a{width:100%}.section-container.horizontal-nav>section .content,.section-container.horizontal-nav>.section .content{display:none}.section-container.horizontal-nav>section.active>.content,.section-container.horizontal-nav>.section.active>.content{display:block;position:absolute;z-index:999;left:0;top:-2px;min-width:12.5em;border:solid 1px #ccc}}.contain-to-grid{width:100%;background:#111}.fixed{width:100%;left:0;position:fixed;top:0;z-index:99}.top-bar{overflow:hidden;height:45px;line-height:45px;position:relative;background:#111;margin-bottom:1.875em}.top-bar ul{margin-bottom:0;list-style:none}.top-bar .row{max-width:none}.top-bar form,.top-bar input{margin-bottom:0}.top-bar input{height:2.45em}.top-bar .button{padding-top:.5em;padding-bottom:.5em;margin-bottom:0}.top-bar .title-area{position:relative}.top-bar .name{height:45px;margin:0;font-size:16px}.top-bar .name h1{line-height:45px;font-size:1.0625em;margin:0}.top-bar .name h1 a{font-weight:bold;color:#fff;width:50%;display:block;padding:0 15px}.top-bar .toggle-topbar{position:absolute;right:0;top:0}.top-bar .toggle-topbar a{color:#fff;text-transform:uppercase;font-size:0.8125em;font-weight:bold;position:relative;display:block;padding:0 15px;height:45px;line-height:45px}.top-bar .toggle-topbar.menu-icon{right:15px;top:50%;margin-top:-16px;padding-left:40px}.top-bar .toggle-topbar.menu-icon a{text-indent:-48px;width:34px;height:34px;line-height:33px;padding:0;color:#fff}.top-bar .toggle-topbar.menu-icon a span{position:absolute;right:0;display:block;width:16px;height:0;-webkit-box-shadow:0 10px 0 1px #fff,0 16px 0 1px #fff,0 22px 0 1px #fff;box-shadow:0 10px 0 1px #fff,0 16px 0 1px #fff,0 22px 0 1px #fff}.top-bar.expanded{height:auto;background:transparent}.top-bar.expanded .title-area{background:#111}.top-bar.expanded .toggle-topbar a{color:#888}.top-bar.expanded .toggle-topbar a span{-webkit-box-shadow:0 10px 0 1px #888,0 16px 0 1px #888,0 22px 0 1px #888;box-shadow:0 10px 0 1px #888,0 16px 0 1px #888,0 22px 0 1px #888}.top-bar-section{left:0;position:relative;width:auto;-webkit-transition:left 300ms ease-out;-moz-transition:left 300ms ease-out;transition:left 300ms ease-out}.top-bar-section ul{width:100%;height:auto;display:block;background:#333;font-size:16px;margin:0}.top-bar-section .divider,.top-bar-section [role="separator"]{border-bottom:solid 1px #4d4d4d;border-top:solid 1px #1a1a1a;clear:both;height:1px;width:100%}.top-bar-section ul li>a{display:block;width:100%;color:#fff;padding:12px 0 12px 0;padding-left:15px;font-size:0.8125em;font-weight:bold;background:#333}.top-bar-section ul li>a:hover{background:#2b2b2b}.top-bar-section ul li>a.button{background:#2ba6cb;font-size:0.8125em}.top-bar-section ul li>a.button:hover{background:#2284a1}.top-bar-section ul li>a.button.secondary{background:#e9e9e9}.top-bar-section ul li>a.button.secondary:hover{background:#d0d0d0}.top-bar-section ul li>a.button.success{background:#5da423}.top-bar-section ul li>a.button.success:hover{background:#457a1a}.top-bar-section ul li>a.button.alert{background:#c60f13}.top-bar-section ul li>a.button.alert:hover{background:#970b0e}.top-bar-section ul li.active>a{background:#2b2b2b}.top-bar-section .has-form{padding:15px}.top-bar-section .has-dropdown{position:relative}.top-bar-section .has-dropdown>a:after{content:"";display:block;width:0;height:0;border:inset 5px;border-color:transparent transparent transparent rgba(255,255,255,0.5);border-left-style:solid;margin-right:15px;margin-top:-4.5px;position:absolute;top:22px;right:0}.top-bar-section .has-dropdown.moved{position:static}.top-bar-section .has-dropdown.moved>.dropdown{visibility:visible}.top-bar-section .dropdown{position:absolute;left:100%;top:0;visibility:hidden;z-index:99}.top-bar-section .dropdown li{width:100%}.top-bar-section .dropdown li a{font-weight:normal;padding:8px 15px}.top-bar-section .dropdown li.title h5{margin-bottom:0}.top-bar-section .dropdown li.title h5 a{color:#fff;line-height:22.5px;display:block}.top-bar-section .dropdown label{padding:8px 15px 2px;margin-bottom:0;text-transform:uppercase;color:#555;font-weight:bold;font-size:0.625em}.top-bar-js-breakpoint{width:58.75em !important;visibility:hidden}.js-generated{display:block}@media only screen and (min-width: 58.75em){.top-bar{background:#111;*zoom:1;overflow:visible}.top-bar:before,.top-bar:after{content:" ";display:table}.top-bar:after{clear:both}.top-bar .toggle-topbar{display:none}.top-bar .title-area{float:left}.top-bar .name h1 a{width:auto}.top-bar input,.top-bar .button{line-height:2em;font-size:0.875em;height:2em;padding:0 10px;position:relative;top:8px}.top-bar.expanded{background:#111}.contain-to-grid .top-bar{max-width:62.5em;margin:0 auto;margin-bottom:1.875em}.top-bar-section{-webkit-transition:none 0 0;-moz-transition:none 0 0;transition:none 0 0;left:0 !important}.top-bar-section ul{width:auto;height:auto !important;display:inline}.top-bar-section ul li{float:left}.top-bar-section ul li .js-generated{display:none}.top-bar-section li a:not(.button){padding:0 15px;line-height:45px;background:#111}.top-bar-section li a:not(.button):hover{background:#000}.top-bar-section .has-dropdown>a{padding-right:35px !important}.top-bar-section .has-dropdown>a:after{content:"";display:block;width:0;height:0;border:inset 5px;border-color:rgba(255,255,255,0.5) transparent transparent transparent;border-top-style:solid;margin-top:-2.5px;top:22.5px}.top-bar-section .has-dropdown.moved{position:relative}.top-bar-section .has-dropdown.moved>.dropdown{visibility:hidden}.top-bar-section .has-dropdown:hover>.dropdown,.top-bar-section .has-dropdown:active>.dropdown{visibility:visible}.top-bar-section .has-dropdown .dropdown li.has-dropdown>a:after{border:none;content:"\00bb";margin-top:-15px;right:5px}.top-bar-section .dropdown{left:0;top:auto;background:transparent;min-width:100%}.top-bar-section .dropdown li a{color:#fff;line-height:1;white-space:nowrap;padding:7px 15px;background:#1e1e1e}.top-bar-section .dropdown li label{white-space:nowrap;background:#1e1e1e}.top-bar-section .dropdown li .dropdown{left:100%;top:0}.top-bar-section>ul>.divider,.top-bar-section>ul>[role="separator"]{border-bottom:none;border-top:none;border-right:solid 1px #2b2b2b;border-left:solid 1px #000;clear:none;height:45px;width:0px}.top-bar-section .has-form{background:#111;padding:0 15px;height:45px}.top-bar-section ul.right li .dropdown{left:auto;right:0}.top-bar-section ul.right li .dropdown li .dropdown{right:100%}}.orbit-container{overflow:hidden;width:100%;position:relative;background:#f5f5f5}.orbit-container .orbit-slides-container{list-style:none;margin:0;padding:0;position:relative}.orbit-container .orbit-slides-container img{display:block}.orbit-container .orbit-slides-container>*{position:relative;float:left;height:100%}.orbit-container .orbit-slides-container>* .orbit-caption{position:absolute;bottom:0;background-color:#000;background-color:rgba(0,0,0,0.6);color:#fff;width:100%;padding:10px 14px;font-size:0.875em}.orbit-container .orbit-slides-container>* .orbit-caption *{color:#fff}.orbit-container .orbit-slide-number{position:absolute;top:10px;left:10px;font-size:12px;color:#fff;background:rgba(0,0,0,0)}.orbit-container .orbit-slide-number span{font-weight:700;padding:0.3125em}.orbit-container .orbit-timer{position:absolute;top:10px;right:10px;height:6px;width:100px}.orbit-container .orbit-timer .orbit-progress{height:100%;background-color:#000;background-color:rgba(0,0,0,0.6);display:block;width:0%}.orbit-container .orbit-timer>span{display:none;position:absolute;top:10px;right:0px;width:11px;height:14px;border:solid 4px #000;border-top:none;border-bottom:none}.orbit-container .orbit-timer.paused>span{right:-6px;top:9px;width:11px;height:14px;border:inset 8px;border-right-style:solid;border-color:transparent transparent transparent #000}.orbit-container:hover .orbit-timer>span{display:block}.orbit-container .orbit-prev,.orbit-container .orbit-next{position:absolute;top:50%;margin-top:-25px;background-color:#000;background-color:rgba(0,0,0,0.6);width:50px;height:60px;line-height:50px;color:white;text-indent:-9999px !important}.orbit-container .orbit-prev>span,.orbit-container .orbit-next>span{position:absolute;top:50%;margin-top:-16px;display:block;width:0;height:0;border:inset 16px}.orbit-container .orbit-prev{left:0}.orbit-container .orbit-prev>span{border-right-style:solid;border-color:transparent;border-right-color:#fff}.orbit-container .orbit-prev:hover>span{border-right-color:#ccc}.orbit-container .orbit-next{right:0}.orbit-container .orbit-next>span{border-color:transparent;border-left-style:solid;border-left-color:#fff;left:50%;margin-left:-8px}.orbit-container .orbit-next:hover>span{border-left-color:#ccc}.orbit-bullets{margin:0 auto 30px auto;overflow:hidden;position:relative;top:10px}.orbit-bullets li{display:block;width:18px;height:18px;background:#999;float:left;margin-right:6px;border:solid 2px #222;-webkit-border-radius:1000px;border-radius:1000px}.orbit-bullets li.active{background:#222}.orbit-bullets li:last-child{margin-right:0}.touch .orbit-container .orbit-prev,.touch .orbit-container .orbit-next{display:none}.touch .orbit-bullets{display:none}@media only screen and (min-width: 48em){.touch .orbit-container .orbit-prev,.touch .orbit-container .orbit-next{display:inherit}.touch .orbit-bullets{display:block}}.reveal-modal-bg{position:fixed;height:100%;width:100%;background:#000;background:rgba(0,0,0,0.45);z-index:98;display:none;top:0;left:0}.reveal-modal{visibility:hidden;display:none;position:absolute;left:50%;z-index:99;height:auto;background-color:#fff;margin-left:-40%;width:80%;background-color:#fff;padding:1.25em;border:solid 1px #666;-webkit-box-shadow:0 0 10px rgba(0,0,0,0.4);box-shadow:0 0 10px rgba(0,0,0,0.4);top:50px}.reveal-modal .column,.reveal-modal .columns{min-width:0}.reveal-modal>:first-child{margin-top:0}.reveal-modal>:last-child{margin-bottom:0}.reveal-modal .close-reveal-modal{font-size:1.375em;line-height:1;position:absolute;top:0.5em;right:0.6875em;color:#aaa;font-weight:bold;cursor:pointer}@media only screen and (min-width: 48em){.reveal-modal{padding:1.875em;top:6.25em}.reveal-modal.tiny{margin-left:-15%;width:30%}.reveal-modal.small{margin-left:-20%;width:40%}.reveal-modal.medium{margin-left:-30%;width:60%}.reveal-modal.large{margin-left:-35%;width:70%}.reveal-modal.xlarge{margin-left:-47.5%;width:95%}}@media print{.reveal-modal{background:#fff !important}}.joyride-list{display:none}.joyride-tip-guide{display:none;position:absolute;background:#000;color:#fff;z-index:101;top:0;left:2.5%;font-family:inherit;font-weight:normal;width:95%}.lt-ie9 .joyride-tip-guide{max-width:800px;left:50%;margin-left:-400px}.joyride-content-wrapper{width:100%;padding:1.125em 1.25em 1.5em}.joyride-content-wrapper .button{margin-bottom:0 !important}.joyride-tip-guide .joyride-nub{display:block;position:absolute;left:22px;width:0;height:0;border:inset 14px}.joyride-tip-guide .joyride-nub.top{border-top-style:solid;border-color:#000;border-top-color:transparent !important;border-left-color:transparent !important;border-right-color:transparent !important;top:-28px}.joyride-tip-guide .joyride-nub.bottom{border-bottom-style:solid;border-color:#000 !important;border-bottom-color:transparent !important;border-left-color:transparent !important;border-right-color:transparent !important;bottom:-28px}.joyride-tip-guide .joyride-nub.right{right:-28px}.joyride-tip-guide .joyride-nub.left{left:-28px}.joyride-tip-guide h1,.joyride-tip-guide h2,.joyride-tip-guide h3,.joyride-tip-guide h4,.joyride-tip-guide h5,.joyride-tip-guide h6{line-height:1.25;margin:0;font-weight:bold;color:#fff}.joyride-tip-guide p{margin:0 0 1.125em 0;font-size:0.875em;line-height:1.3}.joyride-timer-indicator-wrap{width:50px;height:3px;border:solid 1px #555;position:absolute;right:1.0625em;bottom:1em}.joyride-timer-indicator{display:block;width:0;height:inherit;background:#666}.joyride-close-tip{position:absolute;right:12px;top:10px;color:#777 !important;text-decoration:none;font-size:30px;font-weight:normal;line-height:0.5 !important}.joyride-close-tip:hover,.joyride-close-tip:focus{color:#eee !important}.joyride-modal-bg{position:fixed;height:100%;width:100%;background:transparent;background:rgba(0,0,0,0.5);z-index:100;display:none;top:0;left:0;cursor:pointer}.joyride-expose-wrapper{background-color:#ffffff;position:absolute;border-radius:3px;z-index:102;-moz-box-shadow:0px 0px 30px #fff;-webkit-box-shadow:0px 0px 15px #fff;box-shadow:0px 0px 15px #fff}.joyride-expose-cover{background:transparent;border-radius:3px;position:absolute;z-index:9999;top:0px;left:0px}@media only screen and (min-width: 48em){.joyride-tip-guide{width:300px;left:inherit}.joyride-tip-guide .joyride-nub.bottom{border-color:#000 !important;border-bottom-color:transparent !important;border-left-color:transparent !important;border-right-color:transparent !important;bottom:-28px}.joyride-tip-guide .joyride-nub.right{border-color:#000 !important;border-top-color:transparent !important;border-right-color:transparent !important;border-bottom-color:transparent !important;top:22px;left:auto;right:-28px}.joyride-tip-guide .joyride-nub.left{border-color:#000 !important;border-top-color:transparent !important;border-left-color:transparent !important;border-bottom-color:transparent !important;top:22px;left:-28px;right:auto}}[data-clearing]{*zoom:1;margin-bottom:0;list-style:none}[data-clearing]:before,[data-clearing]:after{content:" ";display:table}[data-clearing]:after{clear:both}[data-clearing] li{float:left;margin-right:10px}.clearing-blackout{background:#111;position:fixed;width:100%;height:100%;top:0;left:0;z-index:998}.clearing-blackout .clearing-close{display:block}.clearing-container{position:relative;z-index:998;height:100%;overflow:hidden;margin:0}.visible-img{height:95%;position:relative}.visible-img img{position:absolute;left:50%;top:50%;margin-left:-50%;max-height:100%;max-width:100%}.clearing-caption{color:#fff;line-height:1.3;margin-bottom:0;text-align:center;bottom:0;background:#111;width:100%;padding:10px 30px;position:absolute;left:0}.clearing-close{z-index:999;padding-left:20px;padding-top:10px;font-size:40px;line-height:1;color:#fff;display:none}.clearing-close:hover,.clearing-close:focus{color:#ccc}.clearing-assembled .clearing-container{height:100%}.clearing-assembled .clearing-container .carousel>ul{display:none}@media only screen and (min-width: 48em){.clearing-main-prev,.clearing-main-next{position:absolute;height:100%;width:40px;top:0}.clearing-main-prev>span,.clearing-main-next>span{position:absolute;top:50%;display:block;width:0;height:0;border:solid 16px}.clearing-main-prev{left:0}.clearing-main-prev>span{left:5px;border-color:transparent;border-right-color:#fff}.clearing-main-next{right:0}.clearing-main-next>span{border-color:transparent;border-left-color:#fff}.clearing-main-prev.disabled,.clearing-main-next.disabled{opacity:0.5}.clearing-feature ~ li{display:none}.clearing-assembled .clearing-container .carousel{background:#111;height:150px;margin-top:5px}.clearing-assembled .clearing-container .carousel>ul{display:block;z-index:999;width:200%;height:100%;margin-left:0;position:relative;left:0}.clearing-assembled .clearing-container .carousel>ul li{display:block;width:175px;height:inherit;padding:0;float:left;overflow:hidden;margin-right:1px;position:relative;cursor:pointer;opacity:0.4}.clearing-assembled .clearing-container .carousel>ul li.fix-height img{min-height:100%;height:100%;max-width:none}.clearing-assembled .clearing-container .carousel>ul li a.th{border:none;-webkit-box-shadow:none;box-shadow:none;display:block}.clearing-assembled .clearing-container .carousel>ul li img{cursor:pointer !important;min-width:100% !important}.clearing-assembled .clearing-container .carousel>ul li.visible{opacity:1}.clearing-assembled .clearing-container .visible-img{background:#111;overflow:hidden;height:75%}.clearing-close{position:absolute;top:10px;right:20px;padding-left:0;padding-top:0}}.alert-box{border-style:solid;border-width:1px;display:block;font-weight:bold;margin-bottom:1.25em;position:relative;padding:0.6875em 1.3125em 0.75em 0.6875em;font-size:0.875em;background-color:#2ba6cb;border-color:#2284a1;color:#fff}.alert-box .close{font-size:1.375em;padding:5px 4px 4px;line-height:0;position:absolute;top:0.4375em;right:0.3125em;color:#333;opacity:0.3}.alert-box .close:hover,.alert-box .close:focus{opacity:0.5}.alert-box.radius{-webkit-border-radius:3px;border-radius:3px}.alert-box.round{-webkit-border-radius:1000px;border-radius:1000px}.alert-box.success{background-color:#5da423;border-color:#457a1a;color:#fff}.alert-box.alert{background-color:#c60f13;border-color:#970b0e;color:#fff}.alert-box.secondary{background-color:#e9e9e9;border-color:#d0d0d0;color:#505050}.breadcrumbs{display:block;padding:0.5625em 0.875em 0.5625em;overflow:hidden;margin-left:0;list-style:none;border-style:solid;border-width:1px;background-color:#f6f6f6;border-color:#dcdcdc;-webkit-border-radius:3px;border-radius:3px}.breadcrumbs>*{margin:0;float:left;font-size:0.6875em;text-transform:uppercase;color:#2ba6cb}.breadcrumbs>*:hover a,.breadcrumbs>*:focus a{text-decoration:underline}.breadcrumbs>* a,.breadcrumbs>* span{text-transform:uppercase;color:#2ba6cb}.breadcrumbs>*.current{cursor:default;color:#333}.breadcrumbs>*.current a{cursor:default;color:#333}.breadcrumbs>*.current:hover,.breadcrumbs>*.current:hover a,.breadcrumbs>*.current:focus,.breadcrumbs>*.current:focus a{text-decoration:none}.breadcrumbs>*.unavailable{color:#999}.breadcrumbs>*.unavailable a{color:#999}.breadcrumbs>*.unavailable:hover,.breadcrumbs>*.unavailable:hover a,.breadcrumbs>*.unavailable:focus,.breadcrumbs>*.unavailable a:focus{text-decoration:none;color:#999;cursor:default}.breadcrumbs>*:before{content:"/";color:#aaa;margin:0 0.75em;position:relative;top:1px}.breadcrumbs>*:first-child:before{content:" ";margin:0}.keystroke,kbd{background-color:#ededed;border-color:#dbdbdb;color:#222;border-style:solid;border-width:1px;margin:0;font-family:"Consolas","Menlo","Courier",monospace;font-size:0.9375em;padding:0.125em 0.25em 0em;-webkit-border-radius:3px;border-radius:3px}.label{font-weight:bold;text-align:center;text-decoration:none;line-height:1;white-space:nowrap;display:inline-block;position:relative;padding:0.1875em 0.625em 0.25em;font-size:0.875em;background-color:#2ba6cb;color:#fff}.label.radius{-webkit-border-radius:3px;border-radius:3px}.label.round{-webkit-border-radius:1000px;border-radius:1000px}.label.alert{background-color:#c60f13;color:#fff}.label.success{background-color:#5da423;color:#fff}.label.secondary{background-color:#e9e9e9;color:#333}.inline-list{margin:0 auto 1.0625em auto;margin-left:-1.375em;margin-right:0;padding:0;list-style:none;overflow:hidden}.inline-list>li{list-style:none;float:left;margin-left:1.375em;display:block}.inline-list>li>*{display:block}.pagination{display:block;height:1.5em;margin-left:-0.3125em}.pagination li{display:block;float:left;height:1.5em;color:#222;font-size:0.875em;margin-left:0.3125em}.pagination li a{display:block;padding:0.0625em 0.4375em 0.0625em;color:#999}.pagination li:hover a,.pagination li a:focus{background:#e6e6e6}.pagination li.unavailable a{cursor:default;color:#999}.pagination li.unavailable:hover a,.pagination li.unavailable a:focus{background:transparent}.pagination li.current a{background:#2ba6cb;color:#fff;font-weight:bold;cursor:default}.pagination li.current a:hover,.pagination li.current a:focus{background:#2ba6cb}.pagination-centered{text-align:center}.pagination-centered ul>li{float:none;display:inline-block}.panel{border-style:solid;border-width:1px;border-color:#d9d9d9;margin-bottom:1.25em;padding:1.25em;background:#f2f2f2}.panel h1,.panel h2,.panel h3,.panel h4,.panel h5,.panel h6,.panel p{color:#333}.panel>:first-child{margin-top:0}.panel>:last-child{margin-bottom:0}.panel h1,.panel h2,.panel h3,.panel h4,.panel h5,.panel h6{line-height:1;margin-bottom:0.625em}.panel h1.subheader,.panel h2.subheader,.panel h3.subheader,.panel h4.subheader,.panel h5.subheader,.panel h6.subheader{line-height:1.4}.panel.callout{border-style:solid;border-width:1px;border-color:#2284a1;margin-bottom:1.25em;padding:1.25em;background:#2ba6cb;-webkit-box-shadow:0 1px 0 rgba(255,255,255,0.5) inset;box-shadow:0 1px 0 rgba(255,255,255,0.5) inset}.panel.callout h1,.panel.callout h2,.panel.callout h3,.panel.callout h4,.panel.callout h5,.panel.callout h6,.panel.callout p{color:#fff}.panel.callout>:first-child{margin-top:0}.panel.callout>:last-child{margin-bottom:0}.panel.callout h1,.panel.callout h2,.panel.callout h3,.panel.callout h4,.panel.callout h5,.panel.callout h6{line-height:1;margin-bottom:0.625em}.panel.callout h1.subheader,.panel.callout h2.subheader,.panel.callout h3.subheader,.panel.callout h4.subheader,.panel.callout h5.subheader,.panel.callout h6.subheader{line-height:1.4}.panel.radius{-webkit-border-radius:3px;border-radius:3px}.pricing-table{border:solid 1px #ddd;margin-left:0;margin-bottom:1.25em}.pricing-table *{list-style:none;line-height:1}.pricing-table .title{background-color:#ddd;padding:0.9375em 1.25em;text-align:center;color:#333;font-weight:bold;font-size:1em}.pricing-table .price{background-color:#eee;padding:0.9375em 1.25em;text-align:center;color:#333;font-weight:normal;font-size:1.25em}.pricing-table .description{background-color:#fff;padding:0.9375em;text-align:center;color:#777;font-size:0.75em;font-weight:normal;line-height:1.4;border-bottom:dotted 1px #ddd}.pricing-table .bullet-item{background-color:#fff;padding:0.9375em;text-align:center;color:#333;font-size:0.875em;font-weight:normal;border-bottom:dotted 1px #ddd}.pricing-table .cta-button{background-color:#f5f5f5;text-align:center;padding:1.25em 1.25em 0}.progress{background-color:transparent;height:1.5625em;border:1px solid #ccc;padding:0.125em;margin-bottom:0.625em}.progress .meter{background:#2ba6cb;height:100%;display:block}.progress.secondary .meter{background:#e9e9e9;height:100%;display:block}.progress.success .meter{background:#5da423;height:100%;display:block}.progress.alert .meter{background:#c60f13;height:100%;display:block}.progress.radius{-webkit-border-radius:3px;border-radius:3px}.progress.radius .meter{-webkit-border-radius:2px;border-radius:2px}.progress.round{-webkit-border-radius:1000px;border-radius:1000px}.progress.round .meter{-webkit-border-radius:999px;border-radius:999px}.side-nav{display:block;margin:0;padding:0.875em 0;list-style-type:none;list-style-position:inside}.side-nav li{margin:0 0 0.4375em 0;font-size:0.875em}.side-nav li a{display:block;color:#2ba6cb}.side-nav li.active>a:first-child{color:#4d4d4d;font-weight:bold}.side-nav li.divider{border-top:1px solid;height:0;padding:0;list-style:none;border-top-color:#e6e6e6}.sub-nav{display:block;width:auto;overflow:hidden;margin:-0.25em 0 1.125em;padding-top:0.25em;margin-right:0;margin-left:-0.5625em}.sub-nav dt,.sub-nav dd{float:left;display:inline;margin-left:0.5625em;margin-bottom:0.625em;font-weight:normal;font-size:0.875em}.sub-nav dt a,.sub-nav dd a{color:#999;text-decoration:none}.sub-nav dt.active a,.sub-nav dd.active a{-webkit-border-radius:1000px;border-radius:1000px;font-weight:bold;background:#2ba6cb;padding:0.1875em 0.5625em;cursor:default;color:#fff}@media only screen{div.switch{position:relative;width:100%;padding:0;display:block;overflow:hidden;border-style:solid;border-width:1px;margin-bottom:1.25em;-webkit-animation:webkitSiblingBugfix infinite 1s;height:36px;background:#fff;border-color:#ccc}div.switch label{position:relative;left:0;z-index:2;float:left;width:50%;height:100%;margin:0;font-weight:bold;text-align:left;-webkit-transition:all 0.1s ease-out;-moz-transition:all 0.1s ease-out;transition:all 0.1s ease-out}div.switch input{position:absolute;z-index:3;opacity:0;width:100%;height:100%;-moz-appearance:none}div.switch input:hover,div.switch input:focus{cursor:pointer}div.switch>span{position:absolute;top:-1px;left:-1px;z-index:1;display:block;padding:0;border-width:1px;border-style:solid;-webkit-transition:all 0.1s ease-out;-moz-transition:all 0.1s ease-out;transition:all 0.1s ease-out}div.switch input:not(:checked)+label{opacity:0}div.switch input:checked{display:none !important}div.switch input{left:0;display:block !important}div.switch input:first-of-type+label,div.switch input:first-of-type+span+label{left:-50%}div.switch input:first-of-type:checked+label,div.switch input:first-of-type:checked+span+label{left:0%}div.switch input:last-of-type+label,div.switch input:last-of-type+span+label{right:-50%;left:auto;text-align:right}div.switch input:last-of-type:checked+label,div.switch input:last-of-type:checked+span+label{right:0%;left:auto}div.switch span.custom{display:none !important}div.switch label{padding:0 0.375em;line-height:2.3em;font-size:0.875em}div.switch input:first-of-type:checked ~ span{left:100%;margin-left:-2.1875em}div.switch>span{width:2.25em;height:2.25em}div.switch>span{border-color:#b3b3b3;background:#fff;background:-moz-linear-gradient(top, #fff 0%, #f2f2f2 100%);background:-webkit-linear-gradient(top, #fff 0%, #f2f2f2 100%);background:linear-gradient(to bottom, #fff 0%, #f2f2f2 100%);-webkit-box-shadow:2px 0 10px 0 rgba(0,0,0,0.07),1000px 0 0 1000px #e1f5d1,-2px 0 10px 0 rgba(0,0,0,0.07),-1000px 0 0 1000px #f5f5f5;box-shadow:2px 0 10px 0 rgba(0,0,0,0.07),1000px 0 0 980px #e1f5d1,-2px 0 10px 0 rgba(0,0,0,0.07),-1000px 0 0 1000px #f5f5f5}div.switch:hover>span,div.switch:focus>span{background:#fff;background:-moz-linear-gradient(top, #fff 0%, #e6e6e6 100%);background:-webkit-linear-gradient(top, #fff 0%, #e6e6e6 100%);background:linear-gradient(to bottom, #fff 0%, #e6e6e6 100%)}div.switch:active{background:transparent}div.switch.large{height:44px}div.switch.large label{padding:0 0.375em;line-height:2.3em;font-size:1.0625em}div.switch.large input:first-of-type:checked ~ span{left:100%;margin-left:-2.6875em}div.switch.large>span{width:2.75em;height:2.75em}div.switch.small{height:28px}div.switch.small label{padding:0 0.375em;line-height:2.1em;font-size:0.75em}div.switch.small input:first-of-type:checked ~ span{left:100%;margin-left:-1.6875em}div.switch.small>span{width:1.75em;height:1.75em}div.switch.tiny{height:22px}div.switch.tiny label{padding:0 0.375em;line-height:1.9em;font-size:0.6875em}div.switch.tiny input:first-of-type:checked ~ span{left:100%;margin-left:-1.3125em}div.switch.tiny>span{width:1.375em;height:1.375em}div.switch.radius{-webkit-border-radius:4px;border-radius:4px}div.switch.radius>span{-webkit-border-radius:3px;border-radius:3px}div.switch.round{-webkit-border-radius:1000px;border-radius:1000px}div.switch.round>span{-webkit-border-radius:999px;border-radius:999px}div.switch.round label{padding:0 0.5625em}@-webkit-keyframes webkitSiblingBugfix{from{position:relative}to{position:relative}}}[data-magellan-expedition]{background:#fff;z-index:50;min-width:100%;padding:10px}[data-magellan-expedition] .sub-nav{margin-bottom:0}[data-magellan-expedition] .sub-nav dd{margin-bottom:0}table{background:#fff;margin-bottom:1.25em;border:solid 1px #ddd}table thead,table tfoot{background:#f5f5f5;font-weight:bold}table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:0.5em 0.625em 0.625em;font-size:0.875em;color:#222;text-align:left}table tr th,table tr td{padding:0.5625em 0.625em;font-size:0.875em;color:#222}table tr.even,table tr.alt,table tr:nth-of-type(even){background:#f9f9f9}table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{display:table-cell;line-height:1.125em}.th{line-height:0;display:inline-block;border:solid 4px #fff;-webkit-box-shadow:0 0 0 1px rgba(0,0,0,0.2);box-shadow:0 0 0 1px rgba(0,0,0,0.2);-webkit-transition:all 200ms ease-out;-moz-transition:all 200ms ease-out;transition:all 200ms ease-out}.th:hover,.th:focus{-webkit-box-shadow:0 0 6px 1px rgba(43,166,203,0.5);box-shadow:0 0 6px 1px rgba(43,166,203,0.5)}.th.radius{-webkit-border-radius:3px;border-radius:3px}a.th{display:block}.has-tip{border-bottom:dotted 1px #ccc;cursor:help;font-weight:bold;color:#333}.has-tip:hover,.has-tip:focus{border-bottom:dotted 1px #196177;color:#2ba6cb}.has-tip.tip-left,.has-tip.tip-right{float:none !important}.tooltip{display:none;position:absolute;z-index:999;font-weight:bold;font-size:0.9375em;line-height:1.3;padding:0.5em;max-width:85%;left:50%;width:100%;color:#fff;background:#000;-webkit-border-radius:3px;border-radius:3px}.tooltip>.nub{display:block;left:5px;position:absolute;width:0;height:0;border:solid 5px;border-color:transparent transparent #000 transparent;top:-10px}.tooltip.opened{color:#2ba6cb !important;border-bottom:dotted 1px #196177 !important}.tap-to-close{display:block;font-size:0.625em;color:#888;font-weight:normal}@media only screen and (min-width: 48em){.tooltip>.nub{border-color:transparent transparent #000 transparent;top:-10px}.tooltip.tip-top>.nub{border-color:#000 transparent transparent transparent;top:auto;bottom:-10px}.tooltip.tip-left,.tooltip.tip-right{float:none !important}.tooltip.tip-left>.nub{border-color:transparent transparent transparent #000;right:-10px;left:auto;top:50%;margin-top:-5px}.tooltip.tip-right>.nub{border-color:transparent #000 transparent transparent;right:auto;left:-10px;top:50%;margin-top:-5px}}@media only screen and (max-width: 767px){.f-dropdown{max-width:100%;left:0}}.f-dropdown{position:absolute;top:-9999px;list-style:none;padding:1.25em;width:100%;height:auto;max-height:none;background:#fff;border:solid 1px #ccc;font-size:16px;z-index:99;margin-top:2px;max-width:200px}.f-dropdown *:first-child{margin-top:0}.f-dropdown *:last-child{margin-bottom:0}.f-dropdown:before{content:"";display:block;width:0;height:0;border:inset 6px;border-color:transparent transparent #fff transparent;border-bottom-style:solid;position:absolute;top:-12px;left:10px;z-index:99}.f-dropdown:after{content:"";display:block;width:0;height:0;border:inset 7px;border-color:transparent transparent #ccc transparent;border-bottom-style:solid;position:absolute;top:-14px;left:9px;z-index:98}.f-dropdown.right:before{left:auto;right:10px}.f-dropdown.right:after{left:auto;right:9px}.f-dropdown li{font-size:0.875em;cursor:pointer;line-height:1.125em;margin:0}.f-dropdown li:hover,.f-dropdown li:focus{background:#eee}.f-dropdown li a{display:block;padding:0.3125em 0.625em;color:#555}.f-dropdown.content{position:absolute;top:-9999px;list-style:none;padding:1.25em;width:100%;height:auto;max-height:none;background:#fff;border:solid 1px #ccc;font-size:16px;z-index:99;max-width:200px}.f-dropdown.content *:first-child{margin-top:0}.f-dropdown.content *:last-child{margin-bottom:0}.f-dropdown.tiny{max-width:200px}.f-dropdown.small{max-width:300px}.f-dropdown.medium{max-width:500px}.f-dropdown.large{max-width:800px} diff --git a/assets/css/normalize.min.css b/assets/css/normalize.min.css new file mode 100644 index 0000000..791c63b --- /dev/null +++ b/assets/css/normalize.min.css @@ -0,0 +1 @@ +/*! normalize.css v2.1.1 | MIT License | git.io/normalize */ article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{background:#fff;color:#000;font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em}pre{white-space:pre-wrap}q{quotes:"\201C" "\201D" "\2018" "\2019"}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:0}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}button,input{line-height:normal}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0} \ No newline at end of file diff --git a/assets/css/style.css b/assets/css/style.css new file mode 100644 index 0000000..dc15003 --- /dev/null +++ b/assets/css/style.css @@ -0,0 +1,179 @@ +html { + background: #cbc395 url('../img/tausta.jpg'); + height: 100%; + margin: 0px; + padding: 0px; +} + +body { + color: #333; + background: transparent url('../img/headerbg2.png') top center no-repeat; +} + +body footer { + padding-top: 30px; +} +body footer .panel { + font-size: 11px; +} + +h1, h2, h3 { + font-family: Georgia, 'Sans serif'; +} + +.top-bar-section .adminmenu > a { + background-color: #333 !important; +} +.top-bar-section .adminmenu a.logout { + background-color: #900 !important; +} + +.progress span { + display: block; + padding: 4px 5px; + font-size: 11px; + overflow: hidden; + color: #fff; +} + +.progress span.queue { + background: #111; +} +.progress span.public { + background: #333; +} +.progress span.private { + background: #555; +} +.progress span.hidden { + background: #777; +} + +.clear { + clear: both; + overflow: auto; +} + +.loginlink { + float: right; + font-size: 12px; + color: #aaa; +} + +.headerlogo { + text-align: center; + padding-right: 20px; + margin-bottom: 0px; +} + +.backgroundfix { + background: #fff; +} + +.panel { + border-color: transparent; +} + +.image-panel a { + color: #333; + font-size: 11px; +} + +#ecard_form label.error { + margin-top: -10px; + padding-bottom: 20px; + clear: both; + overflow: auto; +} + +#message_text { + width: 100%; + max-width: 100%; + min-width: 100%; + height: 200px; + min-height: 200px; + max-height: 500px; +} + +#previewpanel { + border: 1px solid #333; + position: relative; + height: 350px; +} + +#previewpanel, .previewpanelclear { + clear: both; + overflow: auto; +} + +#previewimage { + position:absolute; + top: 0px; + left: 0px; + border: 1px solid #333; + z-index: 1; +} + +.postcard_preview { + width: 100%; + height: 300px; + background: #333; +} + +#message_title_preview { + font-size: 30px; + font-weight: bold; +} +#message_text_preview { + font-size: 18px; +} + +#message_title_preview, +#message_text_preview { + position: relative; + z-index: 2; + color: #fff; + text-shadow: 0px 0px 10px rgba(150, 150, 150, 1); + width: 90%; + cursor: move; + border: 1px dashed #666; +} +#message_title_preview { + top: 20px; + left: 10px; +} +#message_text_preview { + top: 40px; + left: 10px; + white-space: pre; +} + +.ui-draggable-dragging, +.ui-resizable-resizing { + background: #fff; + background: rgba(255, 255, 255, 0.5); +} + +ul.thumbnails.image_picker_selector { + overflow: auto; + list-style-image: none; + list-style-position: outside; + list-style-type: none; + padding: 0px; + margin: 0px; +} +ul.thumbnails.image_picker_selector li { + margin: 0px 3px 3px 0px; + float: left; +} +ul.thumbnails.image_picker_selector li .thumbnail { + padding: 3px; + border: 1px solid #dddddd; + width: 90px; + height: 71px; +} +ul.thumbnails.image_picker_selector li .thumbnail.selected { + background: #0088cc; + width: 90px; + height: 71px; +} \ No newline at end of file diff --git a/assets/img/headerbg2.png b/assets/img/headerbg2.png new file mode 100644 index 0000000000000000000000000000000000000000..42cd4bbb99ff9ac0e9dc3a6446fb40ace02f2b72 GIT binary patch literal 129251 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

CE9LJF;yZF12B%>%Yr3q6t8G)^ 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 LNkvXXu0mjfvj1>9 literal 0 HcmV?d00001 diff --git a/assets/img/tausta.jpg b/assets/img/tausta.jpg new file mode 100644 index 0000000000000000000000000000000000000000..92f963472c0daeff1c74841b29ffd7fc244b14ca GIT binary patch literal 48008 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 literal 0 HcmV?d00001 diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..5c40759ba2070469d6ef5abdd6e5748c04cc5b50 GIT binary patch literal 3794 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 literal 0 HcmV?d00001 diff --git a/index.php b/index.php new file mode 100644 index 0000000..0cab8ab --- /dev/null +++ b/index.php @@ -0,0 +1,213 @@ +marker[$name] = microtime(); + } + + // -------------------------------------------------------------------- + + /** + * 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. + * + * @access public + * @param string a particular marked point + * @param string a particular marked point + * @param integer the number of decimal places + * @return mixed + */ + 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(); + } + + list($sm, $ss) = explode(' ', $this->marker[$point1]); + list($em, $es) = explode(' ', $this->marker[$point2]); + + return number_format(($em + $es) - ($sm + $ss), $decimals); + } + + // -------------------------------------------------------------------- + + /** + * Memory Usage + * + * This function returns the {memory_usage} pseudo-variable. + * 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. + * + * @access public + * @return string + */ + function memory_usage() + { + return '{memory_usage}'; + } + +} + +// END CI_Benchmark class + +/* End of file Benchmark.php */ +/* Location: ./system/core/Benchmark.php */ \ No newline at end of file diff --git a/system/core/CodeIgniter.php b/system/core/CodeIgniter.php new file mode 100755 index 0000000..c16c79c --- /dev/null +++ b/system/core/CodeIgniter.php @@ -0,0 +1,402 @@ + $assign_to_config['subclass_prefix'])); + } + +/* + * ------------------------------------------------------ + * Set a liberal script execution time limit + * ------------------------------------------------------ + */ + if (function_exists("set_time_limit") == TRUE AND @ini_get("safe_mode") == 0) + { + @set_time_limit(300); + } + +/* + * ------------------------------------------------------ + * 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 + * ------------------------------------------------------ + */ + $CFG =& load_class('Config', 'core'); + + // Do we have any manually set config items in the index.php file? + if (isset($assign_to_config)) + { + $CFG->_assign_to_config($assign_to_config); + } + +/* + * ------------------------------------------------------ + * Instantiate the UTF-8 class + * ------------------------------------------------------ + * + * Note: Order here is rather important as the UTF-8 + * class needs to be used very early on, but it cannot + * properly determine if UTf-8 can be supported until + * after the Config class is instantiated. + * + */ + + $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'); + $RTR->_set_routing(); + + // Set any routing overrides that may exist in the main index file + if (isset($routing)) + { + $RTR->_set_overrides($routing); + } + +/* + * ------------------------------------------------------ + * 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) + { + if ($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 BASEPATH.'core/Controller.php'; + + function &get_instance() + { + return CI_Controller::get_instance(); + } + + + if (file_exists(APPPATH.'core/'.$CFG->config['subclass_prefix'].'Controller.php')) + { + require APPPATH.'core/'.$CFG->config['subclass_prefix'].'Controller.php'; + } + + // Load the local application controller + // Note: The Router class automatically validates the controller path using the router->_validate_request(). + // If this include fails it means that the default controller in the Routes.php file is not resolving to something valid. + if ( ! file_exists(APPPATH.'controllers/'.$RTR->fetch_directory().$RTR->fetch_class().'.php')) + { + show_error('Unable to load your default controller. Please make sure the controller specified in your Routes.php file is valid.'); + } + + include(APPPATH.'controllers/'.$RTR->fetch_directory().$RTR->fetch_class().'.php'); + + // Set a mark point for benchmarking + $BM->mark('loading_time:_base_classes_end'); + +/* + * ------------------------------------------------------ + * Security check + * ------------------------------------------------------ + * + * None of the functions in the app controller or the + * loader class can be called via the URI, nor can + * controller functions that begin with an underscore + */ + $class = $RTR->fetch_class(); + $method = $RTR->fetch_method(); + + if ( ! class_exists($class) + OR strncmp($method, '_', 1) == 0 + OR in_array(strtolower($method), array_map('strtolower', get_class_methods('CI_Controller'))) + ) + { + if ( ! empty($RTR->routes['404_override'])) + { + $x = explode('/', $RTR->routes['404_override']); + $class = $x[0]; + $method = (isset($x[1]) ? $x[1] : 'index'); + if ( ! class_exists($class)) + { + if ( ! file_exists(APPPATH.'controllers/'.$class.'.php')) + { + show_404("{$class}/{$method}"); + } + + include_once(APPPATH.'controllers/'.$class.'.php'); + } + } + else + { + show_404("{$class}/{$method}"); + } + } + +/* + * ------------------------------------------------------ + * 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 + * ------------------------------------------------------ + */ + // Is there a "remap" function? If so, we call it instead + if (method_exists($CI, '_remap')) + { + $CI->_remap($method, array_slice($URI->rsegments, 2)); + } + else + { + // is_callable() returns TRUE on some versions of PHP 5 for private and protected + // methods, so we'll use this workaround for consistent behavior + if ( ! in_array(strtolower($method), array_map('strtolower', get_class_methods($CI)))) + { + // Check and see if we are using a 404 override and use it. + if ( ! empty($RTR->routes['404_override'])) + { + $x = explode('/', $RTR->routes['404_override']); + $class = $x[0]; + $method = (isset($x[1]) ? $x[1] : 'index'); + if ( ! class_exists($class)) + { + if ( ! file_exists(APPPATH.'controllers/'.$class.'.php')) + { + show_404("{$class}/{$method}"); + } + + include_once(APPPATH.'controllers/'.$class.'.php'); + unset($CI); + $CI = new $class(); + } + } + else + { + show_404("{$class}/{$method}"); + } + } + + // Call the requested method. + // Any URI segments present (besides the class/function) will be passed to the method for convenience + call_user_func_array(array(&$CI, $method), array_slice($URI->rsegments, 2)); + } + + + // 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'); + +/* + * ------------------------------------------------------ + * Close the DB connection if one exists + * ------------------------------------------------------ + */ + if (class_exists('CI_DB') AND isset($CI->db)) + { + $CI->db->close(); + } + + +/* End of file CodeIgniter.php */ +/* Location: ./system/core/CodeIgniter.php */ \ No newline at end of file diff --git a/system/core/Common.php b/system/core/Common.php new file mode 100644 index 0000000..07534c5 --- /dev/null +++ b/system/core/Common.php @@ -0,0 +1,564 @@ + 5 +* we'll set a static variable. +* +* @access public +* @param string +* @return bool TRUE if the current version is $version or higher +*/ +if ( ! function_exists('is_php')) +{ + function is_php($version = '5.0.0') + { + static $_is_php; + $version = (string)$version; + + if ( ! isset($_is_php[$version])) + { + $_is_php[$version] = (version_compare(PHP_VERSION, $version) < 0) ? FALSE : TRUE; + } + + return $_is_php[$version]; + } +} + +// ------------------------------------------------------------------------ + +/** + * 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. + * + * @access private + * @return void + */ +if ( ! function_exists('is_really_writable')) +{ + function is_really_writable($file) + { + // If we're on a Unix server with safe_mode off we call is_writable + if (DIRECTORY_SEPARATOR == '/' AND @ini_get("safe_mode") == FALSE) + { + 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(1,100).mt_rand(1,100)); + + if (($fp = @fopen($file, FOPEN_WRITE_CREATE)) === FALSE) + { + return FALSE; + } + + fclose($fp); + @chmod($file, DIR_WRITE_MODE); + @unlink($file); + return TRUE; + } + elseif ( ! is_file($file) OR ($fp = @fopen($file, FOPEN_WRITE_CREATE)) === FALSE) + { + return FALSE; + } + + fclose($fp); + return TRUE; + } +} + +// ------------------------------------------------------------------------ + +/** +* 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. +* +* @access public +* @param string the class name being requested +* @param string the directory where the class should be found +* @param string the class name prefix +* @return object +*/ +if ( ! function_exists('load_class')) +{ + function &load_class($class, $directory = 'libraries', $prefix = 'CI_') + { + 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 = $prefix.$class; + + if (class_exists($name) === FALSE) + { + require($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) + { + require(APPPATH.$directory.'/'.config_item('subclass_prefix').$class.'.php'); + } + } + + // Did we find the class? + if ($name === FALSE) + { + // Note: We use exit() rather then show_error() in order to avoid a + // self-referencing loop with the Excptions class + exit('Unable to locate the specified class: '.$class.'.php'); + } + + // Keep track of what we just loaded + is_loaded($class); + + $_classes[$class] = new $name(); + return $_classes[$class]; + } +} + +// -------------------------------------------------------------------- + +/** +* Keeps track of which libraries have been loaded. This function is +* called by the load_class() function above +* +* @access public +* @return array +*/ +if ( ! function_exists('is_loaded')) +{ + function &is_loaded($class = '') + { + static $_is_loaded = array(); + + if ($class != '') + { + $_is_loaded[strtolower($class)] = $class; + } + + return $_is_loaded; + } +} + +// ------------------------------------------------------------------------ + +/** +* Loads the main config.php file +* +* This function lets us grab the config file even if the Config class +* hasn't been instantiated yet +* +* @access private +* @return array +*/ +if ( ! function_exists('get_config')) +{ + function &get_config($replace = array()) + { + static $_config; + + if (isset($_config)) + { + return $_config[0]; + } + + // Is the config file in the environment folder? + if ( ! defined('ENVIRONMENT') OR ! file_exists($file_path = APPPATH.'config/'.ENVIRONMENT.'/config.php')) + { + $file_path = APPPATH.'config/config.php'; + } + + // Fetch the config file + if ( ! file_exists($file_path)) + { + exit('The configuration file does not exist.'); + } + + require($file_path); + + // Does the $config array exist in the file? + if ( ! isset($config) OR ! is_array($config)) + { + exit('Your config file does not appear to be formatted correctly.'); + } + + // Are any values being dynamically replaced? + if (count($replace) > 0) + { + foreach ($replace as $key => $val) + { + if (isset($config[$key])) + { + $config[$key] = $val; + } + } + } + + return $_config[0] =& $config; + } +} + +// ------------------------------------------------------------------------ + +/** +* Returns the specified config item +* +* @access public +* @return mixed +*/ +if ( ! function_exists('config_item')) +{ + function config_item($item) + { + static $_config_item = array(); + + if ( ! isset($_config_item[$item])) + { + $config =& get_config(); + + if ( ! isset($config[$item])) + { + return FALSE; + } + $_config_item[$item] = $config[$item]; + } + + return $_config_item[$item]; + } +} + +// ------------------------------------------------------------------------ + +/** +* Error Handler +* +* This function lets us invoke the exception class and +* display errors using the standard error template located +* in application/errors/errors.php +* This function will send the error page directly to the +* browser and exit. +* +* @access public +* @return void +*/ +if ( ! function_exists('show_error')) +{ + function show_error($message, $status_code = 500, $heading = 'An Error Was Encountered') + { + $_error =& load_class('Exceptions', 'core'); + echo $_error->show_error($heading, $message, 'error_general', $status_code); + exit; + } +} + +// ------------------------------------------------------------------------ + +/** +* 404 Page Handler +* +* This function is similar to the show_error() function above +* However, instead of the standard error template it displays +* 404 errors. +* +* @access public +* @return void +*/ +if ( ! function_exists('show_404')) +{ + function show_404($page = '', $log_error = TRUE) + { + $_error =& load_class('Exceptions', 'core'); + $_error->show_404($page, $log_error); + exit; + } +} + +// ------------------------------------------------------------------------ + +/** +* Error Logging Interface +* +* We use this as a simple mechanism to access the logging +* class and send messages to be logged. +* +* @access public +* @return void +*/ +if ( ! function_exists('log_message')) +{ + function log_message($level = 'error', $message, $php_error = FALSE) + { + static $_log; + + if (config_item('log_threshold') == 0) + { + return; + } + + $_log =& load_class('Log'); + $_log->write_log($level, $message, $php_error); + } +} + +// ------------------------------------------------------------------------ + +/** + * Set HTTP Status Header + * + * @access public + * @param int the status code + * @param string + * @return void + */ +if ( ! function_exists('set_status_header')) +{ + function set_status_header($code = 200, $text = '') + { + $stati = array( + 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', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 307 => 'Temporary Redirect', + + 400 => 'Bad Request', + 401 => 'Unauthorized', + 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', + + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported' + ); + + if ($code == '' OR ! is_numeric($code)) + { + show_error('Status codes must be numeric', 500); + } + + if (isset($stati[$code]) AND $text == '') + { + $text = $stati[$code]; + } + + if ($text == '') + { + show_error('No status text available. Please check your status code number or supply your own message text.', 500); + } + + $server_protocol = (isset($_SERVER['SERVER_PROTOCOL'])) ? $_SERVER['SERVER_PROTOCOL'] : FALSE; + + if (substr(php_sapi_name(), 0, 3) == 'cgi') + { + header("Status: {$code} {$text}", TRUE); + } + elseif ($server_protocol == 'HTTP/1.1' OR $server_protocol == 'HTTP/1.0') + { + header($server_protocol." {$code} {$text}", TRUE, $code); + } + else + { + header("HTTP/1.1 {$code} {$text}", TRUE, $code); + } + } +} + +// -------------------------------------------------------------------- + +/** +* Exception Handler +* +* This is the custom exception handler that is declaired at the top +* of Codeigniter.php. The main reason we use this is to permit +* 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. +* +* @access private +* @return void +*/ +if ( ! function_exists('_exception_handler')) +{ + function _exception_handler($severity, $message, $filepath, $line) + { + // We don't bother with "strict" notices since they tend to fill up + // the log file with excess information that isn't normally very helpful. + // For example, if you are running PHP 5 and you use version 4 style + // class functions (without prefixes like "public", "private", etc.) + // you'll get notices telling you that these have been deprecated. + if ($severity == E_STRICT) + { + return; + } + + $_error =& load_class('Exceptions', 'core'); + + // Should we display 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) + { + $_error->show_php_error($severity, $message, $filepath, $line); + } + + // Should we log the error? No? We're done... + if (config_item('log_threshold') == 0) + { + return; + } + + $_error->log_exception($severity, $message, $filepath, $line); + } +} + +// -------------------------------------------------------------------- + +/** + * Remove Invisible Characters + * + * This prevents sandwiching null characters + * between ascii characters, like Java\0script. + * + * @access public + * @param string + * @return string + */ +if ( ! function_exists('remove_invisible_characters')) +{ + 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]/'; // url encoded 00-08, 11, 12, 14, 15 + $non_displayables[] = '/%1[0-9a-f]/'; // url encoded 16-31 + } + + $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; + } +} + +// ------------------------------------------------------------------------ + +/** +* Returns HTML escaped variable +* +* @access public +* @param mixed +* @return mixed +*/ +if ( ! function_exists('html_escape')) +{ + function html_escape($var) + { + if (is_array($var)) + { + return array_map('html_escape', $var); + } + else + { + return htmlspecialchars($var, ENT_QUOTES, config_item('charset')); + } + } +} + +/* End of file Common.php */ +/* Location: ./system/core/Common.php */ \ No newline at end of file diff --git a/system/core/Config.php b/system/core/Config.php new file mode 100755 index 0000000..5dffbf3 --- /dev/null +++ b/system/core/Config.php @@ -0,0 +1,379 @@ +config =& get_config(); + log_message('debug', "Config Class Initialized"); + + // Set the base_url automatically if none was provided + if ($this->config['base_url'] == '') + { + if (isset($_SERVER['HTTP_HOST'])) + { + $base_url = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off' ? 'https' : 'http'; + $base_url .= '://'. $_SERVER['HTTP_HOST']; + $base_url .= str_replace(basename($_SERVER['SCRIPT_NAME']), '', $_SERVER['SCRIPT_NAME']); + } + + else + { + $base_url = 'http://localhost/'; + } + + $this->set_item('base_url', $base_url); + } + } + + // -------------------------------------------------------------------- + + /** + * Load Config File + * + * @access public + * @param string the config file name + * @param boolean if configuration values should be loaded into their own section + * @param boolean true if errors should just return false, false if an error message should be displayed + * @return boolean if the file was loaded correctly + */ + function load($file = '', $use_sections = FALSE, $fail_gracefully = FALSE) + { + $file = ($file == '') ? 'config' : str_replace('.php', '', $file); + $found = FALSE; + $loaded = FALSE; + + $check_locations = defined('ENVIRONMENT') + ? array(ENVIRONMENT.'/'.$file, $file) + : array($file); + + foreach ($this->_config_paths as $path) + { + foreach ($check_locations as $location) + { + $file_path = $path.'config/'.$location.'.php'; + + if (in_array($file_path, $this->is_loaded, TRUE)) + { + $loaded = TRUE; + continue 2; + } + + if (file_exists($file_path)) + { + $found = TRUE; + break; + } + } + + if ($found === FALSE) + { + 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) + { + if (isset($this->config[$file])) + { + $this->config[$file] = array_merge($this->config[$file], $config); + } + else + { + $this->config[$file] = $config; + } + } + else + { + $this->config = array_merge($this->config, $config); + } + + $this->is_loaded[] = $file_path; + unset($config); + + $loaded = TRUE; + log_message('debug', 'Config file loaded: '.$file_path); + break; + } + + if ($loaded === FALSE) + { + if ($fail_gracefully === TRUE) + { + return FALSE; + } + show_error('The configuration file '.$file.'.php does not exist.'); + } + + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Fetch a config file item + * + * + * @access public + * @param string the config item name + * @param string the index name + * @param bool + * @return string + */ + function item($item, $index = '') + { + if ($index == '') + { + if ( ! isset($this->config[$item])) + { + return FALSE; + } + + $pref = $this->config[$item]; + } + else + { + if ( ! isset($this->config[$index])) + { + return FALSE; + } + + if ( ! isset($this->config[$index][$item])) + { + return FALSE; + } + + $pref = $this->config[$index][$item]; + } + + return $pref; + } + + // -------------------------------------------------------------------- + + /** + * Fetch a config file item - adds slash after item (if item is not empty) + * + * @access public + * @param string the config item name + * @param bool + * @return string + */ + function slash_item($item) + { + if ( ! isset($this->config[$item])) + { + return FALSE; + } + if( trim($this->config[$item]) == '') + { + return ''; + } + + return rtrim($this->config[$item], '/').'/'; + } + + // -------------------------------------------------------------------- + + /** + * Site URL + * Returns base_url . index_page [. uri_string] + * + * @access public + * @param string the URI string + * @return string + */ + function site_url($uri = '') + { + if ($uri == '') + { + return $this->slash_item('base_url').$this->item('index_page'); + } + + if ($this->item('enable_query_strings') == FALSE) + { + $suffix = ($this->item('url_suffix') == FALSE) ? '' : $this->item('url_suffix'); + return $this->slash_item('base_url').$this->slash_item('index_page').$this->_uri_string($uri).$suffix; + } + else + { + return $this->slash_item('base_url').$this->item('index_page').'?'.$this->_uri_string($uri); + } + } + + // ------------------------------------------------------------- + + /** + * Base URL + * Returns base_url [. uri_string] + * + * @access public + * @param string $uri + * @return string + */ + function base_url($uri = '') + { + return $this->slash_item('base_url').ltrim($this->_uri_string($uri), '/'); + } + + // ------------------------------------------------------------- + + /** + * Build URI string for use in Config::site_url() and Config::base_url() + * + * @access protected + * @param $uri + * @return string + */ + protected function _uri_string($uri) + { + if ($this->item('enable_query_strings') == FALSE) + { + if (is_array($uri)) + { + $uri = implode('/', $uri); + } + $uri = trim($uri, '/'); + } + else + { + if (is_array($uri)) + { + $i = 0; + $str = ''; + foreach ($uri as $key => $val) + { + $prefix = ($i == 0) ? '' : '&'; + $str .= $prefix.$key.'='.$val; + $i++; + } + $uri = $str; + } + } + return $uri; + } + + // -------------------------------------------------------------------- + + /** + * System URL + * + * @access public + * @return string + */ + function system_url() + { + $x = explode("/", preg_replace("|/*(.+?)/*$|", "\\1", BASEPATH)); + return $this->slash_item('base_url').end($x).'/'; + } + + // -------------------------------------------------------------------- + + /** + * Set a config file item + * + * @access public + * @param string the config item key + * @param string the config item value + * @return void + */ + function set_item($item, $value) + { + $this->config[$item] = $value; + } + + // -------------------------------------------------------------------- + + /** + * Assign to Config + * + * This function is called by the front controller (CodeIgniter.php) + * after the Config class is instantiated. It permits config items + * to be assigned or overriden by variables contained in the index.php file + * + * @access private + * @param array + * @return void + */ + function _assign_to_config($items = array()) + { + if (is_array($items)) + { + foreach ($items as $key => $val) + { + $this->set_item($key, $val); + } + } + } +} + +// END CI_Config class + +/* End of file Config.php */ +/* Location: ./system/core/Config.php */ diff --git a/system/core/Controller.php b/system/core/Controller.php new file mode 100644 index 0000000..fddb81e --- /dev/null +++ b/system/core/Controller.php @@ -0,0 +1,64 @@ + $class) + { + $this->$var =& load_class($class); + } + + $this->load =& load_class('Loader', 'core'); + + $this->load->initialize(); + + log_message('debug', "Controller Class Initialized"); + } + + public static function &get_instance() + { + return self::$instance; + } +} +// END Controller class + +/* End of file Controller.php */ +/* Location: ./system/core/Controller.php */ \ No newline at end of file diff --git a/system/core/Exceptions.php b/system/core/Exceptions.php new file mode 100755 index 0000000..869739a --- /dev/null +++ b/system/core/Exceptions.php @@ -0,0 +1,193 @@ + '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' + ); + + + /** + * Constructor + */ + public function __construct() + { + $this->ob_level = ob_get_level(); + // Note: Do not log messages from this constructor. + } + + // -------------------------------------------------------------------- + + /** + * Exception Logger + * + * This function logs PHP generated error messages + * + * @access private + * @param string the error severity + * @param string the error string + * @param string the error filepath + * @param string the error line number + * @return string + */ + function log_exception($severity, $message, $filepath, $line) + { + $severity = ( ! isset($this->levels[$severity])) ? $severity : $this->levels[$severity]; + + log_message('error', 'Severity: '.$severity.' --> '.$message. ' '.$filepath.' '.$line, TRUE); + } + + // -------------------------------------------------------------------- + + /** + * 404 Page Not Found Handler + * + * @access private + * @param string the page + * @param bool log error yes/no + * @return string + */ + function show_404($page = '', $log_error = TRUE) + { + $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', '404 Page Not Found --> '.$page); + } + + echo $this->show_error($heading, $message, 'error_404', 404); + exit; + } + + // -------------------------------------------------------------------- + + /** + * General Error Page + * + * This function takes an error message as input + * (either as a string or an array) and displays + * it using the specified template. + * + * @access private + * @param string the heading + * @param string the message + * @param string the template name + * @param int the status code + * @return string + */ + function show_error($heading, $message, $template = 'error_general', $status_code = 500) + { + set_status_header($status_code); + + $message = '

'.implode('

', ( ! is_array($message)) ? array($message) : $message).'

'; + + if (ob_get_level() > $this->ob_level + 1) + { + ob_end_flush(); + } + ob_start(); + include(APPPATH.'errors/'.$template.'.php'); + $buffer = ob_get_contents(); + ob_end_clean(); + return $buffer; + } + + // -------------------------------------------------------------------- + + /** + * Native PHP error handler + * + * @access private + * @param string the error severity + * @param string the error string + * @param string the error filepath + * @param string the error line number + * @return string + */ + function show_php_error($severity, $message, $filepath, $line) + { + $severity = ( ! isset($this->levels[$severity])) ? $severity : $this->levels[$severity]; + + $filepath = str_replace("\\", "/", $filepath); + + // For safety reasons we do not show the full file path + if (FALSE !== strpos($filepath, '/')) + { + $x = explode('/', $filepath); + $filepath = $x[count($x)-2].'/'.end($x); + } + + if (ob_get_level() > $this->ob_level + 1) + { + ob_end_flush(); + } + ob_start(); + include(APPPATH.'errors/error_php.php'); + $buffer = ob_get_contents(); + ob_end_clean(); + echo $buffer; + } + + +} +// END Exceptions Class + +/* End of file Exceptions.php */ +/* Location: ./system/core/Exceptions.php */ \ No newline at end of file diff --git a/system/core/Hooks.php b/system/core/Hooks.php new file mode 100755 index 0000000..33f1c03 --- /dev/null +++ b/system/core/Hooks.php @@ -0,0 +1,248 @@ +_initialize(); + log_message('debug', "Hooks Class Initialized"); + } + + // -------------------------------------------------------------------- + + /** + * Initialize the Hooks Preferences + * + * @access private + * @return void + */ + function _initialize() + { + $CFG =& load_class('Config', 'core'); + + // 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 there are no hooks, we're done. + + if (defined('ENVIRONMENT') AND is_file(APPPATH.'config/'.ENVIRONMENT.'/hooks.php')) + { + include(APPPATH.'config/'.ENVIRONMENT.'/hooks.php'); + } + elseif (is_file(APPPATH.'config/hooks.php')) + { + include(APPPATH.'config/hooks.php'); + } + + + if ( ! isset($hook) OR ! is_array($hook)) + { + return; + } + + $this->hooks =& $hook; + $this->enabled = TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Call Hook + * + * Calls a particular hook + * + * @access private + * @param string the hook name + * @return mixed + */ + function _call_hook($which = '') + { + if ( ! $this->enabled OR ! isset($this->hooks[$which])) + { + return FALSE; + } + + if (isset($this->hooks[$which][0]) AND is_array($this->hooks[$which][0])) + { + 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 + * + * @access private + * @param array the hook details + * @return bool + */ + function _run_hook($data) + { + if ( ! 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']) OR ! isset($data['filename'])) + { + return FALSE; + } + + $filepath = APPPATH.$data['filepath'].'/'.$data['filename']; + + if ( ! file_exists($filepath)) + { + return FALSE; + } + + // ----------------------------------- + // Set class/function name + // ----------------------------------- + + $class = FALSE; + $function = FALSE; + $params = ''; + + if (isset($data['class']) AND $data['class'] != '') + { + $class = $data['class']; + } + + if (isset($data['function'])) + { + $function = $data['function']; + } + + if (isset($data['params'])) + { + $params = $data['params']; + } + + if ($class === FALSE AND $function === FALSE) + { + return FALSE; + } + + // ----------------------------------- + // Set the in_progress flag + // ----------------------------------- + + $this->in_progress = TRUE; + + // ----------------------------------- + // Call the requested class and/or function + // ----------------------------------- + + if ($class !== FALSE) + { + if ( ! class_exists($class)) + { + require($filepath); + } + + $HOOK = new $class; + $HOOK->$function($params); + } + else + { + if ( ! function_exists($function)) + { + require($filepath); + } + + $function($params); + } + + $this->in_progress = FALSE; + return TRUE; + } + +} + +// END CI_Hooks class + +/* End of file Hooks.php */ +/* Location: ./system/core/Hooks.php */ \ No newline at end of file diff --git a/system/core/Input.php b/system/core/Input.php new file mode 100755 index 0000000..0c1f2b0 --- /dev/null +++ b/system/core/Input.php @@ -0,0 +1,849 @@ +_allow_get_array = (config_item('allow_get_array') === TRUE); + $this->_enable_xss = (config_item('global_xss_filtering') === TRUE); + $this->_enable_csrf = (config_item('csrf_protection') === TRUE); + + global $SEC; + $this->security =& $SEC; + + // Do we need the UTF-8 class? + if (UTF8_ENABLED === TRUE) + { + global $UNI; + $this->uni =& $UNI; + } + + // Sanitize global arrays + $this->_sanitize_globals(); + } + + // -------------------------------------------------------------------- + + /** + * Fetch from array + * + * This is a helper function to retrieve values from global arrays + * + * @access private + * @param array + * @param string + * @param bool + * @return string + */ + function _fetch_from_array(&$array, $index = '', $xss_clean = FALSE) + { + if ( ! isset($array[$index])) + { + return FALSE; + } + + if ($xss_clean === TRUE) + { + return $this->security->xss_clean($array[$index]); + } + + return $array[$index]; + } + + // -------------------------------------------------------------------- + + /** + * Fetch an item from the GET array + * + * @access public + * @param string + * @param bool + * @return string + */ + function get($index = NULL, $xss_clean = FALSE) + { + // Check if a field has been provided + if ($index === NULL AND ! empty($_GET)) + { + $get = array(); + + // loop through the full _GET array + foreach (array_keys($_GET) as $key) + { + $get[$key] = $this->_fetch_from_array($_GET, $key, $xss_clean); + } + return $get; + } + + return $this->_fetch_from_array($_GET, $index, $xss_clean); + } + + // -------------------------------------------------------------------- + + /** + * Fetch an item from the POST array + * + * @access public + * @param string + * @param bool + * @return string + */ + function post($index = NULL, $xss_clean = FALSE) + { + // Check if a field has been provided + if ($index === NULL AND ! empty($_POST)) + { + $post = array(); + + // Loop through the full _POST array and return it + foreach (array_keys($_POST) as $key) + { + $post[$key] = $this->_fetch_from_array($_POST, $key, $xss_clean); + } + return $post; + } + + return $this->_fetch_from_array($_POST, $index, $xss_clean); + } + + + // -------------------------------------------------------------------- + + /** + * Fetch an item from either the GET array or the POST + * + * @access public + * @param string The index key + * @param bool XSS cleaning + * @return string + */ + function get_post($index = '', $xss_clean = FALSE) + { + if ( ! isset($_POST[$index]) ) + { + return $this->get($index, $xss_clean); + } + else + { + return $this->post($index, $xss_clean); + } + } + + // -------------------------------------------------------------------- + + /** + * Fetch an item from the COOKIE array + * + * @access public + * @param string + * @param bool + * @return string + */ + function cookie($index = '', $xss_clean = FALSE) + { + return $this->_fetch_from_array($_COOKIE, $index, $xss_clean); + } + + // ------------------------------------------------------------------------ + + /** + * Set cookie + * + * Accepts six parameter, or you can submit an associative + * array in the first parameter containing all the values. + * + * @access public + * @param mixed + * @param string the value of the cookie + * @param string the number of seconds until expiration + * @param string the cookie domain. Usually: .yourdomain.com + * @param string the cookie path + * @param string the cookie prefix + * @param bool true makes the cookie secure + * @return void + */ + function set_cookie($name = '', $value = '', $expire = '', $domain = '', $path = '/', $prefix = '', $secure = FALSE) + { + 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', 'name') as $item) + { + if (isset($name[$item])) + { + $$item = $name[$item]; + } + } + } + + if ($prefix == '' AND config_item('cookie_prefix') != '') + { + $prefix = config_item('cookie_prefix'); + } + if ($domain == '' AND config_item('cookie_domain') != '') + { + $domain = config_item('cookie_domain'); + } + if ($path == '/' AND config_item('cookie_path') != '/') + { + $path = config_item('cookie_path'); + } + if ($secure == FALSE AND config_item('cookie_secure') != FALSE) + { + $secure = config_item('cookie_secure'); + } + + if ( ! is_numeric($expire)) + { + $expire = time() - 86500; + } + else + { + $expire = ($expire > 0) ? time() + $expire : 0; + } + + setcookie($prefix.$name, $value, $expire, $path, $domain, $secure); + } + + // -------------------------------------------------------------------- + + /** + * Fetch an item from the SERVER array + * + * @access public + * @param string + * @param bool + * @return string + */ + function server($index = '', $xss_clean = FALSE) + { + return $this->_fetch_from_array($_SERVER, $index, $xss_clean); + } + + // -------------------------------------------------------------------- + + /** + * Fetch the IP Address + * + * @return string + */ + public function ip_address() + { + if ($this->ip_address !== FALSE) + { + return $this->ip_address; + } + + $proxy_ips = config_item('proxy_ips'); + if ( ! empty($proxy_ips)) + { + $proxy_ips = explode(',', str_replace(' ', '', $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)) !== FALSE) + { + // 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. + if (strpos($spoof, ',') !== FALSE) + { + $spoof = explode(',', $spoof, 2); + $spoof = $spoof[0]; + } + + if ( ! $this->valid_ip($spoof)) + { + $spoof = FALSE; + } + else + { + break; + } + } + } + + $this->ip_address = ($spoof !== FALSE && in_array($_SERVER['REMOTE_ADDR'], $proxy_ips, TRUE)) + ? $spoof : $_SERVER['REMOTE_ADDR']; + } + else + { + $this->ip_address = $_SERVER['REMOTE_ADDR']; + } + + if ( ! $this->valid_ip($this->ip_address)) + { + $this->ip_address = '0.0.0.0'; + } + + return $this->ip_address; + } + + // -------------------------------------------------------------------- + + /** + * Validate IP Address + * + * @access public + * @param string + * @param string ipv4 or ipv6 + * @return bool + */ + public function valid_ip($ip, $which = '') + { + $which = strtolower($which); + + // First check if filter_var is available + if (is_callable('filter_var')) + { + switch ($which) { + case 'ipv4': + $flag = FILTER_FLAG_IPV4; + break; + case 'ipv6': + $flag = FILTER_FLAG_IPV6; + break; + default: + $flag = ''; + break; + } + + return (bool) filter_var($ip, FILTER_VALIDATE_IP, $flag); + } + + if ($which !== 'ipv6' && $which !== 'ipv4') + { + if (strpos($ip, ':') !== FALSE) + { + $which = 'ipv6'; + } + elseif (strpos($ip, '.') !== FALSE) + { + $which = 'ipv4'; + } + else + { + return FALSE; + } + } + + $func = '_valid_'.$which; + return $this->$func($ip); + } + + // -------------------------------------------------------------------- + + /** + * Validate IPv4 Address + * + * Updated version suggested by Geert De Deckere + * + * @access protected + * @param string + * @return bool + */ + protected function _valid_ipv4($ip) + { + $ip_segments = explode('.', $ip); + + // Always 4 segments needed + if (count($ip_segments) !== 4) + { + return FALSE; + } + // IP can not start with 0 + if ($ip_segments[0][0] == '0') + { + return FALSE; + } + + // Check each segment + foreach ($ip_segments as $segment) + { + // IP segments must be digits and can not be + // longer than 3 digits or greater then 255 + if ($segment == '' OR preg_match("/[^0-9]/", $segment) OR $segment > 255 OR strlen($segment) > 3) + { + return FALSE; + } + } + + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Validate IPv6 Address + * + * @access protected + * @param string + * @return bool + */ + protected function _valid_ipv6($str) + { + // 8 groups, separated by : + // 0-ffff per group + // one set of consecutive 0 groups can be collapsed to :: + + $groups = 8; + $collapsed = FALSE; + + $chunks = array_filter( + preg_split('/(:{1,2})/', $str, NULL, PREG_SPLIT_DELIM_CAPTURE) + ); + + // Rule out easy nonsense + if (current($chunks) == ':' OR end($chunks) == ':') + { + return FALSE; + } + + // PHP supports IPv4-mapped IPv6 addresses, so we'll expect those as well + if (strpos(end($chunks), '.') !== FALSE) + { + $ipv4 = array_pop($chunks); + + if ( ! $this->_valid_ipv4($ipv4)) + { + return FALSE; + } + + $groups--; + } + + while ($seg = array_pop($chunks)) + { + if ($seg[0] == ':') + { + if (--$groups == 0) + { + return FALSE; // too many groups + } + + if (strlen($seg) > 2) + { + return FALSE; // long separator + } + + if ($seg == '::') + { + if ($collapsed) + { + return FALSE; // multiple collapsed + } + + $collapsed = TRUE; + } + } + elseif (preg_match("/[^0-9a-f]/i", $seg) OR strlen($seg) > 4) + { + return FALSE; // invalid segment + } + } + + return $collapsed OR $groups == 1; + } + + // -------------------------------------------------------------------- + + /** + * User Agent + * + * @access public + * @return string + */ + function user_agent() + { + if ($this->user_agent !== FALSE) + { + return $this->user_agent; + } + + $this->user_agent = ( ! isset($_SERVER['HTTP_USER_AGENT'])) ? FALSE : $_SERVER['HTTP_USER_AGENT']; + + return $this->user_agent; + } + + // -------------------------------------------------------------------- + + /** + * Sanitize Globals + * + * This function does the following: + * + * Unsets $_GET data (if query strings are not enabled) + * + * Unsets all globals if register_globals is enabled + * + * Standardizes newline characters to \n + * + * @access private + * @return void + */ + function _sanitize_globals() + { + // It would be "wrong" to unset any of these GLOBALS. + $protected = array('_SERVER', '_GET', '_POST', '_FILES', '_REQUEST', + '_SESSION', '_ENV', 'GLOBALS', 'HTTP_RAW_POST_DATA', + 'system_folder', 'application_folder', 'BM', 'EXT', + 'CFG', 'URI', 'RTR', 'OUT', 'IN'); + + // Unset globals for securiy. + // This is effectively the same as register_globals = off + foreach (array($_GET, $_POST, $_COOKIE) as $global) + { + if ( ! is_array($global)) + { + if ( ! in_array($global, $protected)) + { + global $$global; + $$global = NULL; + } + } + else + { + foreach ($global as $key => $val) + { + if ( ! in_array($key, $protected)) + { + global $$key; + $$key = NULL; + } + } + } + } + + // Is $_GET data allowed? If not we'll set the $_GET to an empty array + if ($this->_allow_get_array == FALSE) + { + $_GET = array(); + } + else + { + if (is_array($_GET) AND count($_GET) > 0) + { + foreach ($_GET as $key => $val) + { + $_GET[$this->_clean_input_keys($key)] = $this->_clean_input_data($val); + } + } + } + + // Clean $_POST Data + if (is_array($_POST) AND count($_POST) > 0) + { + foreach ($_POST as $key => $val) + { + $_POST[$this->_clean_input_keys($key)] = $this->_clean_input_data($val); + } + } + + // Clean $_COOKIE Data + if (is_array($_COOKIE) AND count($_COOKIE) > 0) + { + // 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']); + unset($_COOKIE['$Path']); + unset($_COOKIE['$Domain']); + + foreach ($_COOKIE as $key => $val) + { + $_COOKIE[$this->_clean_input_keys($key)] = $this->_clean_input_data($val); + } + } + + // Sanitize PHP_SELF + $_SERVER['PHP_SELF'] = strip_tags($_SERVER['PHP_SELF']); + + + // CSRF Protection check on HTTP requests + if ($this->_enable_csrf == TRUE && ! $this->is_cli_request()) + { + $this->security->csrf_verify(); + } + + log_message('debug', "Global POST and COOKIE data sanitized"); + } + + // -------------------------------------------------------------------- + + /** + * Clean Input Data + * + * This is a helper function. It escapes data and + * standardizes newline characters to \n + * + * @access private + * @param string + * @return string + */ + function _clean_input_data($str) + { + if (is_array($str)) + { + $new_array = array(); + foreach ($str as $key => $val) + { + $new_array[$this->_clean_input_keys($key)] = $this->_clean_input_data($val); + } + 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); + + // Should we filter the input data? + if ($this->_enable_xss === TRUE) + { + $str = $this->security->xss_clean($str); + } + + // Standardize newlines if needed + if ($this->_standardize_newlines == TRUE) + { + if (strpos($str, "\r") !== FALSE) + { + $str = str_replace(array("\r\n", "\r", "\r\n\n"), PHP_EOL, $str); + } + } + + return $str; + } + + // -------------------------------------------------------------------- + + /** + * Clean Keys + * + * This is a helper function. 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. + * + * @access private + * @param string + * @return string + */ + function _clean_input_keys($str) + { + if ( ! preg_match("/^[a-z0-9:_\/-]+$/i", $str)) + { + exit('Disallowed Key Characters.'); + } + + // Clean UTF-8 if supported + if (UTF8_ENABLED === TRUE) + { + $str = $this->uni->clean_string($str); + } + + return $str; + } + + // -------------------------------------------------------------------- + + /** + * Request Headers + * + * In Apache, you can simply call apache_request_headers(), however for + * people running other webservers the function is undefined. + * + * @param bool XSS cleaning + * + * @return array + */ + public function request_headers($xss_clean = FALSE) + { + // Look at Apache go! + if (function_exists('apache_request_headers')) + { + $headers = apache_request_headers(); + } + else + { + $headers['Content-Type'] = (isset($_SERVER['CONTENT_TYPE'])) ? $_SERVER['CONTENT_TYPE'] : @getenv('CONTENT_TYPE'); + + foreach ($_SERVER as $key => $val) + { + if (strncmp($key, 'HTTP_', 5) === 0) + { + $headers[substr($key, 5)] = $this->_fetch_from_array($_SERVER, $key, $xss_clean); + } + } + } + + // take SOME_HEADER and turn it into Some-Header + foreach ($headers as $key => $val) + { + $key = str_replace('_', ' ', strtolower($key)); + $key = str_replace(' ', '-', ucwords($key)); + + $this->headers[$key] = $val; + } + + return $this->headers; + } + + // -------------------------------------------------------------------- + + /** + * Get Request Header + * + * Returns the value of a single member of the headers class member + * + * @param string array key for $this->headers + * @param boolean XSS Clean or not + * @return mixed FALSE on failure, string on success + */ + public function get_request_header($index, $xss_clean = FALSE) + { + if (empty($this->headers)) + { + $this->request_headers(); + } + + if ( ! isset($this->headers[$index])) + { + return FALSE; + } + + if ($xss_clean === TRUE) + { + return $this->security->xss_clean($this->headers[$index]); + } + + return $this->headers[$index]; + } + + // -------------------------------------------------------------------- + + /** + * Is ajax Request? + * + * Test to see if a request contains the HTTP_X_REQUESTED_WITH header + * + * @return boolean + */ + public function is_ajax_request() + { + return ($this->server('HTTP_X_REQUESTED_WITH') === 'XMLHttpRequest'); + } + + // -------------------------------------------------------------------- + + /** + * Is cli Request? + * + * Test to see if a request was made from the command line + * + * @return bool + */ + public function is_cli_request() + { + return (php_sapi_name() === 'cli' OR defined('STDIN')); + } + +} + +/* End of file Input.php */ +/* Location: ./system/core/Input.php */ \ No newline at end of file diff --git a/system/core/Lang.php b/system/core/Lang.php new file mode 100755 index 0000000..5ac6718 --- /dev/null +++ b/system/core/Lang.php @@ -0,0 +1,160 @@ +is_loaded, TRUE)) + { + return; + } + + $config =& get_config(); + + if ($idiom == '') + { + $deft_lang = ( ! isset($config['language'])) ? 'english' : $config['language']; + $idiom = ($deft_lang == '') ? 'english' : $deft_lang; + } + + // Determine where the language file is and load it + if ($alt_path != '' && file_exists($alt_path.'language/'.$idiom.'/'.$langfile)) + { + include($alt_path.'language/'.$idiom.'/'.$langfile); + } + else + { + $found = FALSE; + + foreach (get_instance()->load->get_package_paths(TRUE) as $package_path) + { + if (file_exists($package_path.'language/'.$idiom.'/'.$langfile)) + { + include($package_path.'language/'.$idiom.'/'.$langfile); + $found = TRUE; + break; + } + } + + if ($found !== TRUE) + { + show_error('Unable to load the requested language file: language/'.$idiom.'/'.$langfile); + } + } + + + if ( ! isset($lang)) + { + log_message('error', 'Language file contains no data: language/'.$idiom.'/'.$langfile); + return; + } + + if ($return == TRUE) + { + return $lang; + } + + $this->is_loaded[] = $langfile; + $this->language = array_merge($this->language, $lang); + unset($lang); + + log_message('debug', 'Language file loaded: language/'.$idiom.'/'.$langfile); + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Fetch a single line of text from the language array + * + * @access public + * @param string $line the language line + * @return string + */ + function line($line = '') + { + $value = ($line == '' OR ! isset($this->language[$line])) ? FALSE : $this->language[$line]; + + // Because killer robots like unicorns! + if ($value === FALSE) + { + log_message('error', 'Could not find the language line "'.$line.'"'); + } + + return $value; + } + +} +// END Language Class + +/* End of file Lang.php */ +/* Location: ./system/core/Lang.php */ diff --git a/system/core/Loader.php b/system/core/Loader.php new file mode 100644 index 0000000..6b7ee0c --- /dev/null +++ b/system/core/Loader.php @@ -0,0 +1,1248 @@ + 'unit', + 'user_agent' => 'agent'); + + /** + * Constructor + * + * Sets the path to the view files and gets the initial output buffering level + */ + public function __construct() + { + $this->_ci_ob_level = ob_get_level(); + $this->_ci_library_paths = array(APPPATH, BASEPATH); + $this->_ci_helper_paths = array(APPPATH, BASEPATH); + $this->_ci_model_paths = array(APPPATH); + $this->_ci_view_paths = array(APPPATH.'views/' => TRUE); + + log_message('debug', "Loader Class Initialized"); + } + + // -------------------------------------------------------------------- + + /** + * Initialize the Loader + * + * This method is called once in CI_Controller. + * + * @param array + * @return object + */ + public function initialize() + { + $this->_ci_classes = array(); + $this->_ci_loaded_files = array(); + $this->_ci_models = array(); + $this->_base_classes =& is_loaded(); + + $this->_ci_autoloader(); + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Is Loaded + * + * A utility function to test if a class is in the self::$_ci_classes array. + * This function returns the object name if the class tested for is loaded, + * and returns FALSE if it isn't. + * + * It is mainly used in the form_helper -> _get_validation_object() + * + * @param string class being checked for + * @return mixed class object name on the CI SuperObject or FALSE + */ + public function is_loaded($class) + { + if (isset($this->_ci_classes[$class])) + { + return $this->_ci_classes[$class]; + } + + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Class Loader + * + * This function lets users load and instantiate classes. + * It is designed to be called from a user's app controllers. + * + * @param string the name of the class + * @param mixed the optional parameters + * @param string an optional object name + * @return void + */ + public function library($library = '', $params = NULL, $object_name = NULL) + { + if (is_array($library)) + { + foreach ($library as $class) + { + $this->library($class, $params); + } + + return; + } + + if ($library == '' OR isset($this->_base_classes[$library])) + { + return FALSE; + } + + if ( ! is_null($params) && ! is_array($params)) + { + $params = NULL; + } + + $this->_ci_load_class($library, $params, $object_name); + } + + // -------------------------------------------------------------------- + + /** + * Model Loader + * + * This function lets users load and instantiate models. + * + * @param string the name of the class + * @param string name for the model + * @param bool database connection + * @return void + */ + public function model($model, $name = '', $db_conn = FALSE) + { + if (is_array($model)) + { + foreach ($model as $babe) + { + $this->model($babe); + } + return; + } + + if ($model == '') + { + return; + } + + $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 + 1); + + // And the model name behind it + $model = substr($model, $last_slash + 1); + } + + if ($name == '') + { + $name = $model; + } + + if (in_array($name, $this->_ci_models, TRUE)) + { + return; + } + + $CI =& get_instance(); + if (isset($CI->$name)) + { + show_error('The model name you are loading is the name of a resource that is already being used: '.$name); + } + + $model = strtolower($model); + + foreach ($this->_ci_model_paths as $mod_path) + { + if ( ! file_exists($mod_path.'models/'.$path.$model.'.php')) + { + continue; + } + + if ($db_conn !== FALSE AND ! class_exists('CI_DB')) + { + if ($db_conn === TRUE) + { + $db_conn = ''; + } + + $CI->load->database($db_conn, FALSE, TRUE); + } + + if ( ! class_exists('CI_Model')) + { + load_class('Model', 'core'); + } + + require_once($mod_path.'models/'.$path.$model.'.php'); + + $model = ucfirst($model); + + $CI->$name = new $model(); + + $this->_ci_models[] = $name; + return; + } + + // couldn't find the model + show_error('Unable to locate the model you have specified: '.$model); + } + + // -------------------------------------------------------------------- + + /** + * Database Loader + * + * @param string the DB credentials + * @param bool whether to return the DB object + * @param bool whether to enable active record (this allows us to override the config setting) + * @return object + */ + public function database($params = '', $return = FALSE, $active_record = NULL) + { + // Grab the super object + $CI =& get_instance(); + + // Do we even need to load the database class? + if (class_exists('CI_DB') AND $return == FALSE AND $active_record == NULL AND isset($CI->db) AND is_object($CI->db)) + { + return FALSE; + } + + require_once(BASEPATH.'database/DB.php'); + + if ($return === TRUE) + { + return DB($params, $active_record); + } + + // Initialize the db variable. Needed to prevent + // reference errors with some configurations + $CI->db = ''; + + // Load the DB class + $CI->db =& DB($params, $active_record); + } + + // -------------------------------------------------------------------- + + /** + * Load the Utilities Class + * + * @return string + */ + public function dbutil() + { + if ( ! class_exists('CI_DB')) + { + $this->database(); + } + + $CI =& get_instance(); + + // for backwards compatibility, load dbforge so we can extend dbutils off it + // this use is deprecated and strongly discouraged + $CI->load->dbforge(); + + require_once(BASEPATH.'database/DB_utility.php'); + require_once(BASEPATH.'database/drivers/'.$CI->db->dbdriver.'/'.$CI->db->dbdriver.'_utility.php'); + $class = 'CI_DB_'.$CI->db->dbdriver.'_utility'; + + $CI->dbutil = new $class(); + } + + // -------------------------------------------------------------------- + + /** + * Load the Database Forge Class + * + * @return string + */ + public function dbforge() + { + if ( ! class_exists('CI_DB')) + { + $this->database(); + } + + $CI =& get_instance(); + + require_once(BASEPATH.'database/DB_forge.php'); + require_once(BASEPATH.'database/drivers/'.$CI->db->dbdriver.'/'.$CI->db->dbdriver.'_forge.php'); + $class = 'CI_DB_'.$CI->db->dbdriver.'_forge'; + + $CI->dbforge = new $class(); + } + + // -------------------------------------------------------------------- + + /** + * Load View + * + * This function is used to load a "view" file. It has three parameters: + * + * 1. The name of the "view" file to be included. + * 2. An associative array of data to be extracted for use in the view. + * 3. TRUE/FALSE - whether to return the data or load it. In + * some cases it's advantageous to be able to return data so that + * a developer can process it in some way. + * + * @param string + * @param array + * @param bool + * @return void + */ + public function view($view, $vars = array(), $return = FALSE) + { + return $this->_ci_load(array('_ci_view' => $view, '_ci_vars' => $this->_ci_object_to_array($vars), '_ci_return' => $return)); + } + + // -------------------------------------------------------------------- + + /** + * Load File + * + * This is a generic file loader + * + * @param string + * @param bool + * @return 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 + * @param string + * @return void + */ + public function vars($vars = array(), $val = '') + { + if ($val != '' AND is_string($vars)) + { + $vars = array($vars => $val); + } + + $vars = $this->_ci_object_to_array($vars); + + if (is_array($vars) AND count($vars) > 0) + { + foreach ($vars as $key => $val) + { + $this->_ci_cached_vars[$key] = $val; + } + } + } + + // -------------------------------------------------------------------- + + /** + * Get Variable + * + * Check if a variable is set and retrieve it. + * + * @param array + * @return void + */ + public function get_var($key) + { + return isset($this->_ci_cached_vars[$key]) ? $this->_ci_cached_vars[$key] : NULL; + } + + // -------------------------------------------------------------------- + + /** + * Load Helper + * + * This function loads the specified helper file. + * + * @param mixed + * @return void + */ + public function helper($helpers = array()) + { + foreach ($this->_ci_prep_filename($helpers, '_helper') as $helper) + { + if (isset($this->_ci_helpers[$helper])) + { + continue; + } + + $ext_helper = APPPATH.'helpers/'.config_item('subclass_prefix').$helper.'.php'; + + // Is this a helper extension request? + if (file_exists($ext_helper)) + { + $base_helper = BASEPATH.'helpers/'.$helper.'.php'; + + if ( ! file_exists($base_helper)) + { + show_error('Unable to load the requested file: helpers/'.$helper.'.php'); + } + + include_once($ext_helper); + include_once($base_helper); + + $this->_ci_helpers[$helper] = TRUE; + log_message('debug', 'Helper loaded: '.$helper); + continue; + } + + // Try to load the helper + 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('debug', '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'); + } + } + } + + // -------------------------------------------------------------------- + + /** + * Load Helpers + * + * This is simply an alias to the above function in case the + * user has written the plural form of this function. + * + * @param array + * @return void + */ + public function helpers($helpers = array()) + { + $this->helper($helpers); + } + + // -------------------------------------------------------------------- + + /** + * Loads a language file + * + * @param array + * @param string + * @return void + */ + public function language($file = array(), $lang = '') + { + $CI =& get_instance(); + + if ( ! is_array($file)) + { + $file = array($file); + } + + foreach ($file as $langfile) + { + $CI->lang->load($langfile, $lang); + } + } + + // -------------------------------------------------------------------- + + /** + * Loads a config file + * + * @param string + * @param bool + * @param bool + * @return void + */ + public function config($file = '', $use_sections = FALSE, $fail_gracefully = FALSE) + { + $CI =& get_instance(); + $CI->config->load($file, $use_sections, $fail_gracefully); + } + + // -------------------------------------------------------------------- + + /** + * Driver + * + * Loads a driver library + * + * @param string the name of the class + * @param mixed the optional parameters + * @param string an optional object name + * @return void + */ + public function driver($library = '', $params = NULL, $object_name = NULL) + { + if ( ! class_exists('CI_Driver_Library')) + { + // we aren't instantiating an object here, that'll be done by the Library itself + require BASEPATH.'libraries/Driver.php'; + } + + if ($library == '') + { + return FALSE; + } + + // 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 + * + * @param string + * @param boolean + * @return void + */ + 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'); + array_unshift($config->_config_paths, $path); + } + + // -------------------------------------------------------------------- + + /** + * Get Package Paths + * + * Return a list of all package paths, by default it will ignore BASEPATH. + * + * @param string + * @return void + */ + 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, and helper path arrays if it exists + * If no path is provided, the most recently added path is removed. + * + * @param type + * @param bool + * @return type + */ + public function remove_package_path($path = '', $remove_config_path = TRUE) + { + $config =& $this->_ci_get_component('config'); + + if ($path == '') + { + $void = array_shift($this->_ci_library_paths); + $void = array_shift($this->_ci_model_paths); + $void = array_shift($this->_ci_helper_paths); + $void = array_shift($this->_ci_view_paths); + $void = array_shift($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))); + } + + // -------------------------------------------------------------------- + + /** + * Loader + * + * This function is used to load views and files. + * Variables are prefixed with _ci_ to avoid symbol collision with + * variables made available to view files + * + * @param array + * @return void + */ + 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])) ? FALSE : $_ci_data[$_ci_val]; + } + + $file_exists = FALSE; + + // Set the path to the requested file + if ($_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 $view_file => $cascade) + { + if (file_exists($view_file.$_ci_file)) + { + $_ci_path = $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. + */ + if (is_array($_ci_vars)) + { + $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 ((bool) @ini_get('short_open_tag') === FALSE AND config_item('rewrite_short_tags') == TRUE) + { + echo eval('?>'.preg_replace("/;*\s*\?>/", "; ?>", str_replace(' $this->_ci_ob_level + 1) + { + ob_end_flush(); + } + else + { + $_ci_CI->output->append_output(ob_get_contents()); + @ob_end_clean(); + } + } + + // -------------------------------------------------------------------- + + /** + * Load class + * + * This function loads the requested class. + * + * @param string the item that is being loaded + * @param mixed any additional parameters + * @param string an optional object name + * @return void + */ + protected function _ci_load_class($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 + $subdir = ''; + if (($last_slash = strrpos($class, '/')) !== FALSE) + { + // Extract the path + $subdir = substr($class, 0, $last_slash + 1); + + // Get the filename from the path + $class = substr($class, $last_slash + 1); + } + + // We'll test for both lowercase and capitalized versions of the file name + foreach (array(ucfirst($class), strtolower($class)) as $class) + { + $subclass = APPPATH.'libraries/'.$subdir.config_item('subclass_prefix').$class.'.php'; + + // Is this a class extension request? + if (file_exists($subclass)) + { + $baseclass = BASEPATH.'libraries/'.ucfirst($class).'.php'; + + if ( ! file_exists($baseclass)) + { + log_message('error', "Unable to load the requested class: ".$class); + show_error("Unable to load the requested class: ".$class); + } + + // Safety: Was the class already loaded by a previous call? + if (in_array($subclass, $this->_ci_loaded_files)) + { + // Before we deem this to be a duplicate request, let's see + // if a custom object name is being supplied. If so, we'll + // return a new instance of the object + if ( ! is_null($object_name)) + { + $CI =& get_instance(); + if ( ! isset($CI->$object_name)) + { + return $this->_ci_init_class($class, config_item('subclass_prefix'), $params, $object_name); + } + } + + $is_duplicate = TRUE; + log_message('debug', $class." class already loaded. Second attempt ignored."); + return; + } + + include_once($baseclass); + include_once($subclass); + $this->_ci_loaded_files[] = $subclass; + + return $this->_ci_init_class($class, config_item('subclass_prefix'), $params, $object_name); + } + + // Lets search for the requested library file and load it. + $is_duplicate = FALSE; + foreach ($this->_ci_library_paths as $path) + { + $filepath = $path.'libraries/'.$subdir.$class.'.php'; + + // Does the file exist? No? Bummer... + if ( ! file_exists($filepath)) + { + continue; + } + + // Safety: Was the class already loaded by a previous call? + if (in_array($filepath, $this->_ci_loaded_files)) + { + // Before we deem this to be a duplicate request, let's see + // if a custom object name is being supplied. If so, we'll + // return a new instance of the object + if ( ! is_null($object_name)) + { + $CI =& get_instance(); + if ( ! isset($CI->$object_name)) + { + return $this->_ci_init_class($class, '', $params, $object_name); + } + } + + $is_duplicate = TRUE; + log_message('debug', $class." class already loaded. Second attempt ignored."); + return; + } + + include_once($filepath); + $this->_ci_loaded_files[] = $filepath; + return $this->_ci_init_class($class, '', $params, $object_name); + } + + } // END FOREACH + + // One last attempt. Maybe the library is in a subdirectory, but it wasn't specified? + if ($subdir == '') + { + $path = strtolower($class).'/'.$class; + return $this->_ci_load_class($path, $params); + } + + // If we got this far we were unable to find the requested class. + // We do not issue errors if the load call failed due to a duplicate request + if ($is_duplicate == FALSE) + { + log_message('error', "Unable to load the requested class: ".$class); + show_error("Unable to load the requested class: ".$class); + } + } + + // -------------------------------------------------------------------- + + /** + * Instantiates a class + * + * @param string + * @param string + * @param bool + * @param string an optional object name + * @return null + */ + protected function _ci_init_class($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)) + { + // Break on the first found file, thus package files + // are not overridden by default paths + 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. Check for environment + // first, global next + if (defined('ENVIRONMENT') AND file_exists($path .'config/'.ENVIRONMENT.'/'.strtolower($class).'.php')) + { + include($path .'config/'.ENVIRONMENT.'/'.strtolower($class).'.php'); + break; + } + elseif (defined('ENVIRONMENT') AND file_exists($path .'config/'.ENVIRONMENT.'/'.ucfirst(strtolower($class)).'.php')) + { + include($path .'config/'.ENVIRONMENT.'/'.ucfirst(strtolower($class)).'.php'); + break; + } + elseif (file_exists($path .'config/'.strtolower($class).'.php')) + { + include($path .'config/'.strtolower($class).'.php'); + break; + } + elseif (file_exists($path .'config/'.ucfirst(strtolower($class)).'.php')) + { + include($path .'config/'.ucfirst(strtolower($class)).'.php'); + break; + } + } + } + } + + if ($prefix == '') + { + if (class_exists('CI_'.$class)) + { + $name = 'CI_'.$class; + } + elseif (class_exists(config_item('subclass_prefix').$class)) + { + $name = config_item('subclass_prefix').$class; + } + else + { + $name = $class; + } + } + else + { + $name = $prefix.$class; + } + + // Is the class name valid? + if ( ! class_exists($name)) + { + log_message('error', "Non-existent class: ".$name); + show_error("Non-existent class: ".$class); + } + + // Set the variable name we will assign the class to + // Was a custom class name supplied? If so we'll use it + $class = strtolower($class); + + if (is_null($object_name)) + { + $classvar = ( ! isset($this->_ci_varmap[$class])) ? $class : $this->_ci_varmap[$class]; + } + else + { + $classvar = $object_name; + } + + // Save the class name and object name + $this->_ci_classes[$class] = $classvar; + + // Instantiate the class + $CI =& get_instance(); + if ($config !== NULL) + { + $CI->$classvar = new $name($config); + } + else + { + $CI->$classvar = new $name; + } + } + + // -------------------------------------------------------------------- + + /** + * Autoloader + * + * The config/autoload.php file contains an array that permits sub-systems, + * libraries, and helpers to be loaded automatically. + * + * @param array + * @return void + */ + private function _ci_autoloader() + { + if (defined('ENVIRONMENT') AND file_exists(APPPATH.'config/'.ENVIRONMENT.'/autoload.php')) + { + include(APPPATH.'config/'.ENVIRONMENT.'/autoload.php'); + } + else + { + include(APPPATH.'config/autoload.php'); + } + + if ( ! isset($autoload)) + { + return FALSE; + } + + // 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) + { + $CI =& get_instance(); + foreach ($autoload['config'] as $key => $val) + { + $CI->config->load($val); + } + } + + // Autoload helpers and languages + foreach (array('helper', 'language') as $type) + { + if (isset($autoload[$type]) AND count($autoload[$type]) > 0) + { + $this->$type($autoload[$type]); + } + } + + // A little tweak to remain backward compatible + // The $autoload['core'] item was deprecated + if ( ! isset($autoload['libraries']) AND isset($autoload['core'])) + { + $autoload['libraries'] = $autoload['core']; + } + + // Load libraries + if (isset($autoload['libraries']) AND 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 + foreach ($autoload['libraries'] as $item) + { + $this->library($item); + } + } + + // Autoload models + if (isset($autoload['model'])) + { + $this->model($autoload['model']); + } + } + + // -------------------------------------------------------------------- + + /** + * Object to Array + * + * Takes an object as input and converts the class variables to array key/vals + * + * @param object + * @return array + */ + protected function _ci_object_to_array($object) + { + return (is_object($object)) ? get_object_vars($object) : $object; + } + + // -------------------------------------------------------------------- + + /** + * Get a reference to a specific library or model + * + * @param string + * @return bool + */ + protected function &_ci_get_component($component) + { + $CI =& get_instance(); + return $CI->$component; + } + + // -------------------------------------------------------------------- + + /** + * Prep filename + * + * This function preps the name of various items to make loading them more reliable. + * + * @param mixed + * @param string + * @return array + */ + protected function _ci_prep_filename($filename, $extension) + { + if ( ! is_array($filename)) + { + return array(strtolower(str_replace('.php', '', str_replace($extension, '', $filename)).$extension)); + } + else + { + foreach ($filename as $key => $val) + { + $filename[$key] = strtolower(str_replace('.php', '', str_replace($extension, '', $val)).$extension); + } + + return $filename; + } + } +} + +/* End of file Loader.php */ +/* Location: ./system/core/Loader.php */ \ No newline at end of file diff --git a/system/core/Model.php b/system/core/Model.php new file mode 100755 index 0000000..e15ffbe --- /dev/null +++ b/system/core/Model.php @@ -0,0 +1,57 @@ +$key; + } +} +// END Model Class + +/* End of file Model.php */ +/* Location: ./system/core/Model.php */ \ No newline at end of file diff --git a/system/core/Output.php b/system/core/Output.php new file mode 100755 index 0000000..ccecafd --- /dev/null +++ b/system/core/Output.php @@ -0,0 +1,574 @@ +_zlib_oc = @ini_get('zlib.output_compression'); + + // Get mime types for later + if (defined('ENVIRONMENT') AND file_exists(APPPATH.'config/'.ENVIRONMENT.'/mimes.php')) + { + include APPPATH.'config/'.ENVIRONMENT.'/mimes.php'; + } + else + { + include APPPATH.'config/mimes.php'; + } + + + $this->mime_types = $mimes; + + log_message('debug', "Output Class Initialized"); + } + + // -------------------------------------------------------------------- + + /** + * Get Output + * + * Returns the current output string + * + * @access public + * @return string + */ + function get_output() + { + return $this->final_output; + } + + // -------------------------------------------------------------------- + + /** + * Set Output + * + * Sets the output string + * + * @access public + * @param string + * @return void + */ + function set_output($output) + { + $this->final_output = $output; + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Append Output + * + * Appends data onto the output string + * + * @access public + * @param string + * @return void + */ + function append_output($output) + { + if ($this->final_output == '') + { + $this->final_output = $output; + } + else + { + $this->final_output .= $output; + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Set Header + * + * Lets you set a server header which will be outputted with the final display. + * + * Note: If a file is cached, headers will not be sent. We need to figure out + * how to permit header data to be saved with the cache data... + * + * @access public + * @param string + * @param bool + * @return void + */ + 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->headers[] = array($header, $replace); + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Set Content Type Header + * + * @access public + * @param string extension of the file we're outputting + * @return void + */ + function set_content_type($mime_type) + { + if (strpos($mime_type, '/') === FALSE) + { + $extension = ltrim($mime_type, '.'); + + // Is this extension supported? + if (isset($this->mime_types[$extension])) + { + $mime_type =& $this->mime_types[$extension]; + + if (is_array($mime_type)) + { + $mime_type = current($mime_type); + } + } + } + + $header = 'Content-Type: '.$mime_type; + + $this->headers[] = array($header, TRUE); + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Set HTTP Status Header + * moved to Common procedural functions in 1.7.2 + * + * @access public + * @param int the status code + * @param string + * @return void + */ + function set_status_header($code = 200, $text = '') + { + set_status_header($code, $text); + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Enable/disable Profiler + * + * @access public + * @param bool + * @return void + */ + 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 + * + * @access public + * @param array + * @return void + */ + function set_profiler_sections($sections) + { + foreach ($sections as $section => $enable) + { + $this->_profiler_sections[$section] = ($enable !== FALSE) ? TRUE : FALSE; + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Set Cache + * + * @access public + * @param integer + * @return void + */ + function cache($time) + { + $this->cache_expiration = ( ! is_numeric($time)) ? 0 : $time; + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Display Output + * + * All "view" data is automatically put into this variable by the controller class: + * + * $this->final_output + * + * This function sends the finalized output data to the browser along + * with any server headers and profile data. It also stops the + * benchmark timer so the page rendering speed and memory usage can be shown. + * + * @access public + * @param string + * @return mixed + */ + function _display($output = '') + { + // Note: We use globals 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. + global $BM, $CFG; + + // Grab the super object if we can. + if (class_exists('CI_Controller')) + { + $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 = ( ! function_exists('memory_get_usage')) ? '0' : round(memory_get_usage()/1024/1024, 2).'MB'; + + $output = str_replace('{elapsed_time}', $elapsed, $output); + $output = str_replace('{memory_usage}', $memory, $output); + } + + // -------------------------------------------------------------------- + + // Is compression requested? + if ($CFG->item('compress_output') === TRUE && $this->_zlib_oc == FALSE) + { + if (extension_loaded('zlib')) + { + if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) AND 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)) + { + echo $output; + log_message('debug', "Final output sent to browser"); + log_message('debug', "Total execution time: ".$elapsed); + return TRUE; + } + + // -------------------------------------------------------------------- + + // 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 and tags + // we will remove them and add them back after we insert the profile data + if (preg_match("|.*?|is", $output)) + { + $output = preg_replace("|.*?|is", '', $output); + $output .= $CI->profiler->run(); + $output .= ''; + } + else + { + $output .= $CI->profiler->run(); + } + } + + // -------------------------------------------------------------------- + + // 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('debug', "Final output sent to browser"); + log_message('debug', "Total execution time: ".$elapsed); + } + + // -------------------------------------------------------------------- + + /** + * Write a Cache File + * + * @access public + * @param string + * @return void + */ + 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(); + + $cache_path .= md5($uri); + + if ( ! $fp = @fopen($cache_path, FOPEN_WRITE_CREATE_DESTRUCTIVE)) + { + log_message('error', "Unable to write cache file: ".$cache_path); + return; + } + + $expire = time() + ($this->cache_expiration * 60); + + if (flock($fp, LOCK_EX)) + { + fwrite($fp, $expire.'TS--->'.$output); + flock($fp, LOCK_UN); + } + else + { + log_message('error', "Unable to secure a file lock for file at: ".$cache_path); + return; + } + fclose($fp); + @chmod($cache_path, FILE_WRITE_MODE); + + log_message('debug', "Cache file written: ".$cache_path); + } + + // -------------------------------------------------------------------- + + /** + * Update/serve a cached file + * + * @access public + * @param object config class + * @param object uri class + * @return void + */ + 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; + + $filepath = $cache_path.md5($uri); + + if ( ! @file_exists($filepath)) + { + return FALSE; + } + + if ( ! $fp = @fopen($filepath, FOPEN_READ)) + { + return FALSE; + } + + flock($fp, LOCK_SH); + + $cache = ''; + if (filesize($filepath) > 0) + { + $cache = fread($fp, filesize($filepath)); + } + + flock($fp, LOCK_UN); + fclose($fp); + + // Strip out the embedded timestamp + if ( ! preg_match("/(\d+TS--->)/", $cache, $match)) + { + return FALSE; + } + + // Has the file expired? If so we'll delete it. + if (time() >= trim(str_replace('TS--->', '', $match['1']))) + { + if (is_really_writable($cache_path)) + { + @unlink($filepath); + log_message('debug', "Cache file has expired. File deleted"); + return FALSE; + } + } + + // Display the cache + $this->_display(str_replace($match['0'], '', $cache)); + log_message('debug', "Cache file is current. Sending it to browser."); + return TRUE; + } + + +} +// END Output Class + +/* End of file Output.php */ +/* Location: ./system/core/Output.php */ \ No newline at end of file diff --git a/system/core/Router.php b/system/core/Router.php new file mode 100755 index 0000000..6da6674 --- /dev/null +++ b/system/core/Router.php @@ -0,0 +1,522 @@ +config =& load_class('Config', 'core'); + $this->uri =& load_class('URI', 'core'); + log_message('debug', "Router Class Initialized"); + } + + // -------------------------------------------------------------------- + + /** + * Set the route mapping + * + * This function determines what should be served based on the URI request, + * as well as any "routes" that have been set in the routing config file. + * + * @access private + * @return void + */ + function _set_routing() + { + // 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 + $segments = array(); + if ($this->config->item('enable_query_strings') === TRUE AND isset($_GET[$this->config->item('controller_trigger')])) + { + if (isset($_GET[$this->config->item('directory_trigger')])) + { + $this->set_directory(trim($this->uri->_filter_uri($_GET[$this->config->item('directory_trigger')]))); + $segments[] = $this->fetch_directory(); + } + + if (isset($_GET[$this->config->item('controller_trigger')])) + { + $this->set_class(trim($this->uri->_filter_uri($_GET[$this->config->item('controller_trigger')]))); + $segments[] = $this->fetch_class(); + } + + if (isset($_GET[$this->config->item('function_trigger')])) + { + $this->set_method(trim($this->uri->_filter_uri($_GET[$this->config->item('function_trigger')]))); + $segments[] = $this->fetch_method(); + } + } + + // Load the routes.php file. + if (defined('ENVIRONMENT') AND is_file(APPPATH.'config/'.ENVIRONMENT.'/routes.php')) + { + include(APPPATH.'config/'.ENVIRONMENT.'/routes.php'); + } + elseif (is_file(APPPATH.'config/routes.php')) + { + include(APPPATH.'config/routes.php'); + } + + $this->routes = ( ! isset($route) OR ! is_array($route)) ? array() : $route; + unset($route); + + // Set the default controller so we can display it in the event + // the URI doesn't correlated to a valid controller. + $this->default_controller = ( ! isset($this->routes['default_controller']) OR $this->routes['default_controller'] == '') ? FALSE : strtolower($this->routes['default_controller']); + + // Were there any query string segments? If so, we'll validate them and bail out since we're done. + if (count($segments) > 0) + { + return $this->_validate_request($segments); + } + + // Fetch the complete URI string + $this->uri->_fetch_uri_string(); + + // Is there a URI string? If not, the default controller specified in the "routes" file will be shown. + if ($this->uri->uri_string == '') + { + return $this->_set_default_controller(); + } + + // Do we need to remove the URL suffix? + $this->uri->_remove_url_suffix(); + + // Compile the segments into an array + $this->uri->_explode_segments(); + + // Parse any custom routing that may exist + $this->_parse_routes(); + + // Re-index the segment array so that it starts with 1 rather than 0 + $this->uri->_reindex_segments(); + } + + // -------------------------------------------------------------------- + + /** + * Set the default controller + * + * @access private + * @return void + */ + function _set_default_controller() + { + if ($this->default_controller === FALSE) + { + 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 (strpos($this->default_controller, '/') !== FALSE) + { + $x = explode('/', $this->default_controller); + + $this->set_class($x[0]); + $this->set_method($x[1]); + $this->_set_request($x); + } + else + { + $this->set_class($this->default_controller); + $this->set_method('index'); + $this->_set_request(array($this->default_controller, 'index')); + } + + // re-index the routed segments array so it starts with 1 rather than 0 + $this->uri->_reindex_segments(); + + log_message('debug', "No URI present. Default controller set."); + } + + // -------------------------------------------------------------------- + + /** + * Set the Route + * + * This function takes an array of URI segments as + * input, and sets the current class/method + * + * @access private + * @param array + * @param bool + * @return void + */ + function _set_request($segments = array()) + { + $segments = $this->_validate_request($segments); + + if (count($segments) == 0) + { + return $this->_set_default_controller(); + } + + $this->set_class($segments[0]); + + if (isset($segments[1])) + { + // A standard method request + $this->set_method($segments[1]); + } + else + { + // This lets the "routed" segment array identify that the default + // index method is being used. + $segments[1] = 'index'; + } + + // Update our "routed" segment array to contain the segments. + // Note: If there is no custom routing, this array will be + // identical to $this->uri->segments + $this->uri->rsegments = $segments; + } + + // -------------------------------------------------------------------- + + /** + * Validates the supplied segments. Attempts to determine the path to + * the controller. + * + * @access private + * @param array + * @return array + */ + function _validate_request($segments) + { + if (count($segments) == 0) + { + return $segments; + } + + // Does the requested controller exist in the root folder? + if (file_exists(APPPATH.'controllers/'.$segments[0].'.php')) + { + return $segments; + } + + // Is the controller in a sub-folder? + if (is_dir(APPPATH.'controllers/'.$segments[0])) + { + // Set the directory and remove it from the segment array + $this->set_directory($segments[0]); + $segments = array_slice($segments, 1); + + if (count($segments) > 0) + { + // Does the requested controller exist in the sub-folder? + if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$segments[0].'.php')) + { + if ( ! empty($this->routes['404_override'])) + { + $x = explode('/', $this->routes['404_override']); + + $this->set_directory(''); + $this->set_class($x[0]); + $this->set_method(isset($x[1]) ? $x[1] : 'index'); + + return $x; + } + else + { + show_404($this->fetch_directory().$segments[0]); + } + } + } + else + { + // Is the method being specified in the route? + if (strpos($this->default_controller, '/') !== FALSE) + { + $x = explode('/', $this->default_controller); + + $this->set_class($x[0]); + $this->set_method($x[1]); + } + else + { + $this->set_class($this->default_controller); + $this->set_method('index'); + } + + // Does the default controller exist in the sub-folder? + if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$this->default_controller.'.php')) + { + $this->directory = ''; + return array(); + } + + } + + return $segments; + } + + + // If we've gotten this far it means that the URI does not correlate to a valid + // controller class. We will now see if there is an override + if ( ! empty($this->routes['404_override'])) + { + $x = explode('/', $this->routes['404_override']); + + $this->set_class($x[0]); + $this->set_method(isset($x[1]) ? $x[1] : 'index'); + + return $x; + } + + + // Nothing else to do at this point but show a 404 + show_404($segments[0]); + } + + // -------------------------------------------------------------------- + + /** + * Parse Routes + * + * This function 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. + * + * @access private + * @return void + */ + function _parse_routes() + { + // Turn the segment array into a URI string + $uri = implode('/', $this->uri->segments); + + // Is there a literal match? If so we're done + if (isset($this->routes[$uri])) + { + return $this->_set_request(explode('/', $this->routes[$uri])); + } + + // Loop through the route array looking for wild-cards + foreach ($this->routes as $key => $val) + { + // Convert wild-cards to RegEx + $key = str_replace(':any', '.+', str_replace(':num', '[0-9]+', $key)); + + // Does the RegEx match? + if (preg_match('#^'.$key.'$#', $uri)) + { + // Do we have a back-reference? + if (strpos($val, '$') !== FALSE AND strpos($key, '(') !== FALSE) + { + $val = preg_replace('#^'.$key.'$#', $val, $uri); + } + + return $this->_set_request(explode('/', $val)); + } + } + + // 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($this->uri->segments); + } + + // -------------------------------------------------------------------- + + /** + * Set the class name + * + * @access public + * @param string + * @return void + */ + function set_class($class) + { + $this->class = str_replace(array('/', '.'), '', $class); + } + + // -------------------------------------------------------------------- + + /** + * Fetch the current class + * + * @access public + * @return string + */ + function fetch_class() + { + return $this->class; + } + + // -------------------------------------------------------------------- + + /** + * Set the method name + * + * @access public + * @param string + * @return void + */ + function set_method($method) + { + $this->method = $method; + } + + // -------------------------------------------------------------------- + + /** + * Fetch the current method + * + * @access public + * @return string + */ + function fetch_method() + { + if ($this->method == $this->fetch_class()) + { + return 'index'; + } + + return $this->method; + } + + // -------------------------------------------------------------------- + + /** + * Set the directory name + * + * @access public + * @param string + * @return void + */ + function set_directory($dir) + { + $this->directory = str_replace(array('/', '.'), '', $dir).'/'; + } + + // -------------------------------------------------------------------- + + /** + * Fetch the sub-directory (if any) that contains the requested controller class + * + * @access public + * @return string + */ + function fetch_directory() + { + return $this->directory; + } + + // -------------------------------------------------------------------- + + /** + * Set the controller overrides + * + * @access public + * @param array + * @return null + */ + function _set_overrides($routing) + { + if ( ! is_array($routing)) + { + return; + } + + if (isset($routing['directory'])) + { + $this->set_directory($routing['directory']); + } + + if (isset($routing['controller']) AND $routing['controller'] != '') + { + $this->set_class($routing['controller']); + } + + if (isset($routing['function'])) + { + $routing['function'] = ($routing['function'] == '') ? 'index' : $routing['function']; + $this->set_method($routing['function']); + } + } + + +} +// END Router Class + +/* End of file Router.php */ +/* Location: ./system/core/Router.php */ \ No newline at end of file diff --git a/system/core/Security.php b/system/core/Security.php new file mode 100755 index 0000000..00089d7 --- /dev/null +++ b/system/core/Security.php @@ -0,0 +1,876 @@ + '[removed]', + 'document.write' => '[removed]', + '.parentNode' => '[removed]', + '.innerHTML' => '[removed]', + 'window.location' => '[removed]', + '-moz-binding' => '[removed]', + '' => '-->', + ' '<![CDATA[', + '' => '<comment>' + ); + + /* never allowed, regex replacement */ + /** + * List of never allowed regex replacement + * + * @var array + * @access protected + */ + protected $_never_allowed_regex = array( + 'javascript\s*:', + 'expression\s*(\(|&\#40;)', // CSS and IE + 'vbscript\s*:', // IE, surprise! + 'Redirect\s+302', + "([\"'])?data\s*:[^\\1]*?base64[^\\1]*?,[^\\1]*?\\1?" + ); + + /** + * Constructor + * + * @return void + */ + public function __construct() + { + // Is CSRF protection enabled? + if (config_item('csrf_protection') === TRUE) + { + // CSRF config + foreach (array('csrf_expire', 'csrf_token_name', 'csrf_cookie_name') as $key) + { + if (FALSE !== ($val = config_item($key))) + { + $this->{'_'.$key} = $val; + } + } + + // Append application specific cookie prefix + if (config_item('cookie_prefix')) + { + $this->_csrf_cookie_name = config_item('cookie_prefix').$this->_csrf_cookie_name; + } + + // Set the CSRF hash + $this->_csrf_set_hash(); + } + + log_message('debug', "Security Class Initialized"); + } + + // -------------------------------------------------------------------- + + /** + * Verify Cross Site Request Forgery Protection + * + * @return object + */ + 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(); + } + + // Do the tokens exist in both the _POST and _COOKIE arrays? + if ( ! isset($_POST[$this->_csrf_token_name], $_COOKIE[$this->_csrf_cookie_name])) + { + $this->csrf_show_error(); + } + + // Do the tokens match? + if ($_POST[$this->_csrf_token_name] != $_COOKIE[$this->_csrf_cookie_name]) + { + $this->csrf_show_error(); + } + + // We kill this since we're done and we don't want to + // polute the _POST array + unset($_POST[$this->_csrf_token_name]); + + // Nothing should last forever + unset($_COOKIE[$this->_csrf_cookie_name]); + $this->_csrf_set_hash(); + $this->csrf_set_cookie(); + + log_message('debug', 'CSRF token verified'); + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Set Cross Site Request Forgery Protection Cookie + * + * @return object + */ + public function csrf_set_cookie() + { + $expire = time() + $this->_csrf_expire; + $secure_cookie = (config_item('cookie_secure') === TRUE) ? 1 : 0; + + if ($secure_cookie && (empty($_SERVER['HTTPS']) OR strtolower($_SERVER['HTTPS']) === 'off')) + { + return FALSE; + } + + setcookie($this->_csrf_cookie_name, $this->_csrf_hash, $expire, config_item('cookie_path'), config_item('cookie_domain'), $secure_cookie); + + log_message('debug', "CRSF cookie Set"); + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Show CSRF Error + * + * @return void + */ + public function csrf_show_error() + { + show_error('The action you have requested is not allowed.'); + } + + // -------------------------------------------------------------------- + + /** + * Get CSRF Hash + * + * Getter Method + * + * @return string self::_csrf_hash + */ + public function get_csrf_hash() + { + return $this->_csrf_hash; + } + + // -------------------------------------------------------------------- + + /** + * Get CSRF Token Name + * + * Getter Method + * + * @return string self::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 function 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: This function should only be used to deal with data + * upon submission. It's not something that should + * be used for general runtime processing. + * + * This function was based in part on some code and ideas I + * got from Bitflux: http://channel.bitflux.ch/wiki/XSS_Prevention + * + * 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: + * http://ha.ckers.org/xss.html + * + * @param mixed string or array + * @param bool + * @return string + */ + public function xss_clean($str, $is_image = FALSE) + { + /* + * Is the string an array? + * + */ + if (is_array($str)) + { + while (list($key) = each($str)) + { + $str[$key] = $this->xss_clean($str[$key]); + } + + return $str; + } + + /* + * Remove Invisible Characters + */ + $str = remove_invisible_characters($str); + + // Validate Entities in URLs + $str = $this->_validate_entities($str); + + /* + * URL Decode + * + * Just in case stuff like this is submitted: + * + * Google + * + * Note: Use rawurldecode() so it does not remove plus signs + * + */ + $str = rawurldecode($str); + + /* + * 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-z]+=([\'\"]).*?\\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. + */ + + if (strpos($str, "\t") !== FALSE) + { + $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: + * + * '), array('<?', '?>'), $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', 'script', 'base64', + 'applet', 'alert', 'document', 'write', 'cookie', 'window' + ); + + foreach ($words as $word) + { + $temp = ''; + + for ($i = 0, $wordlen = strlen($word); $i < $wordlen; $i++) + { + $temp .= substr($word, $i, 1)."\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($temp, 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 for PHP5, + * but it is dog slow compared to these simplified non-capturing + * preg_match(), especially if the pattern exists in the string + */ + do + { + $original = $str; + + if (preg_match("/]*?)(>|$)#si", array($this, '_js_link_removal'), $str); + } + + if (preg_match("/]*?)(\s?/?>|$)#si", array($this, '_js_img_removal'), $str); + } + + if (preg_match("/script/i", $str) OR preg_match("/xss/i", $str)) + { + $str = preg_replace("#<(/*)(script|xss)(.*?)\>#si", '[removed]', $str); + } + } + while($original != $str); + + unset($original); + + // Remove evil attributes such as style, onclick and xmlns + $str = $this->_remove_evil_attributes($str, $is_image); + + /* + * 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: + * Becomes: <blink> + */ + $naughty = 'alert|applet|audio|basefont|base|behavior|bgsound|blink|body|embed|expression|form|frameset|frame|head|html|ilayer|iframe|input|isindex|layer|link|meta|object|plaintext|style|script|textarea|title|video|xml|xss'; + $str = preg_replace_callback('#<(/*\s*)('.$naughty.')([^><]*)([><]*)#is', array($this, '_sanitize_naughty_html'), $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('some code') + */ + $str = preg_replace('#(alert|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)\((.*?)\)#si', "\\1\\2(\\3)", $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) ? TRUE: FALSE; + } + + log_message('debug', "XSS Filtering completed"); + return $str; + } + + // -------------------------------------------------------------------- + + /** + * Random Hash for protecting URLs + * + * @return string + */ + public function xss_hash() + { + if ($this->_xss_hash == '') + { + mt_srand(); + $this->_xss_hash = md5(time() + mt_rand(0, 1999999999)); + } + + return $this->_xss_hash; + } + + // -------------------------------------------------------------------- + + /** + * HTML Entities Decode + * + * This function is 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. + * + * @param string + * @param string + * @return string + */ + public function entity_decode($str, $charset='UTF-8') + { + if (stristr($str, '&') === FALSE) + { + return $str; + } + + $str = html_entity_decode($str, ENT_COMPAT, $charset); + $str = preg_replace('~&#x(0*[0-9a-f]{2,5})~ei', 'chr(hexdec("\\1"))', $str); + return preg_replace('~&#([0-9]{2,4})~e', 'chr(\\1)', $str); + } + + // -------------------------------------------------------------------- + + /** + * Filename Security + * + * @param string + * @param bool + * @return string + */ + public function sanitize_filename($str, $relative_path = FALSE) + { + $bad = array( + "../", + "", + "<", + ">", + "'", + '"', + '&', + '$', + '#', + '{', + '}', + '[', + ']', + '=', + ';', + '?', + "%20", + "%22", + "%3c", // < + "%253c", // < + "%3e", // > + "%0e", // > + "%28", // ( + "%29", // ) + "%2528", // ( + "%26", // & + "%24", // $ + "%3f", // ? + "%3b", // ; + "%3d" // = + ); + + if ( ! $relative_path) + { + $bad[] = './'; + $bad[] = '/'; + } + + $str = remove_invisible_characters($str, FALSE); + return stripslashes(str_replace($bad, '', $str)); + } + + // ---------------------------------------------------------------- + + /** + * Compact Exploded Words + * + * Callback function for xss_clean() to remove whitespace from + * things like j a v a s c r i p t + * + * @param type + * @return type + */ + protected function _compact_exploded_words($matches) + { + return preg_replace('/\s+/s', '', $matches[1]).$matches[2]; + } + + // -------------------------------------------------------------------- + + /* + * Remove Evil HTML Attributes (like evenhandlers and style) + * + * It removes the evil attribute and either: + * - Everything up until a space + * For example, everything between the pipes: + * + * - Everything inside the quotes + * For example, everything between the pipes: + * + * + * @param string $str The string to check + * @param boolean $is_image TRUE if this is an image + * @return string The string with the evil attributes removed + */ + protected function _remove_evil_attributes($str, $is_image) + { + // All javascript event handlers (e.g. onload, onclick, onmouseover), style, and xmlns + $evil_attributes = array('on\w*', 'style', 'xmlns', 'formaction'); + + if ($is_image === TRUE) + { + /* + * Adobe Photoshop puts XML metadata into JFIF images, + * including namespacing, so we have to allow this for images. + */ + unset($evil_attributes[array_search('xmlns', $evil_attributes)]); + } + + do { + $count = 0; + $attribs = array(); + + // find occurrences of illegal attribute strings without quotes + preg_match_all('/('.implode('|', $evil_attributes).')\s*=\s*([^\s>]*)/is', $str, $matches, PREG_SET_ORDER); + + foreach ($matches as $attr) + { + + $attribs[] = preg_quote($attr[0], '/'); + } + + // find occurrences of illegal attribute strings with quotes (042 and 047 are octal quotes) + preg_match_all("/(".implode('|', $evil_attributes).")\s*=\s*(\042|\047)([^\\2]*?)(\\2)/is", $str, $matches, PREG_SET_ORDER); + + foreach ($matches as $attr) + { + $attribs[] = preg_quote($attr[0], '/'); + } + + // replace illegal attribute strings that are inside an html tag + if (count($attribs) > 0) + { + $str = preg_replace("/<(\/?[^><]+?)([^A-Za-z<>\-])(.*?)(".implode('|', $attribs).")(.*?)([\s><])([><]*)/i", '<$1 $3$5$6$7', $str, -1, $count); + } + + } while ($count); + + return $str; + } + + // -------------------------------------------------------------------- + + /** + * Sanitize Naughty HTML + * + * Callback function for xss_clean() to remove naughty HTML elements + * + * @param array + * @return string + */ + protected function _sanitize_naughty_html($matches) + { + // encode opening brace + $str = '<'.$matches[1].$matches[2].$matches[3]; + + // encode captured opening or closing brace to prevent recursive vectors + $str .= str_replace(array('>', '<'), array('>', '<'), + $matches[4]); + + return $str; + } + + // -------------------------------------------------------------------- + + /** + * JS Link Removal + * + * Callback function 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 + * + * @param array + * @return string + */ + protected function _js_link_removal($match) + { + return str_replace( + $match[1], + preg_replace( + '#href=.*?(alert\(|alert&\#40;|javascript\:|livescript\:|mocha\:|charset\=|window\.|document\.|\.cookie|_filter_attributes(str_replace(array('<', '>'), '', $match[1])) + ), + $match[0] + ); + } + + // -------------------------------------------------------------------- + + /** + * JS Image Removal + * + * Callback function 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 + * + * @param array + * @return string + */ + protected function _js_img_removal($match) + { + return str_replace( + $match[1], + preg_replace( + '#src=.*?(alert\(|alert&\#40;|javascript\:|livescript\:|mocha\:|charset\=|window\.|document\.|\.cookie|_filter_attributes(str_replace(array('<', '>'), '', $match[1])) + ), + $match[0] + ); + } + + // -------------------------------------------------------------------- + + /** + * Attribute Conversion + * + * Used as a callback for XSS Clean + * + * @param array + * @return string + */ + protected function _convert_attribute($match) + { + return str_replace(array('>', '<', '\\'), array('>', '<', '\\\\'), $match[0]); + } + + // -------------------------------------------------------------------- + + /** + * Filter Attributes + * + * Filters tag attributes for consistency and safety + * + * @param string + * @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 as a callback for XSS Clean + * + * @param array + * @return string + */ + protected function _decode_entity($match) + { + return $this->entity_decode($match[0], strtoupper(config_item('charset'))); + } + + // -------------------------------------------------------------------- + + /** + * Validate URL entities + * + * Called by xss_clean() + * + * @param string + * @return string + */ + protected function _validate_entities($str) + { + /* + * Protect GET variables in URLs + */ + + // 901119URL5918AMP18930PROTECT8198 + + $str = preg_replace('|\&([a-z\_0-9\-]+)\=([a-z\_0-9\-]+)|i', $this->xss_hash()."\\1=\\2", $str); + + /* + * Validate standard character entities + * + * Add a semicolon if missing. We do this to enable + * the conversion of entities to ASCII later. + * + */ + $str = preg_replace('#(&\#?[0-9a-z]{2,})([\x00-\x20])*;?#i', "\\1;\\2", $str); + + /* + * Validate UTF16 two byte encoding (x00) + * + * Just as above, adds a semicolon if missing. + * + */ + $str = preg_replace('#(&\#x?)([0-9A-F]+);?#i',"\\1\\2;",$str); + + /* + * Un-Protect GET variables in URLs + */ + $str = str_replace($this->xss_hash(), '&', $str); + + return $str; + } + + // ---------------------------------------------------------------------- + + /** + * Do Never Allowed + * + * A utility function for 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 Cross Site Request Forgery Protection Cookie + * + * @return string + */ + protected function _csrf_set_hash() + { + if ($this->_csrf_hash == '') + { + // If the cookie exists we will use it's 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]) && + preg_match('#^[0-9a-f]{32}$#iS', $_COOKIE[$this->_csrf_cookie_name]) === 1) + { + return $this->_csrf_hash = $_COOKIE[$this->_csrf_cookie_name]; + } + + return $this->_csrf_hash = md5(uniqid(rand(), TRUE)); + } + + return $this->_csrf_hash; + } + +} + +/* End of file Security.php */ +/* Location: ./system/libraries/Security.php */ \ No newline at end of file diff --git a/system/core/URI.php b/system/core/URI.php new file mode 100755 index 0000000..a3ae20c --- /dev/null +++ b/system/core/URI.php @@ -0,0 +1,654 @@ +config =& load_class('Config', 'core'); + log_message('debug', "URI Class Initialized"); + } + + + // -------------------------------------------------------------------- + + /** + * Get the URI String + * + * @access private + * @return string + */ + function _fetch_uri_string() + { + if (strtoupper($this->config->item('uri_protocol')) == 'AUTO') + { + // Is the request coming from the command line? + if (php_sapi_name() == 'cli' or defined('STDIN')) + { + $this->_set_uri_string($this->_parse_cli_args()); + return; + } + + // Let's try the REQUEST_URI first, this will work in most situations + if ($uri = $this->_detect_uri()) + { + $this->_set_uri_string($uri); + return; + } + + // Is there a PATH_INFO variable? + // Note: some servers seem to have trouble with getenv() so we'll test it two ways + $path = (isset($_SERVER['PATH_INFO'])) ? $_SERVER['PATH_INFO'] : @getenv('PATH_INFO'); + if (trim($path, '/') != '' && $path != "/".SELF) + { + $this->_set_uri_string($path); + return; + } + + // No PATH_INFO?... What about QUERY_STRING? + $path = (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : @getenv('QUERY_STRING'); + if (trim($path, '/') != '') + { + $this->_set_uri_string($path); + return; + } + + // As a last ditch effort lets try using the $_GET array + if (is_array($_GET) && count($_GET) == 1 && trim(key($_GET), '/') != '') + { + $this->_set_uri_string(key($_GET)); + return; + } + + // We've exhausted all our options... + $this->uri_string = ''; + return; + } + + $uri = strtoupper($this->config->item('uri_protocol')); + + if ($uri == 'REQUEST_URI') + { + $this->_set_uri_string($this->_detect_uri()); + return; + } + elseif ($uri == 'CLI') + { + $this->_set_uri_string($this->_parse_cli_args()); + return; + } + + $path = (isset($_SERVER[$uri])) ? $_SERVER[$uri] : @getenv($uri); + $this->_set_uri_string($path); + } + + // -------------------------------------------------------------------- + + /** + * Set the URI String + * + * @access public + * @param string + * @return string + */ + function _set_uri_string($str) + { + // Filter out control characters + $str = remove_invisible_characters($str, FALSE); + + // If the URI contains only a slash we'll kill it + $this->uri_string = ($str == '/') ? '' : $str; + } + + // -------------------------------------------------------------------- + + /** + * Detects the URI + * + * This function will detect the URI automatically and fix the query string + * if necessary. + * + * @access private + * @return string + */ + private function _detect_uri() + { + if ( ! isset($_SERVER['REQUEST_URI']) OR ! isset($_SERVER['SCRIPT_NAME'])) + { + return ''; + } + + $uri = $_SERVER['REQUEST_URI']; + if (strpos($uri, $_SERVER['SCRIPT_NAME']) === 0) + { + $uri = substr($uri, strlen($_SERVER['SCRIPT_NAME'])); + } + elseif (strpos($uri, dirname($_SERVER['SCRIPT_NAME'])) === 0) + { + $uri = 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 (strncmp($uri, '?/', 2) === 0) + { + $uri = substr($uri, 2); + } + $parts = preg_split('#\?#i', $uri, 2); + $uri = $parts[0]; + if (isset($parts[1])) + { + $_SERVER['QUERY_STRING'] = $parts[1]; + parse_str($_SERVER['QUERY_STRING'], $_GET); + } + else + { + $_SERVER['QUERY_STRING'] = ''; + $_GET = array(); + } + + if ($uri == '/' || empty($uri)) + { + return '/'; + } + + $uri = parse_url($uri, PHP_URL_PATH); + + // Do some final cleaning of the URI and return it + return str_replace(array('//', '../'), '/', trim($uri, '/')); + } + + // -------------------------------------------------------------------- + + /** + * Parse cli arguments + * + * Take each command line argument and assume it is a URI segment. + * + * @access private + * @return string + */ + private function _parse_cli_args() + { + $args = array_slice($_SERVER['argv'], 1); + + return $args ? '/' . implode('/', $args) : ''; + } + + // -------------------------------------------------------------------- + + /** + * Filter segments for malicious characters + * + * @access private + * @param string + * @return string + */ + function _filter_uri($str) + { + if ($str != '' && $this->config->item('permitted_uri_chars') != '' && $this->config->item('enable_query_strings') == FALSE) + { + // preg_quote() in PHP 5.3 escapes -, so the str_replace() and addition of - to preg_quote() is to maintain backwards + // compatibility as many are unaware of how characters in the permitted_uri_chars will be parsed as a regex pattern + if ( ! preg_match("|^[".str_replace(array('\\-', '\-'), '-', preg_quote($this->config->item('permitted_uri_chars'), '-'))."]+$|i", $str)) + { + show_error('The URI you submitted has disallowed characters.', 400); + } + } + + // Convert programatic characters to entities + $bad = array('$', '(', ')', '%28', '%29'); + $good = array('$', '(', ')', '(', ')'); + + return str_replace($bad, $good, $str); + } + + // -------------------------------------------------------------------- + + /** + * Remove the suffix from the URL if needed + * + * @access private + * @return void + */ + function _remove_url_suffix() + { + if ($this->config->item('url_suffix') != "") + { + $this->uri_string = preg_replace("|".preg_quote($this->config->item('url_suffix'))."$|", "", $this->uri_string); + } + } + + // -------------------------------------------------------------------- + + /** + * Explode the URI Segments. The individual segments will + * be stored in the $this->segments array. + * + * @access private + * @return void + */ + function _explode_segments() + { + foreach (explode("/", preg_replace("|/*(.+?)/*$|", "\\1", $this->uri_string)) as $val) + { + // Filter segments for security + $val = trim($this->_filter_uri($val)); + + if ($val != '') + { + $this->segments[] = $val; + } + } + } + + // -------------------------------------------------------------------- + /** + * Re-index Segments + * + * This function re-indexes the $this->segment array so that it + * starts at 1 rather than 0. Doing so makes it simpler to + * use functions like $this->uri->segment(n) since there is + * a 1:1 relationship between the segment array and the actual segments. + * + * @access private + * @return void + */ + function _reindex_segments() + { + array_unshift($this->segments, NULL); + array_unshift($this->rsegments, NULL); + unset($this->segments[0]); + unset($this->rsegments[0]); + } + + // -------------------------------------------------------------------- + + /** + * Fetch a URI Segment + * + * This function returns the URI segment based on the number provided. + * + * @access public + * @param integer + * @param bool + * @return string + */ + function segment($n, $no_result = FALSE) + { + return ( ! isset($this->segments[$n])) ? $no_result : $this->segments[$n]; + } + + // -------------------------------------------------------------------- + + /** + * Fetch a URI "routed" Segment + * + * This function returns the re-routed URI segment (assuming routing rules are used) + * based on the number provided. If there is no routing this function returns the + * same result as $this->segment() + * + * @access public + * @param integer + * @param bool + * @return string + */ + function rsegment($n, $no_result = FALSE) + { + return ( ! isset($this->rsegments[$n])) ? $no_result : $this->rsegments[$n]; + } + + // -------------------------------------------------------------------- + + /** + * Generate a key value pair from the URI string + * + * This function generates and associative array of URI data starting + * at the supplied segment. For example, if this is your URI: + * + * example.com/user/search/name/joe/location/UK/gender/male + * + * You can use this function to generate an array with this prototype: + * + * array ( + * name => joe + * location => UK + * gender => male + * ) + * + * @access public + * @param integer the starting segment number + * @param array an array of default values + * @return array + */ + function uri_to_assoc($n = 3, $default = array()) + { + return $this->_uri_to_assoc($n, $default, 'segment'); + } + /** + * Identical to above only it uses the re-routed segment array + * + * @access public + * @param integer the starting segment number + * @param array an array of default values + * @return array + * + */ + function ruri_to_assoc($n = 3, $default = array()) + { + return $this->_uri_to_assoc($n, $default, 'rsegment'); + } + + // -------------------------------------------------------------------- + + /** + * Generate a key value pair from the URI string or Re-routed URI string + * + * @access private + * @param integer the starting segment number + * @param array an array of default values + * @param string which array we should use + * @return array + */ + function _uri_to_assoc($n = 3, $default = array(), $which = 'segment') + { + if ($which == 'segment') + { + $total_segments = 'total_segments'; + $segment_array = 'segment_array'; + } + else + { + $total_segments = 'total_rsegments'; + $segment_array = 'rsegment_array'; + } + + if ( ! is_numeric($n)) + { + return $default; + } + + if (isset($this->keyval[$n])) + { + return $this->keyval[$n]; + } + + if ($this->$total_segments() < $n) + { + if (count($default) == 0) + { + return array(); + } + + $retval = array(); + foreach ($default as $val) + { + $retval[$val] = FALSE; + } + return $retval; + } + + $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] = FALSE; + $lastval = $seg; + } + + $i++; + } + + if (count($default) > 0) + { + foreach ($default as $val) + { + if ( ! array_key_exists($val, $retval)) + { + $retval[$val] = FALSE; + } + } + } + + // Cache the array for reuse + $this->keyval[$n] = $retval; + return $retval; + } + + // -------------------------------------------------------------------- + + /** + * Generate a URI string from an associative array + * + * + * @access public + * @param array an associative array of key/values + * @return array + */ + function assoc_to_uri($array) + { + $temp = array(); + foreach ((array)$array as $key => $val) + { + $temp[] = $key; + $temp[] = $val; + } + + return implode('/', $temp); + } + + // -------------------------------------------------------------------- + + /** + * Fetch a URI Segment and add a trailing slash + * + * @access public + * @param integer + * @param string + * @return string + */ + function slash_segment($n, $where = 'trailing') + { + return $this->_slash_segment($n, $where, 'segment'); + } + + // -------------------------------------------------------------------- + + /** + * Fetch a URI Segment and add a trailing slash + * + * @access public + * @param integer + * @param string + * @return string + */ + function slash_rsegment($n, $where = 'trailing') + { + return $this->_slash_segment($n, $where, 'rsegment'); + } + + // -------------------------------------------------------------------- + + /** + * Fetch a URI Segment and add a trailing slash - helper function + * + * @access private + * @param integer + * @param string + * @param string + * @return string + */ + 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 + * + * @access public + * @return array + */ + function segment_array() + { + return $this->segments; + } + + // -------------------------------------------------------------------- + + /** + * Routed Segment Array + * + * @access public + * @return array + */ + function rsegment_array() + { + return $this->rsegments; + } + + // -------------------------------------------------------------------- + + /** + * Total number of segments + * + * @access public + * @return integer + */ + function total_segments() + { + return count($this->segments); + } + + // -------------------------------------------------------------------- + + /** + * Total number of routed segments + * + * @access public + * @return integer + */ + function total_rsegments() + { + return count($this->rsegments); + } + + // -------------------------------------------------------------------- + + /** + * Fetch the entire URI string + * + * @access public + * @return string + */ + function uri_string() + { + return $this->uri_string; + } + + + // -------------------------------------------------------------------- + + /** + * Fetch the entire Re-routed URI string + * + * @access public + * @return string + */ + function ruri_string() + { + return '/'.implode('/', $this->rsegment_array()); + } + +} +// END URI Class + +/* End of file URI.php */ +/* Location: ./system/core/URI.php */ \ No newline at end of file diff --git a/system/core/Utf8.php b/system/core/Utf8.php new file mode 100644 index 0000000..2a27d1f --- /dev/null +++ b/system/core/Utf8.php @@ -0,0 +1,165 @@ +item('charset') == 'UTF-8' // Application charset must be UTF-8 + ) + { + log_message('debug', "UTF-8 Support Enabled"); + + define('UTF8_ENABLED', TRUE); + + // set internal encoding for multibyte string functions if necessary + // and set a flag so we don't have to repeatedly use extension_loaded() + // or function_exists() + if (extension_loaded('mbstring')) + { + define('MB_ENABLED', TRUE); + mb_internal_encoding('UTF-8'); + } + else + { + define('MB_ENABLED', FALSE); + } + } + else + { + log_message('debug', "UTF-8 Support Disabled"); + define('UTF8_ENABLED', FALSE); + } + } + + // -------------------------------------------------------------------- + + /** + * Clean UTF-8 strings + * + * Ensures strings are UTF-8 + * + * @access public + * @param string + * @return string + */ + function clean_string($str) + { + if ($this->_is_ascii($str) === FALSE) + { + $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 + * + * @access public + * @param string + * @return string + */ + function safe_ascii_for_xml($str) + { + return remove_invisible_characters($str, FALSE); + } + + // -------------------------------------------------------------------- + + /** + * Convert to UTF-8 + * + * Attempts to convert a string to UTF-8 + * + * @access public + * @param string + * @param string - input encoding + * @return string + */ + function convert_to_utf8($str, $encoding) + { + if (function_exists('iconv')) + { + $str = @iconv($encoding, 'UTF-8', $str); + } + elseif (function_exists('mb_convert_encoding')) + { + $str = @mb_convert_encoding($str, 'UTF-8', $encoding); + } + else + { + return FALSE; + } + + return $str; + } + + // -------------------------------------------------------------------- + + /** + * Is ASCII? + * + * Tests if a string is standard 7-bit ASCII or not + * + * @access public + * @param string + * @return bool + */ + function _is_ascii($str) + { + return (preg_match('/[^\x00-\x7F]/S', $str) == 0); + } + + // -------------------------------------------------------------------- + +} +// End Utf8 Class + +/* End of file Utf8.php */ +/* Location: ./system/core/Utf8.php */ \ No newline at end of file diff --git a/system/core/index.html b/system/core/index.html new file mode 100644 index 0000000..c942a79 --- /dev/null +++ b/system/core/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/system/database/DB.php b/system/database/DB.php new file mode 100755 index 0000000..8314d3b --- /dev/null +++ b/system/database/DB.php @@ -0,0 +1,162 @@ + $dns['scheme'], + 'hostname' => (isset($dns['host'])) ? rawurldecode($dns['host']) : '', + 'username' => (isset($dns['user'])) ? rawurldecode($dns['user']) : '', + 'password' => (isset($dns['pass'])) ? rawurldecode($dns['pass']) : '', + 'database' => (isset($dns['path'])) ? rawurldecode(substr($dns['path'], 1)) : '' + ); + + // were additional config items set? + if (isset($dns['query'])) + { + parse_str($dns['query'], $extra); + + foreach ($extra as $key => $val) + { + // booleans please + if (strtoupper($val) == "TRUE") + { + $val = TRUE; + } + elseif (strtoupper($val) == "FALSE") + { + $val = FALSE; + } + + $params[$key] = $val; + } + } + } + + // No DB specified yet? Beat them senseless... + if ( ! isset($params['dbdriver']) OR $params['dbdriver'] == '') + { + show_error('You have not selected a database type to connect to.'); + } + + // Load the DB classes. Note: Since the active record class is optional + // we need to dynamically create a class that extends proper parent class + // based on whether we're using the active record class or not. + // Kudos to Paul for discovering this clever use of eval() + + if ($active_record_override !== NULL) + { + $active_record = $active_record_override; + } + + require_once(BASEPATH.'database/DB_driver.php'); + + if ( ! isset($active_record) OR $active_record == TRUE) + { + require_once(BASEPATH.'database/DB_active_rec.php'); + + if ( ! class_exists('CI_DB')) + { + eval('class CI_DB extends CI_DB_active_record { }'); + } + } + else + { + if ( ! class_exists('CI_DB')) + { + eval('class CI_DB extends CI_DB_driver { }'); + } + } + + require_once(BASEPATH.'database/drivers/'.$params['dbdriver'].'/'.$params['dbdriver'].'_driver.php'); + + // Instantiate the DB adapter + $driver = 'CI_DB_'.$params['dbdriver'].'_driver'; + $DB = new $driver($params); + + if ($DB->autoinit == TRUE) + { + $DB->initialize(); + } + + if (isset($params['stricton']) && $params['stricton'] == TRUE) + { + $DB->query('SET SESSION sql_mode="STRICT_ALL_TABLES"'); + } + + return $DB; +} + + + +/* End of file DB.php */ +/* Location: ./system/database/DB.php */ \ No newline at end of file diff --git a/system/database/DB_active_rec.php b/system/database/DB_active_rec.php new file mode 100644 index 0000000..10febb1 --- /dev/null +++ b/system/database/DB_active_rec.php @@ -0,0 +1,2045 @@ +ar_select[] = $val; + $this->ar_no_escape[] = $escape; + + if ($this->ar_caching === TRUE) + { + $this->ar_cache_select[] = $val; + $this->ar_cache_exists[] = 'select'; + $this->ar_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 object + */ + 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 object + */ + 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 object + */ + 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 object + */ + public function select_sum($select = '', $alias = '') + { + return $this->_max_min_avg_sum($select, $alias, 'SUM'); + } + + // -------------------------------------------------------------------- + + /** + * Processing Function for the four functions above: + * + * select_max() + * select_min() + * select_avg() + * select_sum() + * + * @param string the field + * @param string an alias + * @return object + */ + 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 '.$alias; + + $this->ar_select[] = $sql; + + if ($this->ar_caching === TRUE) + { + $this->ar_cache_select[] = $sql; + $this->ar_cache_exists[] = 'select'; + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Determines the alias name based on the table + * + * @param string + * @return string + */ + protected function _create_alias_from_table($item) + { + if (strpos($item, '.') !== FALSE) + { + return end(explode('.', $item)); + } + + return $item; + } + + // -------------------------------------------------------------------- + + /** + * DISTINCT + * + * Sets a flag which tells the query string compiler to add DISTINCT + * + * @param bool + * @return object + */ + public function distinct($val = TRUE) + { + $this->ar_distinct = (is_bool($val)) ? $val : TRUE; + return $this; + } + + // -------------------------------------------------------------------- + + /** + * From + * + * Generates the FROM portion of the query + * + * @param mixed can be a string or array + * @return object + */ + 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->ar_from[] = $this->_protect_identifiers($v, TRUE, NULL, FALSE); + + if ($this->ar_caching === TRUE) + { + $this->ar_cache_from[] = $this->_protect_identifiers($v, TRUE, NULL, FALSE); + $this->ar_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->ar_from[] = $this->_protect_identifiers($val, TRUE, NULL, FALSE); + + if ($this->ar_caching === TRUE) + { + $this->ar_cache_from[] = $this->_protect_identifiers($val, TRUE, NULL, FALSE); + $this->ar_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 + * @return object + */ + public function join($table, $cond, $type = '') + { + if ($type != '') + { + $type = strtoupper(trim($type)); + + if ( ! in_array($type, array('LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER'))) + { + $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); + + // Strip apart the condition and protect the identifiers + if (preg_match('/([\w\.]+)([\W\s]+)(.+)/', $cond, $match)) + { + $match[1] = $this->_protect_identifiers($match[1]); + $match[3] = $this->_protect_identifiers($match[3]); + + $cond = $match[1].$match[2].$match[3]; + } + + // Assemble the JOIN statement + $join = $type.'JOIN '.$this->_protect_identifiers($table, TRUE, NULL, FALSE).' ON '.$cond; + + $this->ar_join[] = $join; + if ($this->ar_caching === TRUE) + { + $this->ar_cache_join[] = $join; + $this->ar_cache_exists[] = 'join'; + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Where + * + * Generates the WHERE portion of the query. Separates + * multiple calls with AND + * + * @param mixed + * @param mixed + * @return object + */ + public function where($key, $value = NULL, $escape = TRUE) + { + return $this->_where($key, $value, 'AND ', $escape); + } + + // -------------------------------------------------------------------- + + /** + * OR Where + * + * Generates the WHERE portion of the query. Separates + * multiple calls with OR + * + * @param mixed + * @param mixed + * @return object + */ + public function or_where($key, $value = NULL, $escape = TRUE) + { + return $this->_where($key, $value, 'OR ', $escape); + } + + // -------------------------------------------------------------------- + + /** + * Where + * + * Called by where() or or_where() + * + * @param mixed + * @param mixed + * @param string + * @return object + */ + protected function _where($key, $value = NULL, $type = 'AND ', $escape = NULL) + { + if ( ! is_array($key)) + { + $key = array($key => $value); + } + + // If the escape value was not set will will base it on the global setting + if ( ! is_bool($escape)) + { + $escape = $this->_protect_identifiers; + } + + foreach ($key as $k => $v) + { + $prefix = (count($this->ar_where) == 0 AND count($this->ar_cache_where) == 0) ? '' : $type; + + if (is_null($v) && ! $this->_has_operator($k)) + { + // value appears not to have been set, assign the test to IS NULL + $k .= ' IS NULL'; + } + + if ( ! is_null($v)) + { + if ($escape === TRUE) + { + $k = $this->_protect_identifiers($k, FALSE, $escape); + + $v = ' '.$this->escape($v); + } + + if ( ! $this->_has_operator($k)) + { + $k .= ' = '; + } + } + else + { + $k = $this->_protect_identifiers($k, FALSE, $escape); + } + + $this->ar_where[] = $prefix.$k.$v; + + if ($this->ar_caching === TRUE) + { + $this->ar_cache_where[] = $prefix.$k.$v; + $this->ar_cache_exists[] = 'where'; + } + + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Where_in + * + * Generates a WHERE field IN ('item', 'item') SQL query joined with + * AND if appropriate + * + * @param string The field to search + * @param array The values searched on + * @return object + */ + public function where_in($key = NULL, $values = NULL) + { + return $this->_where_in($key, $values); + } + + // -------------------------------------------------------------------- + + /** + * Where_in_or + * + * Generates a WHERE field IN ('item', 'item') SQL query joined with + * OR if appropriate + * + * @param string The field to search + * @param array The values searched on + * @return object + */ + public function or_where_in($key = NULL, $values = NULL) + { + return $this->_where_in($key, $values, FALSE, 'OR '); + } + + // -------------------------------------------------------------------- + + /** + * Where_not_in + * + * Generates a WHERE field NOT IN ('item', 'item') SQL query joined + * with AND if appropriate + * + * @param string The field to search + * @param array The values searched on + * @return object + */ + public function where_not_in($key = NULL, $values = NULL) + { + return $this->_where_in($key, $values, TRUE); + } + + // -------------------------------------------------------------------- + + /** + * Where_not_in_or + * + * Generates a WHERE field NOT IN ('item', 'item') SQL query joined + * with OR if appropriate + * + * @param string The field to search + * @param array The values searched on + * @return object + */ + public function or_where_not_in($key = NULL, $values = NULL) + { + return $this->_where_in($key, $values, TRUE, 'OR '); + } + + // -------------------------------------------------------------------- + + /** + * Where_in + * + * Called by where_in, where_in_or, where_not_in, where_not_in_or + * + * @param string The field to search + * @param array The values searched on + * @param boolean If the statement would be IN or NOT IN + * @param string + * @return object + */ + protected function _where_in($key = NULL, $values = NULL, $not = FALSE, $type = 'AND ') + { + if ($key === NULL OR $values === NULL) + { + return; + } + + if ( ! is_array($values)) + { + $values = array($values); + } + + $not = ($not) ? ' NOT' : ''; + + foreach ($values as $value) + { + $this->ar_wherein[] = $this->escape($value); + } + + $prefix = (count($this->ar_where) == 0) ? '' : $type; + + $where_in = $prefix . $this->_protect_identifiers($key) . $not . " IN (" . implode(", ", $this->ar_wherein) . ") "; + + $this->ar_where[] = $where_in; + if ($this->ar_caching === TRUE) + { + $this->ar_cache_where[] = $where_in; + $this->ar_cache_exists[] = 'where'; + } + + // reset the array for multiple calls + $this->ar_wherein = array(); + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Like + * + * Generates a %LIKE% portion of the query. Separates + * multiple calls with AND + * + * @param mixed + * @param mixed + * @return object + */ + public function like($field, $match = '', $side = 'both') + { + return $this->_like($field, $match, 'AND ', $side); + } + + // -------------------------------------------------------------------- + + /** + * Not Like + * + * Generates a NOT LIKE portion of the query. Separates + * multiple calls with AND + * + * @param mixed + * @param mixed + * @return object + */ + public function not_like($field, $match = '', $side = 'both') + { + return $this->_like($field, $match, 'AND ', $side, 'NOT'); + } + + // -------------------------------------------------------------------- + + /** + * OR Like + * + * Generates a %LIKE% portion of the query. Separates + * multiple calls with OR + * + * @param mixed + * @param mixed + * @return object + */ + public function or_like($field, $match = '', $side = 'both') + { + return $this->_like($field, $match, 'OR ', $side); + } + + // -------------------------------------------------------------------- + + /** + * OR Not Like + * + * Generates a NOT LIKE portion of the query. Separates + * multiple calls with OR + * + * @param mixed + * @param mixed + * @return object + */ + public function or_not_like($field, $match = '', $side = 'both') + { + return $this->_like($field, $match, 'OR ', $side, 'NOT'); + } + + // -------------------------------------------------------------------- + + /** + * Like + * + * Called by like() or orlike() + * + * @param mixed + * @param mixed + * @param string + * @return object + */ + protected function _like($field, $match = '', $type = 'AND ', $side = 'both', $not = '') + { + if ( ! is_array($field)) + { + $field = array($field => $match); + } + + foreach ($field as $k => $v) + { + $k = $this->_protect_identifiers($k); + + $prefix = (count($this->ar_like) == 0) ? '' : $type; + + $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 ($this->_like_escape_str != '') + { + $like_statement = $like_statement.sprintf($this->_like_escape_str, $this->_like_escape_chr); + } + + $this->ar_like[] = $like_statement; + if ($this->ar_caching === TRUE) + { + $this->ar_cache_like[] = $like_statement; + $this->ar_cache_exists[] = 'like'; + } + + } + return $this; + } + + // -------------------------------------------------------------------- + + /** + * GROUP BY + * + * @param string + * @return object + */ + public function group_by($by) + { + if (is_string($by)) + { + $by = explode(',', $by); + } + + foreach ($by as $val) + { + $val = trim($val); + + if ($val != '') + { + $this->ar_groupby[] = $this->_protect_identifiers($val); + + if ($this->ar_caching === TRUE) + { + $this->ar_cache_groupby[] = $this->_protect_identifiers($val); + $this->ar_cache_exists[] = 'groupby'; + } + } + } + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Sets the HAVING value + * + * Separates multiple calls with AND + * + * @param string + * @param string + * @return object + */ + public function having($key, $value = '', $escape = TRUE) + { + return $this->_having($key, $value, 'AND ', $escape); + } + + // -------------------------------------------------------------------- + + /** + * Sets the OR HAVING value + * + * Separates multiple calls with OR + * + * @param string + * @param string + * @return object + */ + public function or_having($key, $value = '', $escape = TRUE) + { + return $this->_having($key, $value, 'OR ', $escape); + } + + // -------------------------------------------------------------------- + + /** + * Sets the HAVING values + * + * Called by having() or or_having() + * + * @param string + * @param string + * @return object + */ + protected function _having($key, $value = '', $type = 'AND ', $escape = TRUE) + { + if ( ! is_array($key)) + { + $key = array($key => $value); + } + + foreach ($key as $k => $v) + { + $prefix = (count($this->ar_having) == 0) ? '' : $type; + + if ($escape === TRUE) + { + $k = $this->_protect_identifiers($k); + } + + if ( ! $this->_has_operator($k)) + { + $k .= ' = '; + } + + if ($v != '') + { + $v = ' '.$this->escape($v); + } + + $this->ar_having[] = $prefix.$k.$v; + if ($this->ar_caching === TRUE) + { + $this->ar_cache_having[] = $prefix.$k.$v; + $this->ar_cache_exists[] = 'having'; + } + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Sets the ORDER BY value + * + * @param string + * @param string direction: asc or desc + * @return object + */ + public function order_by($orderby, $direction = '') + { + if (strtolower($direction) == 'random') + { + $orderby = ''; // Random results want or don't need a field name + $direction = $this->_random_keyword; + } + elseif (trim($direction) != '') + { + $direction = (in_array(strtoupper(trim($direction)), array('ASC', 'DESC'), TRUE)) ? ' '.$direction : ' ASC'; + } + + + if (strpos($orderby, ',') !== FALSE) + { + $temp = array(); + foreach (explode(',', $orderby) as $part) + { + $part = trim($part); + if ( ! in_array($part, $this->ar_aliased_tables)) + { + $part = $this->_protect_identifiers(trim($part)); + } + + $temp[] = $part; + } + + $orderby = implode(', ', $temp); + } + else if ($direction != $this->_random_keyword) + { + $orderby = $this->_protect_identifiers($orderby); + } + + $orderby_statement = $orderby.$direction; + + $this->ar_orderby[] = $orderby_statement; + if ($this->ar_caching === TRUE) + { + $this->ar_cache_orderby[] = $orderby_statement; + $this->ar_cache_exists[] = 'orderby'; + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Sets the LIMIT value + * + * @param integer the limit value + * @param integer the offset value + * @return object + */ + public function limit($value, $offset = '') + { + $this->ar_limit = (int) $value; + + if ($offset != '') + { + $this->ar_offset = (int) $offset; + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Sets the OFFSET value + * + * @param integer the offset value + * @return object + */ + public function offset($offset) + { + $this->ar_offset = $offset; + return $this; + } + + // -------------------------------------------------------------------- + + /** + * The "set" function. Allows key/value pairs to be set for inserting or updating + * + * @param mixed + * @param string + * @param boolean + * @return object + */ + public function set($key, $value = '', $escape = TRUE) + { + $key = $this->_object_to_array($key); + + if ( ! is_array($key)) + { + $key = array($key => $value); + } + + foreach ($key as $k => $v) + { + if ($escape === FALSE) + { + $this->ar_set[$this->_protect_identifiers($k)] = $v; + } + else + { + $this->ar_set[$this->_protect_identifiers($k, FALSE, TRUE)] = $this->escape($v); + } + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * 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 object + */ + public function get($table = '', $limit = null, $offset = null) + { + if ($table != '') + { + $this->_track_aliases($table); + $this->from($table); + } + + if ( ! is_null($limit)) + { + $this->limit($limit, $offset); + } + + $sql = $this->_compile_select(); + + $result = $this->query($sql); + $this->_reset_select(); + return $result; + } + + /** + * "Count All Results" query + * + * Generates a platform-specific query string that counts all records + * returned by an Active Record query. + * + * @param string + * @return string + */ + public function count_all_results($table = '') + { + if ($table != '') + { + $this->_track_aliases($table); + $this->from($table); + } + + $sql = $this->_compile_select($this->_count_string . $this->_protect_identifiers('numrows')); + + $query = $this->query($sql); + $this->_reset_select(); + + if ($query->num_rows() == 0) + { + return 0; + } + + $row = $query->row(); + return (int) $row->numrows; + } + + // -------------------------------------------------------------------- + + /** + * Get_Where + * + * Allows the where clause, limit and offset to be added directly + * + * @param string the where clause + * @param string the limit clause + * @param string the offset clause + * @return object + */ + public function get_where($table = '', $where = null, $limit = null, $offset = null) + { + if ($table != '') + { + $this->from($table); + } + + if ( ! is_null($where)) + { + $this->where($where); + } + + if ( ! is_null($limit)) + { + $this->limit($limit, $offset); + } + + $sql = $this->_compile_select(); + + $result = $this->query($sql); + $this->_reset_select(); + return $result; + } + + // -------------------------------------------------------------------- + + /** + * Insert_Batch + * + * Compiles batch insert strings and runs the queries + * + * @param string the table to retrieve the results from + * @param array an associative array of insert values + * @return object + */ + public function insert_batch($table = '', $set = NULL) + { + if ( ! is_null($set)) + { + $this->set_insert_batch($set); + } + + if (count($this->ar_set) == 0) + { + if ($this->db_debug) + { + //No valid data array. Folds in cases where keys and values did not match up + return $this->display_error('db_must_use_set'); + } + return FALSE; + } + + if ($table == '') + { + if ( ! isset($this->ar_from[0])) + { + if ($this->db_debug) + { + return $this->display_error('db_must_set_table'); + } + return FALSE; + } + + $table = $this->ar_from[0]; + } + + // Batch this baby + for ($i = 0, $total = count($this->ar_set); $i < $total; $i = $i + 100) + { + + $sql = $this->_insert_batch($this->_protect_identifiers($table, TRUE, NULL, FALSE), $this->ar_keys, array_slice($this->ar_set, $i, 100)); + + //echo $sql; + + $this->query($sql); + } + + $this->_reset_write(); + + + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * The "set_insert_batch" function. Allows key/value pairs to be set for batch inserts + * + * @param mixed + * @param string + * @param boolean + * @return object + */ + public function set_insert_batch($key, $value = '', $escape = TRUE) + { + $key = $this->_object_to_array_batch($key); + + if ( ! is_array($key)) + { + $key = array($key => $value); + } + + $keys = array_keys(current($key)); + sort($keys); + + foreach ($key as $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->ar_set[] = array(); + return; + } + + ksort($row); // puts $row in the same order as our keys + + if ($escape === FALSE) + { + $this->ar_set[] = '('.implode(',', $row).')'; + } + else + { + $clean = array(); + + foreach ($row as $value) + { + $clean[] = $this->escape($value); + } + + $this->ar_set[] = '('.implode(',', $clean).')'; + } + } + + foreach ($keys as $k) + { + $this->ar_keys[] = $this->_protect_identifiers($k); + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * 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 + * @return object + */ + function insert($table = '', $set = NULL) + { + if ( ! is_null($set)) + { + $this->set($set); + } + + if (count($this->ar_set) == 0) + { + if ($this->db_debug) + { + return $this->display_error('db_must_use_set'); + } + return FALSE; + } + + if ($table == '') + { + if ( ! isset($this->ar_from[0])) + { + if ($this->db_debug) + { + return $this->display_error('db_must_set_table'); + } + return FALSE; + } + + $table = $this->ar_from[0]; + } + + $sql = $this->_insert($this->_protect_identifiers($table, TRUE, NULL, FALSE), array_keys($this->ar_set), array_values($this->ar_set)); + + $this->_reset_write(); + return $this->query($sql); + } + + // -------------------------------------------------------------------- + + /** + * 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 object + */ + public function replace($table = '', $set = NULL) + { + if ( ! is_null($set)) + { + $this->set($set); + } + + if (count($this->ar_set) == 0) + { + if ($this->db_debug) + { + return $this->display_error('db_must_use_set'); + } + return FALSE; + } + + if ($table == '') + { + if ( ! isset($this->ar_from[0])) + { + if ($this->db_debug) + { + return $this->display_error('db_must_set_table'); + } + return FALSE; + } + + $table = $this->ar_from[0]; + } + + $sql = $this->_replace($this->_protect_identifiers($table, TRUE, NULL, FALSE), array_keys($this->ar_set), array_values($this->ar_set)); + + $this->_reset_write(); + return $this->query($sql); + } + + // -------------------------------------------------------------------- + + /** + * Update + * + * 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 mixed the where clause + * @return object + */ + public function update($table = '', $set = NULL, $where = NULL, $limit = NULL) + { + // Combine any cached components with the current statements + $this->_merge_cache(); + + if ( ! is_null($set)) + { + $this->set($set); + } + + if (count($this->ar_set) == 0) + { + if ($this->db_debug) + { + return $this->display_error('db_must_use_set'); + } + return FALSE; + } + + if ($table == '') + { + if ( ! isset($this->ar_from[0])) + { + if ($this->db_debug) + { + return $this->display_error('db_must_set_table'); + } + return FALSE; + } + + $table = $this->ar_from[0]; + } + + if ($where != NULL) + { + $this->where($where); + } + + if ($limit != NULL) + { + $this->limit($limit); + } + + $sql = $this->_update($this->_protect_identifiers($table, TRUE, NULL, FALSE), $this->ar_set, $this->ar_where, $this->ar_orderby, $this->ar_limit); + + $this->_reset_write(); + return $this->query($sql); + } + + + // -------------------------------------------------------------------- + + /** + * 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 object + */ + public function update_batch($table = '', $set = NULL, $index = NULL) + { + // Combine any cached components with the current statements + $this->_merge_cache(); + + if (is_null($index)) + { + if ($this->db_debug) + { + return $this->display_error('db_must_use_index'); + } + + return FALSE; + } + + if ( ! is_null($set)) + { + $this->set_update_batch($set, $index); + } + + if (count($this->ar_set) == 0) + { + if ($this->db_debug) + { + return $this->display_error('db_must_use_set'); + } + + return FALSE; + } + + if ($table == '') + { + if ( ! isset($this->ar_from[0])) + { + if ($this->db_debug) + { + return $this->display_error('db_must_set_table'); + } + return FALSE; + } + + $table = $this->ar_from[0]; + } + + // Batch this baby + for ($i = 0, $total = count($this->ar_set); $i < $total; $i = $i + 100) + { + $sql = $this->_update_batch($this->_protect_identifiers($table, TRUE, NULL, FALSE), array_slice($this->ar_set, $i, 100), $this->_protect_identifiers($index), $this->ar_where); + + $this->query($sql); + } + + $this->_reset_write(); + } + + // -------------------------------------------------------------------- + + /** + * The "set_update_batch" function. Allows key/value pairs to be set for batch updating + * + * @param array + * @param string + * @param boolean + * @return object + */ + public function set_update_batch($key, $index = '', $escape = TRUE) + { + $key = $this->_object_to_array_batch($key); + + if ( ! is_array($key)) + { + // @todo error + } + + foreach ($key as $k => $v) + { + $index_set = FALSE; + $clean = array(); + + foreach ($v as $k2 => $v2) + { + if ($k2 == $index) + { + $index_set = TRUE; + } + else + { + $not[] = $k.'-'.$v; + } + + if ($escape === FALSE) + { + $clean[$this->_protect_identifiers($k2)] = $v2; + } + else + { + $clean[$this->_protect_identifiers($k2)] = $this->escape($v2); + } + } + + if ($index_set == FALSE) + { + return $this->display_error('db_batch_missing_index'); + } + + $this->ar_set[] = $clean; + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Empty Table + * + * Compiles a delete string and runs "DELETE FROM table" + * + * @param string the table to empty + * @return object + */ + public function empty_table($table = '') + { + if ($table == '') + { + if ( ! isset($this->ar_from[0])) + { + if ($this->db_debug) + { + return $this->display_error('db_must_set_table'); + } + return FALSE; + } + + $table = $this->ar_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 object + */ + public function truncate($table = '') + { + if ($table == '') + { + if ( ! isset($this->ar_from[0])) + { + if ($this->db_debug) + { + return $this->display_error('db_must_set_table'); + } + return FALSE; + } + + $table = $this->ar_from[0]; + } + else + { + $table = $this->_protect_identifiers($table, TRUE, NULL, FALSE); + } + + $sql = $this->_truncate($table); + + $this->_reset_write(); + + return $this->query($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 boolean + * @return object + */ + 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->ar_from[0])) + { + if ($this->db_debug) + { + return $this->display_error('db_must_set_table'); + } + return FALSE; + } + + $table = $this->ar_from[0]; + } + elseif (is_array($table)) + { + foreach ($table as $single_table) + { + $this->delete($single_table, $where, $limit, FALSE); + } + + $this->_reset_write(); + return; + } + else + { + $table = $this->_protect_identifiers($table, TRUE, NULL, FALSE); + } + + if ($where != '') + { + $this->where($where); + } + + if ($limit != NULL) + { + $this->limit($limit); + } + + if (count($this->ar_where) == 0 && count($this->ar_wherein) == 0 && count($this->ar_like) == 0) + { + if ($this->db_debug) + { + return $this->display_error('db_del_must_use_where'); + } + + return FALSE; + } + + $sql = $this->_delete($table, $this->ar_where, $this->ar_like, $this->ar_limit); + + if ($reset_data) + { + $this->_reset_write(); + } + + return $this->query($sql); + } + + // -------------------------------------------------------------------- + + /** + * 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->ar_aliased_tables)) + { + $this->ar_aliased_tables[] = $table; + } + } + } + + // -------------------------------------------------------------------- + + /** + * Compile the SELECT statement + * + * Generates a query string based on which functions were used. + * Should not be called directly. The get() function calls it. + * + * @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->ar_distinct) ? 'SELECT ' : 'SELECT DISTINCT '; + + if (count($this->ar_select) == 0) + { + $sql .= '*'; + } + else + { + // Cycle through the "select" portion of the query and prep each column name. + // The reason we protect identifiers here rather then in the select() function + // is because until the user calls the from() function we don't know if there are aliases + foreach ($this->ar_select as $key => $val) + { + $no_escape = isset($this->ar_no_escape[$key]) ? $this->ar_no_escape[$key] : NULL; + $this->ar_select[$key] = $this->_protect_identifiers($val, FALSE, $no_escape); + } + + $sql .= implode(', ', $this->ar_select); + } + } + + // ---------------------------------------------------------------- + + // Write the "FROM" portion of the query + + if (count($this->ar_from) > 0) + { + $sql .= "\nFROM "; + + $sql .= $this->_from_tables($this->ar_from); + } + + // ---------------------------------------------------------------- + + // Write the "JOIN" portion of the query + + if (count($this->ar_join) > 0) + { + $sql .= "\n"; + + $sql .= implode("\n", $this->ar_join); + } + + // ---------------------------------------------------------------- + + // Write the "WHERE" portion of the query + + if (count($this->ar_where) > 0 OR count($this->ar_like) > 0) + { + $sql .= "\nWHERE "; + } + + $sql .= implode("\n", $this->ar_where); + + // ---------------------------------------------------------------- + + // Write the "LIKE" portion of the query + + if (count($this->ar_like) > 0) + { + if (count($this->ar_where) > 0) + { + $sql .= "\nAND "; + } + + $sql .= implode("\n", $this->ar_like); + } + + // ---------------------------------------------------------------- + + // Write the "GROUP BY" portion of the query + + if (count($this->ar_groupby) > 0) + { + $sql .= "\nGROUP BY "; + + $sql .= implode(', ', $this->ar_groupby); + } + + // ---------------------------------------------------------------- + + // Write the "HAVING" portion of the query + + if (count($this->ar_having) > 0) + { + $sql .= "\nHAVING "; + $sql .= implode("\n", $this->ar_having); + } + + // ---------------------------------------------------------------- + + // Write the "ORDER BY" portion of the query + + if (count($this->ar_orderby) > 0) + { + $sql .= "\nORDER BY "; + $sql .= implode(', ', $this->ar_orderby); + + if ($this->ar_order !== FALSE) + { + $sql .= ($this->ar_order == 'desc') ? ' DESC' : ' ASC'; + } + } + + // ---------------------------------------------------------------- + + // Write the "LIMIT" portion of the query + + if (is_numeric($this->ar_limit)) + { + $sql .= "\n"; + $sql = $this->_limit($sql, $this->ar_limit, $this->ar_offset); + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Object to Array + * + * Takes an object as input and converts the class variables to array key/vals + * + * @param object + * @return array + */ + public 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 + */ + public 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; + $i++; + } + } + } + + return $array; + } + + // -------------------------------------------------------------------- + + /** + * Start Cache + * + * Starts AR caching + * + * @return void + */ + public function start_cache() + { + $this->ar_caching = TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Stop Cache + * + * Stops AR caching + * + * @return void + */ + public function stop_cache() + { + $this->ar_caching = FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Flush Cache + * + * Empties the AR cache + * + * @access public + * @return void + */ + public function flush_cache() + { + $this->_reset_run(array( + 'ar_cache_select' => array(), + 'ar_cache_from' => array(), + 'ar_cache_join' => array(), + 'ar_cache_where' => array(), + 'ar_cache_like' => array(), + 'ar_cache_groupby' => array(), + 'ar_cache_having' => array(), + 'ar_cache_orderby' => array(), + 'ar_cache_set' => array(), + 'ar_cache_exists' => array(), + 'ar_cache_no_escape' => array() + )); + } + + // -------------------------------------------------------------------- + + /** + * Merge Cache + * + * When called, this function merges any cached AR arrays with + * locally called ones. + * + * @return void + */ + protected function _merge_cache() + { + if (count($this->ar_cache_exists) == 0) + { + return; + } + + foreach ($this->ar_cache_exists as $val) + { + $ar_variable = 'ar_'.$val; + $ar_cache_var = 'ar_cache_'.$val; + + if (count($this->$ar_cache_var) == 0) + { + continue; + } + + $this->$ar_variable = array_unique(array_merge($this->$ar_cache_var, $this->$ar_variable)); + } + + // If we are "protecting identifiers" we need to examine the "from" + // portion of the query to determine if there are any aliases + if ($this->_protect_identifiers === TRUE AND count($this->ar_cache_from) > 0) + { + $this->_track_aliases($this->ar_from); + } + + $this->ar_no_escape = $this->ar_cache_no_escape; + } + + // -------------------------------------------------------------------- + + /** + * Resets the active record values. Called by the get() function + * + * @param array An array of fields to reset + * @return void + */ + protected function _reset_run($ar_reset_items) + { + foreach ($ar_reset_items as $item => $default_value) + { + if ( ! in_array($item, $this->ar_store_array)) + { + $this->$item = $default_value; + } + } + } + + // -------------------------------------------------------------------- + + /** + * Resets the active record values. Called by the get() function + * + * @return void + */ + protected function _reset_select() + { + $ar_reset_items = array( + 'ar_select' => array(), + 'ar_from' => array(), + 'ar_join' => array(), + 'ar_where' => array(), + 'ar_like' => array(), + 'ar_groupby' => array(), + 'ar_having' => array(), + 'ar_orderby' => array(), + 'ar_wherein' => array(), + 'ar_aliased_tables' => array(), + 'ar_no_escape' => array(), + 'ar_distinct' => FALSE, + 'ar_limit' => FALSE, + 'ar_offset' => FALSE, + 'ar_order' => FALSE, + ); + + $this->_reset_run($ar_reset_items); + } + + // -------------------------------------------------------------------- + + /** + * Resets the active record "write" values. + * + * Called by the insert() update() insert_batch() update_batch() and delete() functions + * + * @return void + */ + protected function _reset_write() + { + $ar_reset_items = array( + 'ar_set' => array(), + 'ar_from' => array(), + 'ar_where' => array(), + 'ar_like' => array(), + 'ar_orderby' => array(), + 'ar_keys' => array(), + 'ar_limit' => FALSE, + 'ar_order' => FALSE + ); + + $this->_reset_run($ar_reset_items); + } +} + +/* End of file DB_active_rec.php */ +/* Location: ./system/database/DB_active_rec.php */ \ No newline at end of file diff --git a/system/database/DB_cache.php b/system/database/DB_cache.php new file mode 100644 index 0000000..ad1c28d --- /dev/null +++ b/system/database/DB_cache.php @@ -0,0 +1,195 @@ +CI + // and load the file helper since we use it a lot + $this->CI =& get_instance(); + $this->db =& $db; + $this->CI->load->helper('file'); + } + + // -------------------------------------------------------------------- + + /** + * Set Cache Directory Path + * + * @access public + * @param string the path to the cache directory + * @return bool + */ + 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 = preg_replace("/(.+?)\/*$/", "\\1/", $path); + + if ( ! is_dir($path) OR ! is_really_writable($path)) + { + // If the path is wrong 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 + * + * @access public + * @return string + */ + function read($sql) + { + if ( ! $this->check_path()) + { + return $this->db->cache_off(); + } + + $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 (FALSE === ($cachedata = read_file($filepath))) + { + return FALSE; + } + + return unserialize($cachedata); + } + + // -------------------------------------------------------------------- + + /** + * Write a query to a cache file + * + * @access public + * @return bool + */ + function write($sql, $object) + { + if ( ! $this->check_path()) + { + return $this->db->cache_off(); + } + + $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)) + { + if ( ! @mkdir($dir_path, DIR_WRITE_MODE)) + { + return FALSE; + } + + @chmod($dir_path, DIR_WRITE_MODE); + } + + if (write_file($dir_path.$filename, serialize($object)) === FALSE) + { + return FALSE; + } + + @chmod($dir_path.$filename, FILE_WRITE_MODE); + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Delete cache files within a particular directory + * + * @access public + * @return bool + */ + 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 + * + * @access public + * @return bool + */ + function delete_all() + { + delete_files($this->db->cachedir, TRUE); + } + +} + + +/* End of file DB_cache.php */ +/* Location: ./system/database/DB_cache.php */ \ No newline at end of file diff --git a/system/database/DB_driver.php b/system/database/DB_driver.php new file mode 100644 index 0000000..775fd33 --- /dev/null +++ b/system/database/DB_driver.php @@ -0,0 +1,1410 @@ + $val) + { + $this->$key = $val; + } + } + + log_message('debug', 'Database Driver Class Initialized'); + } + + // -------------------------------------------------------------------- + + /** + * Initialize Database Settings + * + * @access private Called by the constructor + * @param mixed + * @return void + */ + function initialize() + { + // If an existing connection resource is available + // there is no need to connect and select the database + if (is_resource($this->conn_id) OR is_object($this->conn_id)) + { + return TRUE; + } + + // ---------------------------------------------------------------- + + // Connect to the database and set the connection ID + $this->conn_id = ($this->pconnect == FALSE) ? $this->db_connect() : $this->db_pconnect(); + + // No connection resource? Throw an error + 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; + } + + // ---------------------------------------------------------------- + + // Select the DB... assuming a database name is specified in the config file + if ($this->database != '') + { + if ( ! $this->db_select()) + { + log_message('error', 'Unable to select database: '.$this->database); + + if ($this->db_debug) + { + $this->display_error('db_unable_to_select', $this->database); + } + return FALSE; + } + else + { + // We've selected the DB. Now we set the character set + if ( ! $this->db_set_charset($this->char_set, $this->dbcollat)) + { + return FALSE; + } + + return TRUE; + } + } + + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Set client character set + * + * @access public + * @param string + * @param string + * @return resource + */ + function db_set_charset($charset, $collation) + { + if ( ! $this->_db_set_charset($this->char_set, $this->dbcollat)) + { + log_message('error', 'Unable to set database connection charset: '.$this->char_set); + + if ($this->db_debug) + { + $this->display_error('db_unable_to_set_charset', $this->char_set); + } + + return FALSE; + } + + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * The name of the platform in use (mysql, mssql, etc...) + * + * @access public + * @return string + */ + function platform() + { + return $this->dbdriver; + } + + // -------------------------------------------------------------------- + + /** + * Database Version Number. Returns a string containing the + * version of the database being used + * + * @access public + * @return string + */ + function version() + { + if (FALSE === ($sql = $this->_version())) + { + if ($this->db_debug) + { + return $this->display_error('db_unsupported_function'); + } + return FALSE; + } + + // Some DBs have functions that return the version, and don't run special + // SQL queries per se. In these instances, just return the result. + $driver_version_exceptions = array('oci8', 'sqlite', 'cubrid'); + + if (in_array($this->dbdriver, $driver_version_exceptions)) + { + return $sql; + } + else + { + $query = $this->query($sql); + return $query->row('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. + * + * @access public + * @param string An SQL query string + * @param array An array of binding data + * @return mixed + */ + function query($sql, $binds = FALSE, $return_object = TRUE) + { + if ($sql == '') + { + if ($this->db_debug) + { + log_message('error', 'Invalid query: '.$sql); + return $this->display_error('db_invalid_query'); + } + return FALSE; + } + + // Verify table prefix and replace if necessary + if ( ($this->dbprefix != '' AND $this->swap_pre != '') AND ($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 AND stristr($sql, 'SELECT')) + { + if ($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 = list($sm, $ss) = explode(' ', microtime()); + + // 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 + $this->_trans_status = FALSE; + + if ($this->db_debug) + { + // grab the error number and message now, as we might run some + // additional queries before displaying the error + $error_no = $this->_error_number(); + $error_msg = $this->_error_message(); + + // 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. + $this->trans_complete(); + + // Log and display errors + log_message('error', 'Query error: '.$error_msg); + return $this->display_error( + array( + 'Error Number: '.$error_no, + $error_msg, + $sql + ) + ); + } + + return FALSE; + } + + // Stop and aggregate the query time results + $time_end = list($em, $es) = explode(' ', microtime()); + $this->benchmark += ($em + $es) - ($sm + $ss); + + if ($this->save_queries == TRUE) + { + $this->query_times[] = ($em + $es) - ($sm + $ss); + } + + // Increment the query counter + $this->query_count++; + + // Was the query a "write" type? + // If so we'll simply return true + if ($this->is_write_type($sql) === TRUE) + { + // If caching is enabled we'll auto-cleanup any + // existing files related to this particular URI + if ($this->cache_on == TRUE AND $this->cache_autodel == TRUE AND $this->_cache_init()) + { + $this->CACHE->delete(); + } + + return TRUE; + } + + // Return TRUE if we don't need to create a result object + // Currently only the Oracle driver uses this when stored + // procedures are used + if ($return_object !== TRUE) + { + return TRUE; + } + + // Load and instantiate the result driver + + $driver = $this->load_rdriver(); + $RES = new $driver(); + $RES->conn_id = $this->conn_id; + $RES->result_id = $this->result_id; + + if ($this->dbdriver == 'oci8') + { + $RES->stmt_id = $this->stmt_id; + $RES->curs_id = NULL; + $RES->limit_used = $this->limit_used; + $this->stmt_id = FALSE; + } + + // oci8 vars must be set before calling this + $RES->num_rows = $RES->num_rows(); + + // Is query caching enabled? If so, we'll serialize the + // result object and save it to a cache file. + if ($this->cache_on == TRUE AND $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(); + $CR->num_rows = $RES->num_rows(); + $CR->result_object = $RES->result_object(); + $CR->result_array = $RES->result_array(); + + // 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 + * + * @access public + * @return string the name of the result class + */ + function load_rdriver() + { + $driver = 'CI_DB_'.$this->dbdriver.'_result'; + + if ( ! class_exists($driver)) + { + include_once(BASEPATH.'database/DB_result.php'); + include_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. + * + * @access public + * @param string the sql query + * @return mixed + */ + function simple_query($sql) + { + if ( ! $this->conn_id) + { + $this->initialize(); + } + + return $this->_execute($sql); + } + + // -------------------------------------------------------------------- + + /** + * Disable Transactions + * This permits transactions to be disabled at run-time. + * + * @access public + * @return void + */ + 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 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 + * + * @access public + * @return void + */ + function trans_strict($mode = TRUE) + { + $this->trans_strict = is_bool($mode) ? $mode : TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Start Transaction + * + * @access public + * @return void + */ + function trans_start($test_mode = FALSE) + { + if ( ! $this->trans_enabled) + { + return FALSE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + $this->_trans_depth += 1; + return; + } + + $this->trans_begin($test_mode); + } + + // -------------------------------------------------------------------- + + /** + * Complete Transaction + * + * @access public + * @return bool + */ + function trans_complete() + { + if ( ! $this->trans_enabled) + { + return FALSE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 1) + { + $this->_trans_depth -= 1; + return TRUE; + } + + // The query() function will set this flag to FALSE in the event that a query failed + if ($this->_trans_status === FALSE) + { + $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; + } + + $this->trans_commit(); + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Lets you retrieve the transaction flag to determine if it has failed + * + * @access public + * @return bool + */ + function trans_status() + { + return $this->_trans_status; + } + + // -------------------------------------------------------------------- + + /** + * Compile Bindings + * + * @access public + * @param string the sql statement + * @param array an array of bind data + * @return string + */ + function compile_binds($sql, $binds) + { + if (strpos($sql, $this->bind_marker) === FALSE) + { + return $sql; + } + + if ( ! is_array($binds)) + { + $binds = array($binds); + } + + // Get the sql segments around the bind markers + $segments = explode($this->bind_marker, $sql); + + // The count of bind should be 1 less then the count of segments + // If there are more bind arguments trim it down + if (count($binds) >= count($segments)) { + $binds = array_slice($binds, 0, count($segments)-1); + } + + // Construct the binded query + $result = $segments[0]; + $i = 0; + foreach ($binds as $bind) + { + $result .= $this->escape($bind); + $result .= $segments[++$i]; + } + + return $result; + } + + // -------------------------------------------------------------------- + + /** + * Determines if a query is a "write" type. + * + * @access public + * @param string An SQL query string + * @return boolean + */ + function is_write_type($sql) + { + if ( ! preg_match('/^\s*"?(SET|INSERT|UPDATE|DELETE|REPLACE|CREATE|DROP|TRUNCATE|LOAD DATA|COPY|ALTER|GRANT|REVOKE|LOCK|UNLOCK)\s+/i', $sql)) + { + return FALSE; + } + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Calculate the aggregate query elapsed time + * + * @access public + * @param integer The number of decimal places + * @return integer + */ + function elapsed_time($decimals = 6) + { + return number_format($this->benchmark, $decimals); + } + + // -------------------------------------------------------------------- + + /** + * Returns the total number of queries + * + * @access public + * @return integer + */ + function total_queries() + { + return $this->query_count; + } + + // -------------------------------------------------------------------- + + /** + * Returns the last query that was executed + * + * @access public + * @return void + */ + function last_query() + { + return end($this->queries); + } + + // -------------------------------------------------------------------- + + /** + * "Smart" Escape String + * + * Escapes data based on type + * Sets boolean and null types + * + * @access public + * @param string + * @return mixed + */ + function escape($str) + { + if (is_string($str)) + { + $str = "'".$this->escape_str($str)."'"; + } + elseif (is_bool($str)) + { + $str = ($str === FALSE) ? 0 : 1; + } + elseif (is_null($str)) + { + $str = 'NULL'; + } + + return $str; + } + + // -------------------------------------------------------------------- + + /** + * Escape LIKE String + * + * Calls the individual driver for platform + * specific escaping for LIKE conditions + * + * @access public + * @param string + * @return mixed + */ + function escape_like_str($str) + { + return $this->escape_str($str, TRUE); + } + + // -------------------------------------------------------------------- + + /** + * Primary + * + * Retrieves the primary key. It assumes that the row in the first + * position is the primary key + * + * @access public + * @param string the table name + * @return string + */ + function primary($table = '') + { + $fields = $this->list_fields($table); + + if ( ! is_array($fields)) + { + return FALSE; + } + + return current($fields); + } + + // -------------------------------------------------------------------- + + /** + * Returns an array of table names + * + * @access public + * @return array + */ + 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))) + { + if ($this->db_debug) + { + return $this->display_error('db_unsupported_function'); + } + return FALSE; + } + + $retval = array(); + $query = $this->query($sql); + + if ($query->num_rows() > 0) + { + foreach ($query->result_array() as $row) + { + if (isset($row['TABLE_NAME'])) + { + $retval[] = $row['TABLE_NAME']; + } + else + { + $retval[] = array_shift($row); + } + } + } + + $this->data_cache['table_names'] = $retval; + return $this->data_cache['table_names']; + } + + // -------------------------------------------------------------------- + + /** + * Determine if a particular table exists + * @access public + * @return boolean + */ + function table_exists($table_name) + { + return ( ! in_array($this->_protect_identifiers($table_name, TRUE, FALSE, FALSE), $this->list_tables())) ? FALSE : TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Fetch MySQL Field Names + * + * @access public + * @param string the table name + * @return array + */ + 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 ($table == '') + { + if ($this->db_debug) + { + return $this->display_error('db_field_param_missing'); + } + return FALSE; + } + + if (FALSE === ($sql = $this->_list_columns($table))) + { + if ($this->db_debug) + { + return $this->display_error('db_unsupported_function'); + } + return FALSE; + } + + $query = $this->query($sql); + + $retval = array(); + foreach ($query->result_array() as $row) + { + if (isset($row['COLUMN_NAME'])) + { + $retval[] = $row['COLUMN_NAME']; + } + else + { + $retval[] = current($row); + } + } + + $this->data_cache['field_names'][$table] = $retval; + return $this->data_cache['field_names'][$table]; + } + + // -------------------------------------------------------------------- + + /** + * Determine if a particular field exists + * @access public + * @param string + * @param string + * @return boolean + */ + function field_exists($field_name, $table_name) + { + return ( ! in_array($field_name, $this->list_fields($table_name))) ? FALSE : TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Returns an object with field data + * + * @access public + * @param string the table name + * @return object + */ + function field_data($table = '') + { + if ($table == '') + { + if ($this->db_debug) + { + return $this->display_error('db_field_param_missing'); + } + return FALSE; + } + + $query = $this->query($this->_field_data($this->_protect_identifiers($table, TRUE, NULL, FALSE))); + + return $query->field_data(); + } + + // -------------------------------------------------------------------- + + /** + * Generate an insert string + * + * @access public + * @param string the table upon which the query will be performed + * @param array an associative array data of key/values + * @return string + */ + function insert_string($table, $data) + { + $fields = array(); + $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); + } + + // -------------------------------------------------------------------- + + /** + * Generate an update string + * + * @access public + * @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 + */ + function update_string($table, $data, $where) + { + if ($where == '') + { + return false; + } + + $fields = array(); + foreach ($data as $key => $val) + { + $fields[$this->_protect_identifiers($key)] = $this->escape($val); + } + + if ( ! is_array($where)) + { + $dest = array($where); + } + else + { + $dest = array(); + foreach ($where as $key => $val) + { + $prefix = (count($dest) == 0) ? '' : ' AND '; + + if ($val !== '') + { + if ( ! $this->_has_operator($key)) + { + $key .= ' ='; + } + + $val = ' '.$this->escape($val); + } + + $dest[] = $prefix.$key.$val; + } + } + + return $this->_update($this->_protect_identifiers($table, TRUE, NULL, FALSE), $fields, $dest); + } + + // -------------------------------------------------------------------- + + /** + * Tests whether the string has an SQL operator + * + * @access private + * @param string + * @return bool + */ + function _has_operator($str) + { + $str = trim($str); + if ( ! preg_match("/(\s|<|>|!|=|is null|is not null)/i", $str)) + { + return FALSE; + } + + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Enables a native PHP function to be run, using a platform agnostic wrapper. + * + * @access public + * @param string the function name + * @param mixed any parameters needed by the function + * @return mixed + */ + function call_function($function) + { + $driver = ($this->dbdriver == 'postgre') ? 'pg_' : $this->dbdriver.'_'; + + if (FALSE === strpos($driver, $function)) + { + $function = $driver.$function; + } + + if ( ! function_exists($function)) + { + if ($this->db_debug) + { + return $this->display_error('db_unsupported_function'); + } + return FALSE; + } + else + { + $args = (func_num_args() > 1) ? array_splice(func_get_args(), 1) : null; + if (is_null($args)) + { + return call_user_func($function); + } + else + { + return call_user_func_array($function, $args); + } + } + } + + // -------------------------------------------------------------------- + + /** + * Set Cache Directory Path + * + * @access public + * @param string the path to the cache directory + * @return void + */ + function cache_set_path($path = '') + { + $this->cachedir = $path; + } + + // -------------------------------------------------------------------- + + /** + * Enable Query Caching + * + * @access public + * @return void + */ + function cache_on() + { + $this->cache_on = TRUE; + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Disable Query Caching + * + * @access public + * @return void + */ + function cache_off() + { + $this->cache_on = FALSE; + return FALSE; + } + + + // -------------------------------------------------------------------- + + /** + * Delete the cache files associated with a particular URI + * + * @access public + * @return void + */ + function cache_delete($segment_one = '', $segment_two = '') + { + if ( ! $this->_cache_init()) + { + return FALSE; + } + return $this->CACHE->delete($segment_one, $segment_two); + } + + // -------------------------------------------------------------------- + + /** + * Delete All cache files + * + * @access public + * @return void + */ + function cache_delete_all() + { + if ( ! $this->_cache_init()) + { + return FALSE; + } + + return $this->CACHE->delete_all(); + } + + // -------------------------------------------------------------------- + + /** + * Initialize the Cache Class + * + * @access private + * @return void + */ + function _cache_init() + { + if (is_object($this->CACHE) AND class_exists('CI_DB_Cache')) + { + return TRUE; + } + + if ( ! class_exists('CI_DB_Cache')) + { + if ( ! @include(BASEPATH.'database/DB_cache.php')) + { + return $this->cache_off(); + } + } + + $this->CACHE = new CI_DB_Cache($this); // pass db object to support multiple db connections and returned db objects + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Close DB Connection + * + * @access public + * @return void + */ + function close() + { + if (is_resource($this->conn_id) OR is_object($this->conn_id)) + { + $this->_close($this->conn_id); + } + $this->conn_id = FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Display an error message + * + * @access public + * @param string the error message + * @param string any "swap" values + * @param boolean whether to localize the message + * @return string sends the application/error_db.php template + */ + 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 = $error; + } + else + { + $message = ( ! is_array($error)) ? array(str_replace('%s', $swap, $LANG->line($error))) : $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']) && strpos($call['file'], BASEPATH.'database') === FALSE) + { + // Found it - use a relative path for safety + $message[] = 'Filename: '.str_replace(array(BASEPATH, APPPATH), '', $call['file']); + $message[] = 'Line Number: '.$call['line']; + + break; + } + } + + $error =& load_class('Exceptions', 'core'); + echo $error->show_error($heading, $message, 'error_db'); + exit; + } + + // -------------------------------------------------------------------- + + /** + * Protect Identifiers + * + * This function adds backticks if appropriate based on db type + * + * @access private + * @param mixed the item to escape + * @return mixed the item with backticks + */ + function protect_identifiers($item, $prefix_single = FALSE) + { + return $this->_protect_identifiers($item, $prefix_single); + } + + // -------------------------------------------------------------------- + + /** + * Protect Identifiers + * + * This function is used extensively by the Active Record 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 * FROM 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. + * + * @access private + * @param string + * @param bool + * @param mixed + * @param bool + * @return string + */ + 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); + } + + return $escaped_array; + } + + // Convert tabs or multiple spaces into single spaces + $item = preg_replace('/[\t ]+/', ' ', $item); + + // If the item has an alias declaration we remove it and set it aside. + // Basically we remove everything to the right of the first space + if (strpos($item, ' ') !== FALSE) + { + $alias = strstr($item, ' '); + $item = substr($item, 0, - strlen($alias)); + } + else + { + $alias = ''; + } + + // 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 + if (strpos($item, '(') !== FALSE) + { + return $item.$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 + if (in_array($parts[0], $this->ar_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++; + } + + // Verify table prefix and replace if necessary + if ($this->swap_pre != '' && strncmp($parts[$i], $this->swap_pre, strlen($this->swap_pre)) === 0) + { + $parts[$i] = preg_replace("/^".$this->swap_pre."(\S+?)/", $this->dbprefix."\\1", $parts[$i]); + } + + // We only add the table prefix if it does not already exist + if (substr($parts[$i], 0, strlen($this->dbprefix)) != $this->dbprefix) + { + $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 != '' && strncmp($item, $this->swap_pre, strlen($this->swap_pre)) === 0) + { + $item = preg_replace("/^".$this->swap_pre."(\S+?)/", $this->dbprefix."\\1", $item); + } + + // Do we prefix an item with no segments? + if ($prefix_single == TRUE AND substr($item, 0, strlen($this->dbprefix)) != $this->dbprefix) + { + $item = $this->dbprefix.$item; + } + } + + if ($protect_identifiers === TRUE AND ! in_array($item, $this->_reserved_identifiers)) + { + $item = $this->_escape_identifiers($item); + } + + return $item.$alias; + } + + // -------------------------------------------------------------------- + + /** + * Dummy method that allows Active Record class to be disabled + * + * This function is used extensively by every db driver. + * + * @return void + */ + protected function _reset_select() + { + } + +} + +/* End of file DB_driver.php */ +/* Location: ./system/database/DB_driver.php */ \ No newline at end of file diff --git a/system/database/DB_forge.php b/system/database/DB_forge.php new file mode 100644 index 0000000..6bc4041 --- /dev/null +++ b/system/database/DB_forge.php @@ -0,0 +1,382 @@ +db + $CI =& get_instance(); + $this->db =& $CI->db; + log_message('debug', "Database Forge Class Initialized"); + } + + // -------------------------------------------------------------------- + + /** + * Create database + * + * @access public + * @param string the database name + * @return bool + */ + function create_database($db_name) + { + $sql = $this->_create_database($db_name); + + if (is_bool($sql)) + { + return $sql; + } + + return $this->db->query($sql); + } + + // -------------------------------------------------------------------- + + /** + * Drop database + * + * @access public + * @param string the database name + * @return bool + */ + function drop_database($db_name) + { + $sql = $this->_drop_database($db_name); + + if (is_bool($sql)) + { + return $sql; + } + + return $this->db->query($sql); + } + + // -------------------------------------------------------------------- + + /** + * Add Key + * + * @access public + * @param string key + * @param string type + * @return void + */ + function add_key($key = '', $primary = FALSE) + { + if (is_array($key)) + { + foreach ($key as $one) + { + $this->add_key($one, $primary); + } + + return; + } + + if ($key == '') + { + show_error('Key information is required for that operation.'); + } + + if ($primary === TRUE) + { + $this->primary_keys[] = $key; + } + else + { + $this->keys[] = $key; + } + } + + // -------------------------------------------------------------------- + + /** + * Add Field + * + * @access public + * @param string collation + * @return void + */ + function add_field($field = '') + { + if ($field == '') + { + show_error('Field information is required.'); + } + + 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); + } + + } + + // -------------------------------------------------------------------- + + /** + * Create Table + * + * @access public + * @param string the table name + * @return bool + */ + function create_table($table = '', $if_not_exists = FALSE) + { + if ($table == '') + { + show_error('A table name is required for that operation.'); + } + + if (count($this->fields) == 0) + { + show_error('Field information is required.'); + } + + $sql = $this->_create_table($this->db->dbprefix.$table, $this->fields, $this->primary_keys, $this->keys, $if_not_exists); + + $this->_reset(); + return $this->db->query($sql); + } + + // -------------------------------------------------------------------- + + /** + * Drop Table + * + * @access public + * @param string the table name + * @return bool + */ + function drop_table($table_name) + { + $sql = $this->_drop_table($this->db->dbprefix.$table_name); + + if (is_bool($sql)) + { + return $sql; + } + + return $this->db->query($sql); + } + + // -------------------------------------------------------------------- + + /** + * Rename Table + * + * @access public + * @param string the old table name + * @param string the new table name + * @return bool + */ + 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.'); + } + + $sql = $this->_rename_table($this->db->dbprefix.$table_name, $this->db->dbprefix.$new_table_name); + return $this->db->query($sql); + } + + // -------------------------------------------------------------------- + + /** + * Column Add + * + * @access public + * @param string the table name + * @param string the column name + * @param string the column definition + * @return bool + */ + function add_column($table = '', $field = array(), $after_field = '') + { + if ($table == '') + { + show_error('A table name is required for that operation.'); + } + + // add field info into field array, but we can only do one at a time + // so we cycle through + + foreach ($field as $k => $v) + { + $this->add_field(array($k => $field[$k])); + + if (count($this->fields) == 0) + { + show_error('Field information is required.'); + } + + $sql = $this->_alter_table('ADD', $this->db->dbprefix.$table, $this->fields, $after_field); + + $this->_reset(); + + if ($this->db->query($sql) === FALSE) + { + return FALSE; + } + } + + return TRUE; + + } + + // -------------------------------------------------------------------- + + /** + * Column Drop + * + * @access public + * @param string the table name + * @param string the column name + * @return bool + */ + function drop_column($table = '', $column_name = '') + { + + if ($table == '') + { + show_error('A table name is required for that operation.'); + } + + if ($column_name == '') + { + show_error('A column name is required for that operation.'); + } + + $sql = $this->_alter_table('DROP', $this->db->dbprefix.$table, $column_name); + + return $this->db->query($sql); + } + + // -------------------------------------------------------------------- + + /** + * Column Modify + * + * @access public + * @param string the table name + * @param string the column name + * @param string the column definition + * @return bool + */ + function modify_column($table = '', $field = array()) + { + if ($table == '') + { + show_error('A table name is required for that operation.'); + } + + // add field info into field array, but we can only do one at a time + // so we cycle through + + foreach ($field as $k => $v) + { + // If no name provided, use the current name + if ( ! isset($field[$k]['name'])) + { + $field[$k]['name'] = $k; + } + + $this->add_field(array($k => $field[$k])); + + if (count($this->fields) == 0) + { + show_error('Field information is required.'); + } + + $sql = $this->_alter_table('CHANGE', $this->db->dbprefix.$table, $this->fields); + + $this->_reset(); + + if ($this->db->query($sql) === FALSE) + { + return FALSE; + } + } + + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Reset + * + * Resets table creation vars + * + * @access private + * @return void + */ + function _reset() + { + $this->fields = array(); + $this->keys = array(); + $this->primary_keys = array(); + } + +} + +/* End of file DB_forge.php */ +/* Location: ./system/database/DB_forge.php */ \ No newline at end of file diff --git a/system/database/DB_result.php b/system/database/DB_result.php new file mode 100644 index 0000000..48d66c8 --- /dev/null +++ b/system/database/DB_result.php @@ -0,0 +1,410 @@ +result_array(); + else if ($type == 'object') return $this->result_object(); + else return $this->custom_result_object($type); + } + + // -------------------------------------------------------------------- + + /** + * Custom query result. + * + * @param class_name A string that represents the type of object you want back + * @return array of objects + */ + public function custom_result_object($class_name) + { + if (array_key_exists($class_name, $this->custom_result_object)) + { + return $this->custom_result_object[$class_name]; + } + + if ($this->result_id === FALSE OR $this->num_rows() == 0) + { + return array(); + } + + // add the data to the object + $this->_data_seek(0); + $result_object = array(); + + while ($row = $this->_fetch_object()) + { + $object = new $class_name(); + + foreach ($row as $key => $value) + { + $object->$key = $value; + } + + $result_object[] = $object; + } + + // return the array + return $this->custom_result_object[$class_name] = $result_object; + } + + // -------------------------------------------------------------------- + + /** + * Query result. "object" version. + * + * @access public + * @return object + */ + 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 return FALSE since there isn't a valid SQL resource so + // we'll simply return an empty array. + if ($this->result_id === FALSE OR $this->num_rows() == 0) + { + return array(); + } + + $this->_data_seek(0); + while ($row = $this->_fetch_object()) + { + $this->result_object[] = $row; + } + + return $this->result_object; + } + + // -------------------------------------------------------------------- + + /** + * Query result. "array" version. + * + * @access public + * @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 return FALSE since there isn't a valid SQL resource so + // we'll simply return an empty array. + if ($this->result_id === FALSE OR $this->num_rows() == 0) + { + return array(); + } + + $this->_data_seek(0); + while ($row = $this->_fetch_assoc()) + { + $this->result_array[] = $row; + } + + return $this->result_array; + } + + // -------------------------------------------------------------------- + + /** + * Query result. Acts as a wrapper function for the following functions. + * + * @access public + * @param string + * @param string can be "object" or "array" + * @return mixed either a result object or array + */ + public function row($n = 0, $type = 'object') + { + if ( ! is_numeric($n)) + { + // We cache the row data for subsequent uses + if ( ! is_array($this->row_data)) + { + $this->row_data = $this->row_array(0); + } + + // array_key_exists() instead of isset() to allow for MySQL NULL values + if (array_key_exists($n, $this->row_data)) + { + return $this->row_data[$n]; + } + // reset the $n variable if the result was not achieved + $n = 0; + } + + if ($type == 'object') return $this->row_object($n); + else if ($type == 'array') return $this->row_array($n); + else return $this->custom_row_object($n, $type); + } + + // -------------------------------------------------------------------- + + /** + * Assigns an item into a particular column slot + * + * @access public + * @return object + */ + 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 != '' AND ! is_null($value)) + { + $this->row_data[$key] = $value; + } + } + + // -------------------------------------------------------------------- + + /** + * Returns a single result row - custom object version + * + * @access public + * @return object + */ + public function custom_row_object($n, $type) + { + $result = $this->custom_result_object($type); + + if (count($result) == 0) + { + return $result; + } + + if ($n != $this->current_row AND isset($result[$n])) + { + $this->current_row = $n; + } + + return $result[$this->current_row]; + } + + /** + * Returns a single result row - object version + * + * @access public + * @return object + */ + public function row_object($n = 0) + { + $result = $this->result_object(); + + if (count($result) == 0) + { + return $result; + } + + if ($n != $this->current_row AND isset($result[$n])) + { + $this->current_row = $n; + } + + return $result[$this->current_row]; + } + + // -------------------------------------------------------------------- + + /** + * Returns a single result row - array version + * + * @access public + * @return array + */ + public function row_array($n = 0) + { + $result = $this->result_array(); + + if (count($result) == 0) + { + return $result; + } + + if ($n != $this->current_row AND isset($result[$n])) + { + $this->current_row = $n; + } + + return $result[$this->current_row]; + } + + + // -------------------------------------------------------------------- + + /** + * Returns the "first" row + * + * @access public + * @return object + */ + public function first_row($type = 'object') + { + $result = $this->result($type); + + if (count($result) == 0) + { + return $result; + } + return $result[0]; + } + + // -------------------------------------------------------------------- + + /** + * Returns the "last" row + * + * @access public + * @return object + */ + public function last_row($type = 'object') + { + $result = $this->result($type); + + if (count($result) == 0) + { + return $result; + } + return $result[count($result) -1]; + } + + // -------------------------------------------------------------------- + + /** + * Returns the "next" row + * + * @access public + * @return object + */ + public function next_row($type = 'object') + { + $result = $this->result($type); + + if (count($result) == 0) + { + return $result; + } + + if (isset($result[$this->current_row + 1])) + { + ++$this->current_row; + } + + return $result[$this->current_row]; + } + + // -------------------------------------------------------------------- + + /** + * Returns the "previous" row + * + * @access public + * @return object + */ + public function previous_row($type = 'object') + { + $result = $this->result($type); + + if (count($result) == 0) + { + return $result; + } + + if (isset($result[$this->current_row - 1])) + { + --$this->current_row; + } + return $result[$this->current_row]; + } + + // -------------------------------------------------------------------- + + /** + * The following functions 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. + */ + public function num_rows() { return $this->num_rows; } + public function num_fields() { return 0; } + public function list_fields() { return array(); } + public function field_data() { return array(); } + public function free_result() { return TRUE; } + protected function _data_seek() { return TRUE; } + protected function _fetch_assoc() { return array(); } + protected function _fetch_object() { return array(); } + +} +// END DB_result class + +/* End of file DB_result.php */ +/* Location: ./system/database/DB_result.php */ diff --git a/system/database/DB_utility.php b/system/database/DB_utility.php new file mode 100644 index 0000000..52196b7 --- /dev/null +++ b/system/database/DB_utility.php @@ -0,0 +1,414 @@ +db + $CI =& get_instance(); + $this->db =& $CI->db; + + log_message('debug', "Database Utility Class Initialized"); + } + + // -------------------------------------------------------------------- + + /** + * List databases + * + * @access public + * @return bool + */ + function list_databases() + { + // Is there a cached result? + if (isset($this->data_cache['db_names'])) + { + return $this->data_cache['db_names']; + } + + $query = $this->db->query($this->_list_databases()); + $dbs = array(); + if ($query->num_rows() > 0) + { + foreach ($query->result_array() as $row) + { + $dbs[] = current($row); + } + } + + $this->data_cache['db_names'] = $dbs; + return $this->data_cache['db_names']; + } + + // -------------------------------------------------------------------- + + /** + * Determine if a particular database exists + * + * @access public + * @param string + * @return boolean + */ + function database_exists($database_name) + { + // Some databases won't have access to the list_databases() function, so + // this is intended to allow them to override with their own functions as + // defined in $driver_utility.php + if (method_exists($this, '_database_exists')) + { + return $this->_database_exists($database_name); + } + else + { + return ( ! in_array($database_name, $this->list_databases())) ? FALSE : TRUE; + } + } + + + // -------------------------------------------------------------------- + + /** + * Optimize Table + * + * @access public + * @param string the table name + * @return bool + */ + function optimize_table($table_name) + { + $sql = $this->_optimize_table($table_name); + + if (is_bool($sql)) + { + show_error('db_must_use_set'); + } + + $query = $this->db->query($sql); + $res = $query->result_array(); + + // Note: Due to a bug in current() that affects some versions + // of PHP we can not pass function call directly into it + return current($res); + } + + // -------------------------------------------------------------------- + + /** + * Optimize Database + * + * @access public + * @return array + */ + function optimize_database() + { + $result = array(); + foreach ($this->db->list_tables() as $table_name) + { + $sql = $this->_optimize_table($table_name); + + if (is_bool($sql)) + { + return $sql; + } + + $query = $this->db->query($sql); + + // Build the result array... + // Note: Due to a bug in current() that affects some versions + // of PHP we can not pass function call directly into it + $res = $query->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 + * + * @access public + * @param string the table name + * @return bool + */ + function repair_table($table_name) + { + $sql = $this->_repair_table($table_name); + + if (is_bool($sql)) + { + return $sql; + } + + $query = $this->db->query($sql); + + // Note: Due to a bug in current() that affects some versions + // of PHP we can not pass function call directly into it + $res = $query->result_array(); + return current($res); + } + + // -------------------------------------------------------------------- + + /** + * Generate CSV from a query result object + * + * @access public + * @param object The query result object + * @param string The delimiter - comma by default + * @param string The newline character - \n by default + * @param string The enclosure - double quote by default + * @return string + */ + 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 = rtrim($out); + $out .= $newline; + + // Next blast through the result array and build out the rows + foreach ($query->result_array() as $row) + { + foreach ($row as $item) + { + $out .= $enclosure.str_replace($enclosure, $enclosure.$enclosure, $item).$enclosure.$delim; + } + $out = rtrim($out); + $out .= $newline; + } + + return $out; + } + + // -------------------------------------------------------------------- + + /** + * Generate XML data from a query result object + * + * @access public + * @param object The query result object + * @param array Any preferences + * @return string + */ + 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 + $CI =& get_instance(); + $CI->load->helper('xml'); + + // Generate the result + $xml = "<{$root}>".$newline; + foreach ($query->result_array() as $row) + { + $xml .= $tab."<{$element}>".$newline; + + foreach ($row as $key => $val) + { + $xml .= $tab.$tab."<{$key}>".xml_convert($val)."".$newline; + } + $xml .= $tab."".$newline; + } + $xml .= "".$newline; + + return $xml; + } + + // -------------------------------------------------------------------- + + /** + * Database Backup + * + * @access public + * @return void + */ + 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" + ); + + // 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' AND ! @function_exists('gzencode')) + OR ($prefs['format'] == 'zip' AND ! @function_exists('gzcompress'))) + { + if ($this->db->db_debug) + { + return $this->db->display_error('db_unsuported_compression'); + } + + $prefs['format'] = 'txt'; + } + + // ------------------------------------------------------ + + // Set the filename if not provided - Only needed with Zip files + if ($prefs['filename'] == '' AND $prefs['format'] == 'zip') + { + $prefs['filename'] = (count($prefs['tables']) == 1) ? $prefs['tables'] : $this->db->database; + $prefs['filename'] .= '_'.date('Y-m-d_H-i', time()); + } + + // ------------------------------------------------------ + + // Was a Gzip file requested? + if ($prefs['format'] == 'gzip') + { + return gzencode($this->_backup($prefs)); + } + + // ------------------------------------------------------ + + // Was a text file requested? + if ($prefs['format'] == 'txt') + { + return $this->_backup($prefs); + } + + // ------------------------------------------------------ + + // Was a Zip file requested? + if ($prefs['format'] == 'zip') + { + // 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(); + } + + } + +} + + +/* End of file DB_utility.php */ +/* Location: ./system/database/DB_utility.php */ \ No newline at end of file diff --git a/system/database/drivers/cubrid/cubrid_driver.php b/system/database/drivers/cubrid/cubrid_driver.php new file mode 100644 index 0000000..d011404 --- /dev/null +++ b/system/database/drivers/cubrid/cubrid_driver.php @@ -0,0 +1,792 @@ +port == '') + { + $this->port = self::DEFAULT_PORT; + } + + $conn = cubrid_connect($this->hostname, $this->port, $this->database, $this->username, $this->password); + + if ($conn) + { + // Check if a user wants to run queries in dry, i.e. run the + // queries but not commit them. + if (isset($this->auto_commit) && ! $this->auto_commit) + { + cubrid_set_autocommit($conn, CUBRID_AUTOCOMMIT_FALSE); + } + else + { + cubrid_set_autocommit($conn, CUBRID_AUTOCOMMIT_TRUE); + $this->auto_commit = TRUE; + } + } + + return $conn; + } + + // -------------------------------------------------------------------- + + /** + * Persistent database connection + * In CUBRID persistent DB connection is supported natively in CUBRID + * engine which can be configured in the CUBRID Broker configuration + * file by setting the CCI_PCONNECT parameter to ON. In that case, all + * connections established between the client application and the + * server will become persistent. This is calling the same + * @cubrid_connect function will establish persisten connection + * considering that the CCI_PCONNECT is ON. + * + * @access private called by the base class + * @return resource + */ + function db_pconnect() + { + return $this->db_connect(); + } + + // -------------------------------------------------------------------- + + /** + * Reconnect + * + * Keep / reestablish the db connection if no queries have been + * sent for a length of time exceeding the server's idle timeout + * + * @access public + * @return void + */ + function reconnect() + { + if (cubrid_ping($this->conn_id) === FALSE) + { + $this->conn_id = FALSE; + } + } + + // -------------------------------------------------------------------- + + /** + * Select the database + * + * @access private called by the base class + * @return resource + */ + function db_select() + { + // In CUBRID there is no need to select a database as the database + // is chosen at the connection time. + // So, to determine if the database is "selected", all we have to + // do is ping the server and return that value. + return cubrid_ping($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * Set client character set + * + * @access public + * @param string + * @param string + * @return resource + */ + function db_set_charset($charset, $collation) + { + // In CUBRID, there is no need to set charset or collation. + // This is why returning true will allow the application continue + // its normal process. + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Version number query string + * + * @access public + * @return string + */ + function _version() + { + // To obtain the CUBRID Server version, no need to run the SQL query. + // CUBRID PHP API provides a function to determin this value. + // This is why we also need to add 'cubrid' value to the list of + // $driver_version_exceptions array in DB_driver class in + // version() function. + return cubrid_get_server_info($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * Execute the query + * + * @access private called by the base class + * @param string an SQL query + * @return resource + */ + function _execute($sql) + { + $sql = $this->_prep_query($sql); + return @cubrid_query($sql, $this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * Prep the query + * + * If needed, each database adapter can prep the query string + * + * @access private called by execute() + * @param string an SQL query + * @return string + */ + function _prep_query($sql) + { + // No need to prepare + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Begin Transaction + * + * @access public + * @return bool + */ + function trans_begin($test_mode = FALSE) + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + 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) ? TRUE : FALSE; + + if (cubrid_get_autocommit($this->conn_id)) + { + cubrid_set_autocommit($this->conn_id, CUBRID_AUTOCOMMIT_FALSE); + } + + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Commit Transaction + * + * @access public + * @return bool + */ + function trans_commit() + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + return TRUE; + } + + cubrid_commit($this->conn_id); + + if ($this->auto_commit && ! cubrid_get_autocommit($this->conn_id)) + { + cubrid_set_autocommit($this->conn_id, CUBRID_AUTOCOMMIT_TRUE); + } + + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Rollback Transaction + * + * @access public + * @return bool + */ + function trans_rollback() + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + return TRUE; + } + + cubrid_rollback($this->conn_id); + + if ($this->auto_commit && ! cubrid_get_autocommit($this->conn_id)) + { + cubrid_set_autocommit($this->conn_id, CUBRID_AUTOCOMMIT_TRUE); + } + + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Escape String + * + * @access public + * @param string + * @param bool whether or not the string will be used in a LIKE condition + * @return string + */ + function escape_str($str, $like = FALSE) + { + if (is_array($str)) + { + foreach ($str as $key => $val) + { + $str[$key] = $this->escape_str($val, $like); + } + + return $str; + } + + if (function_exists('cubrid_real_escape_string') AND is_resource($this->conn_id)) + { + $str = cubrid_real_escape_string($str, $this->conn_id); + } + else + { + $str = addslashes($str); + } + + // escape LIKE condition wildcards + if ($like === TRUE) + { + $str = str_replace(array('%', '_'), array('\\%', '\\_'), $str); + } + + return $str; + } + + // -------------------------------------------------------------------- + + /** + * Affected Rows + * + * @access public + * @return integer + */ + function affected_rows() + { + return @cubrid_affected_rows($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * Insert ID + * + * @access public + * @return integer + */ + function insert_id() + { + return @cubrid_insert_id($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * "Count All" query + * + * Generates a platform-specific query string that counts all records in + * the specified table + * + * @access public + * @param string + * @return string + */ + function count_all($table = '') + { + if ($table == '') + { + return 0; + } + + $query = $this->query($this->_count_string . $this->_protect_identifiers('numrows') . " FROM " . $this->_protect_identifiers($table, TRUE, NULL, FALSE)); + + if ($query->num_rows() == 0) + { + return 0; + } + + $row = $query->row(); + $this->_reset_select(); + return (int) $row->numrows; + } + + // -------------------------------------------------------------------- + + /** + * List table query + * + * Generates a platform-specific query string so that the table names can be fetched + * + * @access private + * @param boolean + * @return string + */ + function _list_tables($prefix_limit = FALSE) + { + $sql = "SHOW TABLES"; + + if ($prefix_limit !== FALSE AND $this->dbprefix != '') + { + $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 + * + * @access public + * @param string the table name + * @return string + */ + function _list_columns($table = '') + { + return "SHOW COLUMNS FROM ".$this->_protect_identifiers($table, TRUE, NULL, FALSE); + } + + // -------------------------------------------------------------------- + + /** + * Field data query + * + * Generates a platform-specific query so that the column data can be retrieved + * + * @access public + * @param string the table name + * @return object + */ + function _field_data($table) + { + return "SELECT * FROM ".$table." LIMIT 1"; + } + + // -------------------------------------------------------------------- + + /** + * The error message string + * + * @access private + * @return string + */ + function _error_message() + { + return cubrid_error($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * The error message number + * + * @access private + * @return integer + */ + function _error_number() + { + return cubrid_errno($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * Escape the SQL Identifiers + * + * This function escapes column and table names + * + * @access private + * @param string + * @return string + */ + function _escape_identifiers($item) + { + if ($this->_escape_char == '') + { + return $item; + } + + foreach ($this->_reserved_identifiers as $id) + { + if (strpos($item, '.'.$id) !== FALSE) + { + $str = $this->_escape_char. str_replace('.', $this->_escape_char.'.', $item); + + // remove duplicates if the user already included the escape + return preg_replace('/['.$this->_escape_char.']+/', $this->_escape_char, $str); + } + } + + if (strpos($item, '.') !== FALSE) + { + $str = $this->_escape_char.str_replace('.', $this->_escape_char.'.'.$this->_escape_char, $item).$this->_escape_char; + } + else + { + $str = $this->_escape_char.$item.$this->_escape_char; + } + + // remove duplicates if the user already included the escape + return preg_replace('/['.$this->_escape_char.']+/', $this->_escape_char, $str); + } + + // -------------------------------------------------------------------- + + /** + * From Tables + * + * This function implicitly groups FROM tables so there is no confusion + * about operator precedence in harmony with SQL standards + * + * @access public + * @param type + * @return type + */ + function _from_tables($tables) + { + if ( ! is_array($tables)) + { + $tables = array($tables); + } + + return '('.implode(', ', $tables).')'; + } + + // -------------------------------------------------------------------- + + /** + * Insert statement + * + * Generates a platform-specific insert string from the supplied data + * + * @access public + * @param string the table name + * @param array the insert keys + * @param array the insert values + * @return string + */ + function _insert($table, $keys, $values) + { + return "INSERT INTO ".$table." (\"".implode('", "', $keys)."\") VALUES (".implode(', ', $values).")"; + } + + // -------------------------------------------------------------------- + + + /** + * Replace statement + * + * Generates a platform-specific replace string from the supplied data + * + * @access public + * @param string the table name + * @param array the insert keys + * @param array the insert values + * @return string + */ + function _replace($table, $keys, $values) + { + return "REPLACE INTO ".$table." (\"".implode('", "', $keys)."\") VALUES (".implode(', ', $values).")"; + } + + // -------------------------------------------------------------------- + + /** + * Insert_batch statement + * + * Generates a platform-specific insert string from the supplied data + * + * @access public + * @param string the table name + * @param array the insert keys + * @param array the insert values + * @return string + */ + function _insert_batch($table, $keys, $values) + { + return "INSERT INTO ".$table." (\"".implode('", "', $keys)."\") VALUES ".implode(', ', $values); + } + + // -------------------------------------------------------------------- + + + /** + * Update statement + * + * Generates a platform-specific update string from the supplied data + * + * @access public + * @param string the table name + * @param array the update data + * @param array the where clause + * @param array the orderby clause + * @param array the limit clause + * @return string + */ + function _update($table, $values, $where, $orderby = array(), $limit = FALSE) + { + foreach ($values as $key => $val) + { + $valstr[] = sprintf('"%s" = %s', $key, $val); + } + + $limit = ( ! $limit) ? '' : ' LIMIT '.$limit; + + $orderby = (count($orderby) >= 1)?' ORDER BY '.implode(", ", $orderby):''; + + $sql = "UPDATE ".$table." SET ".implode(', ', $valstr); + + $sql .= ($where != '' AND count($where) >=1) ? " WHERE ".implode(" ", $where) : ''; + + $sql .= $orderby.$limit; + + return $sql; + } + + // -------------------------------------------------------------------- + + + /** + * Update_Batch statement + * + * Generates a platform-specific batch update string from the supplied data + * + * @access public + * @param string the table name + * @param array the update data + * @param array the where clause + * @return string + */ + function _update_batch($table, $values, $index, $where = NULL) + { + $ids = array(); + $where = ($where != '' AND count($where) >=1) ? implode(" ", $where).' AND ' : ''; + + foreach ($values as $key => $val) + { + $ids[] = $val[$index]; + + foreach (array_keys($val) as $field) + { + if ($field != $index) + { + $final[$field][] = 'WHEN '.$index.' = '.$val[$index].' THEN '.$val[$field]; + } + } + } + + $sql = "UPDATE ".$table." SET "; + $cases = ''; + + foreach ($final as $k => $v) + { + $cases .= $k.' = CASE '."\n"; + foreach ($v as $row) + { + $cases .= $row."\n"; + } + + $cases .= 'ELSE '.$k.' END, '; + } + + $sql .= substr($cases, 0, -2); + + $sql .= ' WHERE '.$where.$index.' IN ('.implode(',', $ids).')'; + + return $sql; + } + + // -------------------------------------------------------------------- + + + /** + * Truncate statement + * + * Generates a platform-specific truncate string from the supplied data + * If the database does not support the truncate() command + * This function maps to "DELETE FROM table" + * + * @access public + * @param string the table name + * @return string + */ + function _truncate($table) + { + return "TRUNCATE ".$table; + } + + // -------------------------------------------------------------------- + + /** + * Delete statement + * + * Generates a platform-specific delete string from the supplied data + * + * @access public + * @param string the table name + * @param array the where clause + * @param string the limit clause + * @return string + */ + function _delete($table, $where = array(), $like = array(), $limit = FALSE) + { + $conditions = ''; + + if (count($where) > 0 OR count($like) > 0) + { + $conditions = "\nWHERE "; + $conditions .= implode("\n", $this->ar_where); + + if (count($where) > 0 && count($like) > 0) + { + $conditions .= " AND "; + } + $conditions .= implode("\n", $like); + } + + $limit = ( ! $limit) ? '' : ' LIMIT '.$limit; + + return "DELETE FROM ".$table.$conditions.$limit; + } + + // -------------------------------------------------------------------- + + /** + * Limit string + * + * Generates a platform-specific LIMIT clause + * + * @access public + * @param string the sql query string + * @param integer the number of rows to limit the query to + * @param integer the offset value + * @return string + */ + function _limit($sql, $limit, $offset) + { + if ($offset == 0) + { + $offset = ''; + } + else + { + $offset .= ", "; + } + + return $sql."LIMIT ".$offset.$limit; + } + + // -------------------------------------------------------------------- + + /** + * Close DB Connection + * + * @access public + * @param resource + * @return void + */ + function _close($conn_id) + { + @cubrid_close($conn_id); + } + +} + + +/* End of file cubrid_driver.php */ +/* Location: ./system/database/drivers/cubrid/cubrid_driver.php */ \ No newline at end of file diff --git a/system/database/drivers/cubrid/cubrid_forge.php b/system/database/drivers/cubrid/cubrid_forge.php new file mode 100644 index 0000000..bab03f7 --- /dev/null +++ b/system/database/drivers/cubrid/cubrid_forge.php @@ -0,0 +1,288 @@ +$attributes) + { + // Numeric field names aren't allowed in databases, so if the key is + // numeric, we know it was assigned by PHP and the developer manually + // entered the field information, so we'll simply add it to the list + if (is_numeric($field)) + { + $sql .= "\n\t$attributes"; + } + else + { + $attributes = array_change_key_case($attributes, CASE_UPPER); + + $sql .= "\n\t\"" . $this->db->_protect_identifiers($field) . "\""; + + if (array_key_exists('NAME', $attributes)) + { + $sql .= ' '.$this->db->_protect_identifiers($attributes['NAME']).' '; + } + + if (array_key_exists('TYPE', $attributes)) + { + $sql .= ' '.$attributes['TYPE']; + + if (array_key_exists('CONSTRAINT', $attributes)) + { + switch ($attributes['TYPE']) + { + case 'decimal': + case 'float': + case 'numeric': + $sql .= '('.implode(',', $attributes['CONSTRAINT']).')'; + break; + case 'enum': // As of version 8.4.0 CUBRID does not support + // enum data type. + break; + case 'set': + $sql .= '("'.implode('","', $attributes['CONSTRAINT']).'")'; + break; + default: + $sql .= '('.$attributes['CONSTRAINT'].')'; + } + } + } + + if (array_key_exists('UNSIGNED', $attributes) && $attributes['UNSIGNED'] === TRUE) + { + //$sql .= ' UNSIGNED'; + // As of version 8.4.0 CUBRID does not support UNSIGNED INTEGER data type. + // Will be supported in the next release as a part of MySQL Compatibility. + } + + if (array_key_exists('DEFAULT', $attributes)) + { + $sql .= ' DEFAULT \''.$attributes['DEFAULT'].'\''; + } + + if (array_key_exists('NULL', $attributes) && $attributes['NULL'] === TRUE) + { + $sql .= ' NULL'; + } + else + { + $sql .= ' NOT NULL'; + } + + if (array_key_exists('AUTO_INCREMENT', $attributes) && $attributes['AUTO_INCREMENT'] === TRUE) + { + $sql .= ' AUTO_INCREMENT'; + } + + if (array_key_exists('UNIQUE', $attributes) && $attributes['UNIQUE'] === TRUE) + { + $sql .= ' UNIQUE'; + } + } + + // don't add a comma on the end of the last field + if (++$current_field_count < count($fields)) + { + $sql .= ','; + } + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Create Table + * + * @access private + * @param string the table name + * @param mixed the fields + * @param mixed primary key(s) + * @param mixed key(s) + * @param boolean should 'IF NOT EXISTS' be added to the SQL + * @return bool + */ + function _create_table($table, $fields, $primary_keys, $keys, $if_not_exists) + { + $sql = 'CREATE TABLE '; + + if ($if_not_exists === TRUE) + { + //$sql .= 'IF NOT EXISTS '; + // As of version 8.4.0 CUBRID does not support this SQL syntax. + } + + $sql .= $this->db->_escape_identifiers($table)." ("; + + $sql .= $this->_process_fields($fields); + + // If there is a PK defined + if (count($primary_keys) > 0) + { + $key_name = "pk_" . $table . "_" . + $this->db->_protect_identifiers(implode('_', $primary_keys)); + + $primary_keys = $this->db->_protect_identifiers($primary_keys); + $sql .= ",\n\tCONSTRAINT " . $key_name . " PRIMARY KEY(" . implode(', ', $primary_keys) . ")"; + } + + if (is_array($keys) && count($keys) > 0) + { + foreach ($keys as $key) + { + if (is_array($key)) + { + $key_name = $this->db->_protect_identifiers(implode('_', $key)); + $key = $this->db->_protect_identifiers($key); + } + else + { + $key_name = $this->db->_protect_identifiers($key); + $key = array($key_name); + } + + $sql .= ",\n\tKEY \"{$key_name}\" (" . implode(', ', $key) . ")"; + } + } + + $sql .= "\n);"; + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Drop Table + * + * @access private + * @return string + */ + function _drop_table($table) + { + return "DROP TABLE IF EXISTS ".$this->db->_escape_identifiers($table); + } + + // -------------------------------------------------------------------- + + /** + * Alter table query + * + * Generates a platform-specific query so that a table can be altered + * Called by add_column(), drop_column(), and column_alter(), + * + * @access private + * @param string the ALTER type (ADD, DROP, CHANGE) + * @param string the column name + * @param array fields + * @param string the field after which we should add the new field + * @return object + */ + function _alter_table($alter_type, $table, $fields, $after_field = '') + { + $sql = 'ALTER TABLE '.$this->db->_protect_identifiers($table)." $alter_type "; + + // DROP has everything it needs now. + if ($alter_type == 'DROP') + { + return $sql.$this->db->_protect_identifiers($fields); + } + + $sql .= $this->_process_fields($fields); + + if ($after_field != '') + { + $sql .= ' AFTER ' . $this->db->_protect_identifiers($after_field); + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Rename a table + * + * Generates a platform-specific query so that a table can be renamed + * + * @access private + * @param string the old table name + * @param string the new table name + * @return string + */ + function _rename_table($table_name, $new_table_name) + { + $sql = 'RENAME TABLE '.$this->db->_protect_identifiers($table_name)." AS ".$this->db->_protect_identifiers($new_table_name); + return $sql; + } + +} + +/* End of file cubrid_forge.php */ +/* Location: ./system/database/drivers/cubrid/cubrid_forge.php */ \ No newline at end of file diff --git a/system/database/drivers/cubrid/cubrid_result.php b/system/database/drivers/cubrid/cubrid_result.php new file mode 100644 index 0000000..6f0c2b5 --- /dev/null +++ b/system/database/drivers/cubrid/cubrid_result.php @@ -0,0 +1,202 @@ +result_id); + } + + // -------------------------------------------------------------------- + + /** + * Number of fields in the result set + * + * @access public + * @return integer + */ + function num_fields() + { + return @cubrid_num_fields($this->result_id); + } + + // -------------------------------------------------------------------- + + /** + * Fetch Field Names + * + * Generates an array of column names + * + * @access public + * @return array + */ + function list_fields() + { + return cubrid_column_names($this->result_id); + } + + // -------------------------------------------------------------------- + + /** + * Field data + * + * Generates an array of objects containing field meta-data + * + * @access public + * @return array + */ + function field_data() + { + $retval = array(); + + $tablePrimaryKeys = array(); + + while ($field = cubrid_fetch_field($this->result_id)) + { + $F = new stdClass(); + $F->name = $field->name; + $F->type = $field->type; + $F->default = $field->def; + $F->max_length = $field->max_length; + + // At this moment primary_key property is not returned when + // cubrid_fetch_field is called. The following code will + // provide a patch for it. primary_key property will be added + // in the next release. + + // TODO: later version of CUBRID will provide primary_key + // property. + // When PK is defined in CUBRID, an index is automatically + // created in the db_index system table in the form of + // pk_tblname_fieldname. So the following will count how many + // columns are there which satisfy this format. + // The query will search for exact single columns, thus + // compound PK is not supported. + $res = cubrid_query($this->conn_id, + "SELECT COUNT(*) FROM db_index WHERE class_name = '" . $field->table . + "' AND is_primary_key = 'YES' AND index_name = 'pk_" . + $field->table . "_" . $field->name . "'" + ); + + if ($res) + { + $row = cubrid_fetch_array($res, CUBRID_NUM); + $F->primary_key = ($row[0] > 0 ? 1 : null); + } + else + { + $F->primary_key = null; + } + + if (is_resource($res)) + { + cubrid_close_request($res); + $this->result_id = FALSE; + } + + $retval[] = $F; + } + + return $retval; + } + + // -------------------------------------------------------------------- + + /** + * Free the result + * + * @return null + */ + function free_result() + { + if(is_resource($this->result_id) || + 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 + * + * @access private + * @return array + */ + function _data_seek($n = 0) + { + return cubrid_data_seek($this->result_id, $n); + } + + // -------------------------------------------------------------------- + + /** + * Result - associative array + * + * Returns the result set as an array + * + * @access private + * @return array + */ + function _fetch_assoc() + { + return cubrid_fetch_assoc($this->result_id); + } + + // -------------------------------------------------------------------- + + /** + * Result - object + * + * Returns the result set as an object + * + * @access private + * @return object + */ + function _fetch_object() + { + return cubrid_fetch_object($this->result_id); + } + +} + + +/* End of file cubrid_result.php */ +/* Location: ./system/database/drivers/cubrid/cubrid_result.php */ \ No newline at end of file diff --git a/system/database/drivers/cubrid/cubrid_utility.php b/system/database/drivers/cubrid/cubrid_utility.php new file mode 100644 index 0000000..cd16d1e --- /dev/null +++ b/system/database/drivers/cubrid/cubrid_utility.php @@ -0,0 +1,108 @@ +conn_id) + { + return "SELECT '" . $this->database . "'"; + } + else + { + return FALSE; + } + } + + // -------------------------------------------------------------------- + + /** + * Optimize table query + * + * Generates a platform-specific query so that a table can be optimized + * + * @access private + * @param string the table name + * @return object + * @link http://www.cubrid.org/manual/840/en/Optimize%20Database + */ + function _optimize_table($table) + { + // No SQL based support in CUBRID as of version 8.4.0. Database or + // table optimization can be performed using CUBRID Manager + // database administration tool. See the link above for more info. + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Repair table query + * + * Generates a platform-specific query so that a table can be repaired + * + * @access private + * @param string the table name + * @return object + * @link http://www.cubrid.org/manual/840/en/Checking%20Database%20Consistency + */ + function _repair_table($table) + { + // Not supported in CUBRID as of version 8.4.0. Database or + // table consistency can be checked using CUBRID Manager + // database administration tool. See the link above for more info. + return FALSE; + } + + // -------------------------------------------------------------------- + /** + * CUBRID Export + * + * @access private + * @param array Preferences + * @return mixed + */ + 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_unsuported_feature'); + } +} + +/* End of file cubrid_utility.php */ +/* Location: ./system/database/drivers/cubrid/cubrid_utility.php */ \ No newline at end of file diff --git a/system/database/drivers/cubrid/index.html b/system/database/drivers/cubrid/index.html new file mode 100644 index 0000000..c942a79 --- /dev/null +++ b/system/database/drivers/cubrid/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/system/database/drivers/index.html b/system/database/drivers/index.html new file mode 100644 index 0000000..c942a79 --- /dev/null +++ b/system/database/drivers/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/system/database/drivers/mssql/index.html b/system/database/drivers/mssql/index.html new file mode 100644 index 0000000..c942a79 --- /dev/null +++ b/system/database/drivers/mssql/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/system/database/drivers/mssql/mssql_driver.php b/system/database/drivers/mssql/mssql_driver.php new file mode 100644 index 0000000..b39bd93 --- /dev/null +++ b/system/database/drivers/mssql/mssql_driver.php @@ -0,0 +1,667 @@ +port != '') + { + $this->hostname .= ','.$this->port; + } + + return @mssql_connect($this->hostname, $this->username, $this->password); + } + + // -------------------------------------------------------------------- + + /** + * Persistent database connection + * + * @access private called by the base class + * @return resource + */ + function db_pconnect() + { + if ($this->port != '') + { + $this->hostname .= ','.$this->port; + } + + return @mssql_pconnect($this->hostname, $this->username, $this->password); + } + + // -------------------------------------------------------------------- + + /** + * Reconnect + * + * Keep / reestablish the db connection if no queries have been + * sent for a length of time exceeding the server's idle timeout + * + * @access public + * @return void + */ + function reconnect() + { + // not implemented in MSSQL + } + + // -------------------------------------------------------------------- + + /** + * Select the database + * + * @access private called by the base class + * @return resource + */ + function db_select() + { + // Note: The brackets are required in the event that the DB name + // contains reserved characters + return @mssql_select_db('['.$this->database.']', $this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * Set client character set + * + * @access public + * @param string + * @param string + * @return resource + */ + function db_set_charset($charset, $collation) + { + // @todo - add support if needed + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Execute the query + * + * @access private called by the base class + * @param string an SQL query + * @return resource + */ + function _execute($sql) + { + $sql = $this->_prep_query($sql); + return @mssql_query($sql, $this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * Prep the query + * + * If needed, each database adapter can prep the query string + * + * @access private called by execute() + * @param string an SQL query + * @return string + */ + function _prep_query($sql) + { + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Begin Transaction + * + * @access public + * @return bool + */ + function trans_begin($test_mode = FALSE) + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + 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) ? TRUE : FALSE; + + $this->simple_query('BEGIN TRAN'); + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Commit Transaction + * + * @access public + * @return bool + */ + function trans_commit() + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + return TRUE; + } + + $this->simple_query('COMMIT TRAN'); + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Rollback Transaction + * + * @access public + * @return bool + */ + function trans_rollback() + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + return TRUE; + } + + $this->simple_query('ROLLBACK TRAN'); + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Escape String + * + * @access public + * @param string + * @param bool whether or not the string will be used in a LIKE condition + * @return string + */ + function escape_str($str, $like = FALSE) + { + if (is_array($str)) + { + foreach ($str as $key => $val) + { + $str[$key] = $this->escape_str($val, $like); + } + + return $str; + } + + // Escape single quotes + $str = str_replace("'", "''", remove_invisible_characters($str)); + + // escape LIKE condition wildcards + if ($like === TRUE) + { + $str = 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; + } + + // -------------------------------------------------------------------- + + /** + * Affected Rows + * + * @access public + * @return integer + */ + function affected_rows() + { + return @mssql_rows_affected($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * Insert ID + * + * Returns the last id created in the Identity column. + * + * @access public + * @return integer + */ + function insert_id() + { + $ver = self::_parse_major_version($this->version()); + $sql = ($ver >= 8 ? "SELECT SCOPE_IDENTITY() AS last_id" : "SELECT @@IDENTITY AS last_id"); + $query = $this->query($sql); + $row = $query->row(); + return $row->last_id; + } + + // -------------------------------------------------------------------- + + /** + * Parse major version + * + * Grabs the major version number from the + * database server version string passed in. + * + * @access private + * @param string $version + * @return int16 major version number + */ + function _parse_major_version($version) + { + preg_match('/([0-9]+)\.([0-9]+)\.([0-9]+)/', $version, $ver_info); + return $ver_info[1]; // return the major version b/c that's all we're interested in. + } + + // -------------------------------------------------------------------- + + /** + * Version number query string + * + * @access public + * @return string + */ + function _version() + { + return "SELECT @@VERSION AS ver"; + } + + // -------------------------------------------------------------------- + + /** + * "Count All" query + * + * Generates a platform-specific query string that counts all records in + * the specified database + * + * @access public + * @param string + * @return string + */ + function count_all($table = '') + { + if ($table == '') + { + return 0; + } + + $query = $this->query($this->_count_string . $this->_protect_identifiers('numrows') . " FROM " . $this->_protect_identifiers($table, TRUE, NULL, FALSE)); + + if ($query->num_rows() == 0) + { + return 0; + } + + $row = $query->row(); + $this->_reset_select(); + return (int) $row->numrows; + } + + // -------------------------------------------------------------------- + + /** + * List table query + * + * Generates a platform-specific query string so that the table names can be fetched + * + * @access private + * @param boolean + * @return string + */ + function _list_tables($prefix_limit = FALSE) + { + $sql = "SELECT name FROM sysobjects WHERE type = 'U' ORDER BY name"; + + // for future compatibility + if ($prefix_limit !== FALSE AND $this->dbprefix != '') + { + //$sql .= " LIKE '".$this->escape_like_str($this->dbprefix)."%' ".sprintf($this->_like_escape_str, $this->_like_escape_chr); + return FALSE; // not currently supported + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * List column query + * + * Generates a platform-specific query string so that the column names can be fetched + * + * @access private + * @param string the table name + * @return string + */ + function _list_columns($table = '') + { + return "SELECT * FROM INFORMATION_SCHEMA.Columns WHERE TABLE_NAME = '".$table."'"; + } + + // -------------------------------------------------------------------- + + /** + * Field data query + * + * Generates a platform-specific query so that the column data can be retrieved + * + * @access public + * @param string the table name + * @return object + */ + function _field_data($table) + { + return "SELECT TOP 1 * FROM ".$table; + } + + // -------------------------------------------------------------------- + + /** + * The error message string + * + * @access private + * @return string + */ + function _error_message() + { + return mssql_get_last_message(); + } + + // -------------------------------------------------------------------- + + /** + * The error message number + * + * @access private + * @return integer + */ + function _error_number() + { + // Are error numbers supported? + return ''; + } + + // -------------------------------------------------------------------- + + /** + * Escape the SQL Identifiers + * + * This function escapes column and table names + * + * @access private + * @param string + * @return string + */ + function _escape_identifiers($item) + { + if ($this->_escape_char == '') + { + return $item; + } + + foreach ($this->_reserved_identifiers as $id) + { + if (strpos($item, '.'.$id) !== FALSE) + { + $str = $this->_escape_char. str_replace('.', $this->_escape_char.'.', $item); + + // remove duplicates if the user already included the escape + return preg_replace('/['.$this->_escape_char.']+/', $this->_escape_char, $str); + } + } + + if (strpos($item, '.') !== FALSE) + { + $str = $this->_escape_char.str_replace('.', $this->_escape_char.'.'.$this->_escape_char, $item).$this->_escape_char; + } + else + { + $str = $this->_escape_char.$item.$this->_escape_char; + } + + // remove duplicates if the user already included the escape + return preg_replace('/['.$this->_escape_char.']+/', $this->_escape_char, $str); + } + + // -------------------------------------------------------------------- + + /** + * From Tables + * + * This function implicitly groups FROM tables so there is no confusion + * about operator precedence in harmony with SQL standards + * + * @access public + * @param type + * @return type + */ + function _from_tables($tables) + { + if ( ! is_array($tables)) + { + $tables = array($tables); + } + + return implode(', ', $tables); + } + + // -------------------------------------------------------------------- + + /** + * Insert statement + * + * Generates a platform-specific insert string from the supplied data + * + * @access public + * @param string the table name + * @param array the insert keys + * @param array the insert values + * @return string + */ + function _insert($table, $keys, $values) + { + return "INSERT INTO ".$table." (".implode(', ', $keys).") VALUES (".implode(', ', $values).")"; + } + + // -------------------------------------------------------------------- + + /** + * Update statement + * + * Generates a platform-specific update string from the supplied data + * + * @access public + * @param string the table name + * @param array the update data + * @param array the where clause + * @param array the orderby clause + * @param array the limit clause + * @return string + */ + function _update($table, $values, $where, $orderby = array(), $limit = FALSE) + { + foreach ($values as $key => $val) + { + $valstr[] = $key." = ".$val; + } + + $limit = ( ! $limit) ? '' : ' LIMIT '.$limit; + + $orderby = (count($orderby) >= 1)?' ORDER BY '.implode(", ", $orderby):''; + + $sql = "UPDATE ".$table." SET ".implode(', ', $valstr); + + $sql .= ($where != '' AND count($where) >=1) ? " WHERE ".implode(" ", $where) : ''; + + $sql .= $orderby.$limit; + + return $sql; + } + + + // -------------------------------------------------------------------- + + /** + * Truncate statement + * + * Generates a platform-specific truncate string from the supplied data + * If the database does not support the truncate() command + * This function maps to "DELETE FROM table" + * + * @access public + * @param string the table name + * @return string + */ + function _truncate($table) + { + return "TRUNCATE ".$table; + } + + // -------------------------------------------------------------------- + + /** + * Delete statement + * + * Generates a platform-specific delete string from the supplied data + * + * @access public + * @param string the table name + * @param array the where clause + * @param string the limit clause + * @return string + */ + function _delete($table, $where = array(), $like = array(), $limit = FALSE) + { + $conditions = ''; + + if (count($where) > 0 OR count($like) > 0) + { + $conditions = "\nWHERE "; + $conditions .= implode("\n", $this->ar_where); + + if (count($where) > 0 && count($like) > 0) + { + $conditions .= " AND "; + } + $conditions .= implode("\n", $like); + } + + $limit = ( ! $limit) ? '' : ' LIMIT '.$limit; + + return "DELETE FROM ".$table.$conditions.$limit; + } + + // -------------------------------------------------------------------- + + /** + * Limit string + * + * Generates a platform-specific LIMIT clause + * + * @access public + * @param string the sql query string + * @param integer the number of rows to limit the query to + * @param integer the offset value + * @return string + */ + function _limit($sql, $limit, $offset) + { + $i = $limit + $offset; + + return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.$i.' ', $sql); + } + + // -------------------------------------------------------------------- + + /** + * Close DB Connection + * + * @access public + * @param resource + * @return void + */ + function _close($conn_id) + { + @mssql_close($conn_id); + } + +} + + + +/* End of file mssql_driver.php */ +/* Location: ./system/database/drivers/mssql/mssql_driver.php */ \ No newline at end of file diff --git a/system/database/drivers/mssql/mssql_forge.php b/system/database/drivers/mssql/mssql_forge.php new file mode 100644 index 0000000..70b20ec --- /dev/null +++ b/system/database/drivers/mssql/mssql_forge.php @@ -0,0 +1,248 @@ +db->_escape_identifiers($table); + } + + // -------------------------------------------------------------------- + + /** + * Create Table + * + * @access private + * @param string the table name + * @param array the fields + * @param mixed primary key(s) + * @param mixed key(s) + * @param boolean should 'IF NOT EXISTS' be added to the SQL + * @return bool + */ + function _create_table($table, $fields, $primary_keys, $keys, $if_not_exists) + { + $sql = 'CREATE TABLE '; + + if ($if_not_exists === TRUE) + { + $sql .= 'IF NOT EXISTS '; + } + + $sql .= $this->db->_escape_identifiers($table)." ("; + $current_field_count = 0; + + foreach ($fields as $field=>$attributes) + { + // Numeric field names aren't allowed in databases, so if the key is + // numeric, we know it was assigned by PHP and the developer manually + // entered the field information, so we'll simply add it to the list + if (is_numeric($field)) + { + $sql .= "\n\t$attributes"; + } + else + { + $attributes = array_change_key_case($attributes, CASE_UPPER); + + $sql .= "\n\t".$this->db->_protect_identifiers($field); + + $sql .= ' '.$attributes['TYPE']; + + if (array_key_exists('CONSTRAINT', $attributes)) + { + $sql .= '('.$attributes['CONSTRAINT'].')'; + } + + if (array_key_exists('UNSIGNED', $attributes) && $attributes['UNSIGNED'] === TRUE) + { + $sql .= ' UNSIGNED'; + } + + if (array_key_exists('DEFAULT', $attributes)) + { + $sql .= ' DEFAULT \''.$attributes['DEFAULT'].'\''; + } + + if (array_key_exists('NULL', $attributes) && $attributes['NULL'] === TRUE) + { + $sql .= ' NULL'; + } + else + { + $sql .= ' NOT NULL'; + } + + if (array_key_exists('AUTO_INCREMENT', $attributes) && $attributes['AUTO_INCREMENT'] === TRUE) + { + $sql .= ' AUTO_INCREMENT'; + } + } + + // don't add a comma on the end of the last field + if (++$current_field_count < count($fields)) + { + $sql .= ','; + } + } + + if (count($primary_keys) > 0) + { + $primary_keys = $this->db->_protect_identifiers($primary_keys); + $sql .= ",\n\tPRIMARY KEY (" . implode(', ', $primary_keys) . ")"; + } + + if (is_array($keys) && count($keys) > 0) + { + foreach ($keys as $key) + { + if (is_array($key)) + { + $key = $this->db->_protect_identifiers($key); + } + else + { + $key = array($this->db->_protect_identifiers($key)); + } + + $sql .= ",\n\tFOREIGN KEY (" . implode(', ', $key) . ")"; + } + } + + $sql .= "\n)"; + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Alter table query + * + * Generates a platform-specific query so that a table can be altered + * Called by add_column(), drop_column(), and column_alter(), + * + * @access private + * @param string the ALTER type (ADD, DROP, CHANGE) + * @param string the column name + * @param string the table name + * @param string the column definition + * @param string the default value + * @param boolean should 'NOT NULL' be added + * @param string the field after which we should add the new field + * @return object + */ + function _alter_table($alter_type, $table, $column_name, $column_definition = '', $default_value = '', $null = '', $after_field = '') + { + $sql = 'ALTER TABLE '.$this->db->_protect_identifiers($table)." $alter_type ".$this->db->_protect_identifiers($column_name); + + // DROP has everything it needs now. + if ($alter_type == 'DROP') + { + return $sql; + } + + $sql .= " $column_definition"; + + if ($default_value != '') + { + $sql .= " DEFAULT \"$default_value\""; + } + + if ($null === NULL) + { + $sql .= ' NULL'; + } + else + { + $sql .= ' NOT NULL'; + } + + if ($after_field != '') + { + $sql .= ' AFTER ' . $this->db->_protect_identifiers($after_field); + } + + return $sql; + + } + + // -------------------------------------------------------------------- + + /** + * Rename a table + * + * Generates a platform-specific query so that a table can be renamed + * + * @access private + * @param string the old table name + * @param string the new table name + * @return string + */ + function _rename_table($table_name, $new_table_name) + { + // I think this syntax will work, but can find little documentation on renaming tables in MSSQL + $sql = 'ALTER TABLE '.$this->db->_protect_identifiers($table_name)." RENAME TO ".$this->db->_protect_identifiers($new_table_name); + return $sql; + } + +} + +/* End of file mssql_forge.php */ +/* Location: ./system/database/drivers/mssql/mssql_forge.php */ \ No newline at end of file diff --git a/system/database/drivers/mssql/mssql_result.php b/system/database/drivers/mssql/mssql_result.php new file mode 100644 index 0000000..2897ca5 --- /dev/null +++ b/system/database/drivers/mssql/mssql_result.php @@ -0,0 +1,169 @@ +result_id); + } + + // -------------------------------------------------------------------- + + /** + * Number of fields in the result set + * + * @access public + * @return integer + */ + function num_fields() + { + return @mssql_num_fields($this->result_id); + } + + // -------------------------------------------------------------------- + + /** + * Fetch Field Names + * + * Generates an array of column names + * + * @access public + * @return array + */ + function list_fields() + { + $field_names = array(); + 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 + * + * @access public + * @return array + */ + function field_data() + { + $retval = array(); + while ($field = mssql_fetch_field($this->result_id)) + { + $F = new stdClass(); + $F->name = $field->name; + $F->type = $field->type; + $F->max_length = $field->max_length; + $F->primary_key = 0; + $F->default = ''; + + $retval[] = $F; + } + + return $retval; + } + + // -------------------------------------------------------------------- + + /** + * Free the result + * + * @return null + */ + 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 + * + * @access private + * @return array + */ + function _data_seek($n = 0) + { + return mssql_data_seek($this->result_id, $n); + } + + // -------------------------------------------------------------------- + + /** + * Result - associative array + * + * Returns the result set as an array + * + * @access private + * @return array + */ + function _fetch_assoc() + { + return mssql_fetch_assoc($this->result_id); + } + + // -------------------------------------------------------------------- + + /** + * Result - object + * + * Returns the result set as an object + * + * @access private + * @return object + */ + function _fetch_object() + { + return mssql_fetch_object($this->result_id); + } + +} + + +/* End of file mssql_result.php */ +/* Location: ./system/database/drivers/mssql/mssql_result.php */ \ No newline at end of file diff --git a/system/database/drivers/mssql/mssql_utility.php b/system/database/drivers/mssql/mssql_utility.php new file mode 100644 index 0000000..48ecbc7 --- /dev/null +++ b/system/database/drivers/mssql/mssql_utility.php @@ -0,0 +1,88 @@ +db->display_error('db_unsuported_feature'); + } + +} + +/* End of file mssql_utility.php */ +/* Location: ./system/database/drivers/mssql/mssql_utility.php */ \ No newline at end of file diff --git a/system/database/drivers/mysql/index.html b/system/database/drivers/mysql/index.html new file mode 100644 index 0000000..c942a79 --- /dev/null +++ b/system/database/drivers/mysql/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/system/database/drivers/mysql/mysql_driver.php b/system/database/drivers/mysql/mysql_driver.php new file mode 100644 index 0000000..f87cfea --- /dev/null +++ b/system/database/drivers/mysql/mysql_driver.php @@ -0,0 +1,779 @@ +port != '') + { + $this->hostname .= ':'.$this->port; + } + + return @mysql_connect($this->hostname, $this->username, $this->password, TRUE); + } + + // -------------------------------------------------------------------- + + /** + * Persistent database connection + * + * @access private called by the base class + * @return resource + */ + function db_pconnect() + { + if ($this->port != '') + { + $this->hostname .= ':'.$this->port; + } + + return @mysql_pconnect($this->hostname, $this->username, $this->password); + } + + // -------------------------------------------------------------------- + + /** + * Reconnect + * + * Keep / reestablish the db connection if no queries have been + * sent for a length of time exceeding the server's idle timeout + * + * @access public + * @return void + */ + function reconnect() + { + if (mysql_ping($this->conn_id) === FALSE) + { + $this->conn_id = FALSE; + } + } + + // -------------------------------------------------------------------- + + /** + * Select the database + * + * @access private called by the base class + * @return resource + */ + function db_select() + { + return @mysql_select_db($this->database, $this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * Set client character set + * + * @access public + * @param string + * @param string + * @return resource + */ + function db_set_charset($charset, $collation) + { + if ( ! isset($this->use_set_names)) + { + // mysql_set_charset() requires PHP >= 5.2.3 and MySQL >= 5.0.7, use SET NAMES as fallback + $this->use_set_names = (version_compare(PHP_VERSION, '5.2.3', '>=') && version_compare(mysql_get_server_info(), '5.0.7', '>=')) ? FALSE : TRUE; + } + + if ($this->use_set_names === TRUE) + { + return @mysql_query("SET NAMES '".$this->escape_str($charset)."' COLLATE '".$this->escape_str($collation)."'", $this->conn_id); + } + else + { + return @mysql_set_charset($charset, $this->conn_id); + } + } + + // -------------------------------------------------------------------- + + /** + * Version number query string + * + * @access public + * @return string + */ + function _version() + { + return "SELECT version() AS ver"; + } + + // -------------------------------------------------------------------- + + /** + * Execute the query + * + * @access private called by the base class + * @param string an SQL query + * @return resource + */ + function _execute($sql) + { + $sql = $this->_prep_query($sql); + return @mysql_query($sql, $this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * Prep the query + * + * If needed, each database adapter can prep the query string + * + * @access private called by execute() + * @param string an SQL query + * @return string + */ + function _prep_query($sql) + { + // "DELETE FROM TABLE" returns 0 affected rows This hack modifies + // the query so that it returns the number of affected rows + if ($this->delete_hack === TRUE) + { + if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $sql)) + { + $sql = preg_replace("/^\s*DELETE\s+FROM\s+(\S+)\s*$/", "DELETE FROM \\1 WHERE 1=1", $sql); + } + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Begin Transaction + * + * @access public + * @return bool + */ + function trans_begin($test_mode = FALSE) + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + 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) ? TRUE : FALSE; + + $this->simple_query('SET AUTOCOMMIT=0'); + $this->simple_query('START TRANSACTION'); // can also be BEGIN or BEGIN WORK + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Commit Transaction + * + * @access public + * @return bool + */ + function trans_commit() + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + return TRUE; + } + + $this->simple_query('COMMIT'); + $this->simple_query('SET AUTOCOMMIT=1'); + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Rollback Transaction + * + * @access public + * @return bool + */ + function trans_rollback() + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + return TRUE; + } + + $this->simple_query('ROLLBACK'); + $this->simple_query('SET AUTOCOMMIT=1'); + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Escape String + * + * @access public + * @param string + * @param bool whether or not the string will be used in a LIKE condition + * @return string + */ + function escape_str($str, $like = FALSE) + { + if (is_array($str)) + { + foreach ($str as $key => $val) + { + $str[$key] = $this->escape_str($val, $like); + } + + return $str; + } + + if (function_exists('mysql_real_escape_string') AND is_resource($this->conn_id)) + { + $str = mysql_real_escape_string($str, $this->conn_id); + } + elseif (function_exists('mysql_escape_string')) + { + $str = mysql_escape_string($str); + } + else + { + $str = addslashes($str); + } + + // escape LIKE condition wildcards + if ($like === TRUE) + { + $str = str_replace(array('%', '_'), array('\\%', '\\_'), $str); + } + + return $str; + } + + // -------------------------------------------------------------------- + + /** + * Affected Rows + * + * @access public + * @return integer + */ + function affected_rows() + { + return @mysql_affected_rows($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * Insert ID + * + * @access public + * @return integer + */ + function insert_id() + { + return @mysql_insert_id($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * "Count All" query + * + * Generates a platform-specific query string that counts all records in + * the specified database + * + * @access public + * @param string + * @return string + */ + function count_all($table = '') + { + if ($table == '') + { + return 0; + } + + $query = $this->query($this->_count_string . $this->_protect_identifiers('numrows') . " FROM " . $this->_protect_identifiers($table, TRUE, NULL, FALSE)); + + if ($query->num_rows() == 0) + { + return 0; + } + + $row = $query->row(); + $this->_reset_select(); + return (int) $row->numrows; + } + + // -------------------------------------------------------------------- + + /** + * List table query + * + * Generates a platform-specific query string so that the table names can be fetched + * + * @access private + * @param boolean + * @return string + */ + function _list_tables($prefix_limit = FALSE) + { + $sql = "SHOW TABLES FROM ".$this->_escape_char.$this->database.$this->_escape_char; + + if ($prefix_limit !== FALSE AND $this->dbprefix != '') + { + $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 + * + * @access public + * @param string the table name + * @return string + */ + function _list_columns($table = '') + { + return "SHOW COLUMNS FROM ".$this->_protect_identifiers($table, TRUE, NULL, FALSE); + } + + // -------------------------------------------------------------------- + + /** + * Field data query + * + * Generates a platform-specific query so that the column data can be retrieved + * + * @access public + * @param string the table name + * @return object + */ + function _field_data($table) + { + return "DESCRIBE ".$table; + } + + // -------------------------------------------------------------------- + + /** + * The error message string + * + * @access private + * @return string + */ + function _error_message() + { + return mysql_error($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * The error message number + * + * @access private + * @return integer + */ + function _error_number() + { + return mysql_errno($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * Escape the SQL Identifiers + * + * This function escapes column and table names + * + * @access private + * @param string + * @return string + */ + function _escape_identifiers($item) + { + if ($this->_escape_char == '') + { + return $item; + } + + foreach ($this->_reserved_identifiers as $id) + { + if (strpos($item, '.'.$id) !== FALSE) + { + $str = $this->_escape_char. str_replace('.', $this->_escape_char.'.', $item); + + // remove duplicates if the user already included the escape + return preg_replace('/['.$this->_escape_char.']+/', $this->_escape_char, $str); + } + } + + if (strpos($item, '.') !== FALSE) + { + $str = $this->_escape_char.str_replace('.', $this->_escape_char.'.'.$this->_escape_char, $item).$this->_escape_char; + } + else + { + $str = $this->_escape_char.$item.$this->_escape_char; + } + + // remove duplicates if the user already included the escape + return preg_replace('/['.$this->_escape_char.']+/', $this->_escape_char, $str); + } + + // -------------------------------------------------------------------- + + /** + * From Tables + * + * This function implicitly groups FROM tables so there is no confusion + * about operator precedence in harmony with SQL standards + * + * @access public + * @param type + * @return type + */ + function _from_tables($tables) + { + if ( ! is_array($tables)) + { + $tables = array($tables); + } + + return '('.implode(', ', $tables).')'; + } + + // -------------------------------------------------------------------- + + /** + * Insert statement + * + * Generates a platform-specific insert string from the supplied data + * + * @access public + * @param string the table name + * @param array the insert keys + * @param array the insert values + * @return string + */ + function _insert($table, $keys, $values) + { + return "INSERT INTO ".$table." (".implode(', ', $keys).") VALUES (".implode(', ', $values).")"; + } + + // -------------------------------------------------------------------- + + + /** + * Replace statement + * + * Generates a platform-specific replace string from the supplied data + * + * @access public + * @param string the table name + * @param array the insert keys + * @param array the insert values + * @return string + */ + function _replace($table, $keys, $values) + { + return "REPLACE INTO ".$table." (".implode(', ', $keys).") VALUES (".implode(', ', $values).")"; + } + + // -------------------------------------------------------------------- + + /** + * Insert_batch statement + * + * Generates a platform-specific insert string from the supplied data + * + * @access public + * @param string the table name + * @param array the insert keys + * @param array the insert values + * @return string + */ + function _insert_batch($table, $keys, $values) + { + return "INSERT INTO ".$table." (".implode(', ', $keys).") VALUES ".implode(', ', $values); + } + + // -------------------------------------------------------------------- + + + /** + * Update statement + * + * Generates a platform-specific update string from the supplied data + * + * @access public + * @param string the table name + * @param array the update data + * @param array the where clause + * @param array the orderby clause + * @param array the limit clause + * @return string + */ + function _update($table, $values, $where, $orderby = array(), $limit = FALSE) + { + foreach ($values as $key => $val) + { + $valstr[] = $key . ' = ' . $val; + } + + $limit = ( ! $limit) ? '' : ' LIMIT '.$limit; + + $orderby = (count($orderby) >= 1)?' ORDER BY '.implode(", ", $orderby):''; + + $sql = "UPDATE ".$table." SET ".implode(', ', $valstr); + + $sql .= ($where != '' AND count($where) >=1) ? " WHERE ".implode(" ", $where) : ''; + + $sql .= $orderby.$limit; + + return $sql; + } + + // -------------------------------------------------------------------- + + + /** + * Update_Batch statement + * + * Generates a platform-specific batch update string from the supplied data + * + * @access public + * @param string the table name + * @param array the update data + * @param array the where clause + * @return string + */ + function _update_batch($table, $values, $index, $where = NULL) + { + $ids = array(); + $where = ($where != '' AND count($where) >=1) ? implode(" ", $where).' AND ' : ''; + + foreach ($values as $key => $val) + { + $ids[] = $val[$index]; + + foreach (array_keys($val) as $field) + { + if ($field != $index) + { + $final[$field][] = 'WHEN '.$index.' = '.$val[$index].' THEN '.$val[$field]; + } + } + } + + $sql = "UPDATE ".$table." SET "; + $cases = ''; + + foreach ($final as $k => $v) + { + $cases .= $k.' = CASE '."\n"; + foreach ($v as $row) + { + $cases .= $row."\n"; + } + + $cases .= 'ELSE '.$k.' END, '; + } + + $sql .= substr($cases, 0, -2); + + $sql .= ' WHERE '.$where.$index.' IN ('.implode(',', $ids).')'; + + return $sql; + } + + // -------------------------------------------------------------------- + + + /** + * Truncate statement + * + * Generates a platform-specific truncate string from the supplied data + * If the database does not support the truncate() command + * This function maps to "DELETE FROM table" + * + * @access public + * @param string the table name + * @return string + */ + function _truncate($table) + { + return "TRUNCATE ".$table; + } + + // -------------------------------------------------------------------- + + /** + * Delete statement + * + * Generates a platform-specific delete string from the supplied data + * + * @access public + * @param string the table name + * @param array the where clause + * @param string the limit clause + * @return string + */ + function _delete($table, $where = array(), $like = array(), $limit = FALSE) + { + $conditions = ''; + + if (count($where) > 0 OR count($like) > 0) + { + $conditions = "\nWHERE "; + $conditions .= implode("\n", $this->ar_where); + + if (count($where) > 0 && count($like) > 0) + { + $conditions .= " AND "; + } + $conditions .= implode("\n", $like); + } + + $limit = ( ! $limit) ? '' : ' LIMIT '.$limit; + + return "DELETE FROM ".$table.$conditions.$limit; + } + + // -------------------------------------------------------------------- + + /** + * Limit string + * + * Generates a platform-specific LIMIT clause + * + * @access public + * @param string the sql query string + * @param integer the number of rows to limit the query to + * @param integer the offset value + * @return string + */ + function _limit($sql, $limit, $offset) + { + if ($offset == 0) + { + $offset = ''; + } + else + { + $offset .= ", "; + } + + return $sql."LIMIT ".$offset.$limit; + } + + // -------------------------------------------------------------------- + + /** + * Close DB Connection + * + * @access public + * @param resource + * @return void + */ + function _close($conn_id) + { + @mysql_close($conn_id); + } + +} + + +/* End of file mysql_driver.php */ +/* Location: ./system/database/drivers/mysql/mysql_driver.php */ \ No newline at end of file diff --git a/system/database/drivers/mysql/mysql_forge.php b/system/database/drivers/mysql/mysql_forge.php new file mode 100644 index 0000000..c1cae13 --- /dev/null +++ b/system/database/drivers/mysql/mysql_forge.php @@ -0,0 +1,273 @@ +$attributes) + { + // Numeric field names aren't allowed in databases, so if the key is + // numeric, we know it was assigned by PHP and the developer manually + // entered the field information, so we'll simply add it to the list + if (is_numeric($field)) + { + $sql .= "\n\t$attributes"; + } + else + { + $attributes = array_change_key_case($attributes, CASE_UPPER); + + $sql .= "\n\t".$this->db->_protect_identifiers($field); + + if (array_key_exists('NAME', $attributes)) + { + $sql .= ' '.$this->db->_protect_identifiers($attributes['NAME']).' '; + } + + if (array_key_exists('TYPE', $attributes)) + { + $sql .= ' '.$attributes['TYPE']; + + if (array_key_exists('CONSTRAINT', $attributes)) + { + switch ($attributes['TYPE']) + { + case 'decimal': + case 'float': + case 'numeric': + $sql .= '('.implode(',', $attributes['CONSTRAINT']).')'; + break; + + case 'enum': + case 'set': + $sql .= '("'.implode('","', $attributes['CONSTRAINT']).'")'; + break; + + default: + $sql .= '('.$attributes['CONSTRAINT'].')'; + } + } + } + + if (array_key_exists('UNSIGNED', $attributes) && $attributes['UNSIGNED'] === TRUE) + { + $sql .= ' UNSIGNED'; + } + + if (array_key_exists('DEFAULT', $attributes)) + { + $sql .= ' DEFAULT \''.$attributes['DEFAULT'].'\''; + } + + if (array_key_exists('NULL', $attributes) && $attributes['NULL'] === TRUE) + { + $sql .= ' NULL'; + } + else + { + $sql .= ' NOT NULL'; + } + + if (array_key_exists('AUTO_INCREMENT', $attributes) && $attributes['AUTO_INCREMENT'] === TRUE) + { + $sql .= ' AUTO_INCREMENT'; + } + } + + // don't add a comma on the end of the last field + if (++$current_field_count < count($fields)) + { + $sql .= ','; + } + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Create Table + * + * @access private + * @param string the table name + * @param mixed the fields + * @param mixed primary key(s) + * @param mixed key(s) + * @param boolean should 'IF NOT EXISTS' be added to the SQL + * @return bool + */ + function _create_table($table, $fields, $primary_keys, $keys, $if_not_exists) + { + $sql = 'CREATE TABLE '; + + if ($if_not_exists === TRUE) + { + $sql .= 'IF NOT EXISTS '; + } + + $sql .= $this->db->_escape_identifiers($table)." ("; + + $sql .= $this->_process_fields($fields); + + if (count($primary_keys) > 0) + { + $key_name = $this->db->_protect_identifiers(implode('_', $primary_keys)); + $primary_keys = $this->db->_protect_identifiers($primary_keys); + $sql .= ",\n\tPRIMARY KEY ".$key_name." (" . implode(', ', $primary_keys) . ")"; + } + + if (is_array($keys) && count($keys) > 0) + { + foreach ($keys as $key) + { + if (is_array($key)) + { + $key_name = $this->db->_protect_identifiers(implode('_', $key)); + $key = $this->db->_protect_identifiers($key); + } + else + { + $key_name = $this->db->_protect_identifiers($key); + $key = array($key_name); + } + + $sql .= ",\n\tKEY {$key_name} (" . implode(', ', $key) . ")"; + } + } + + $sql .= "\n) DEFAULT CHARACTER SET {$this->db->char_set} COLLATE {$this->db->dbcollat};"; + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Drop Table + * + * @access private + * @return string + */ + function _drop_table($table) + { + return "DROP TABLE IF EXISTS ".$this->db->_escape_identifiers($table); + } + + // -------------------------------------------------------------------- + + /** + * Alter table query + * + * Generates a platform-specific query so that a table can be altered + * Called by add_column(), drop_column(), and column_alter(), + * + * @access private + * @param string the ALTER type (ADD, DROP, CHANGE) + * @param string the column name + * @param array fields + * @param string the field after which we should add the new field + * @return object + */ + function _alter_table($alter_type, $table, $fields, $after_field = '') + { + $sql = 'ALTER TABLE '.$this->db->_protect_identifiers($table)." $alter_type "; + + // DROP has everything it needs now. + if ($alter_type == 'DROP') + { + return $sql.$this->db->_protect_identifiers($fields); + } + + $sql .= $this->_process_fields($fields); + + if ($after_field != '') + { + $sql .= ' AFTER ' . $this->db->_protect_identifiers($after_field); + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Rename a table + * + * Generates a platform-specific query so that a table can be renamed + * + * @access private + * @param string the old table name + * @param string the new table name + * @return string + */ + function _rename_table($table_name, $new_table_name) + { + $sql = 'ALTER TABLE '.$this->db->_protect_identifiers($table_name)." RENAME TO ".$this->db->_protect_identifiers($new_table_name); + return $sql; + } + +} + +/* End of file mysql_forge.php */ +/* Location: ./system/database/drivers/mysql/mysql_forge.php */ \ No newline at end of file diff --git a/system/database/drivers/mysql/mysql_result.php b/system/database/drivers/mysql/mysql_result.php new file mode 100644 index 0000000..e1a6e93 --- /dev/null +++ b/system/database/drivers/mysql/mysql_result.php @@ -0,0 +1,174 @@ +result_id); + } + + // -------------------------------------------------------------------- + + /** + * Number of fields in the result set + * + * @access public + * @return integer + */ + function num_fields() + { + return @mysql_num_fields($this->result_id); + } + + // -------------------------------------------------------------------- + + /** + * Fetch Field Names + * + * Generates an array of column names + * + * @access public + * @return array + */ + function list_fields() + { + $field_names = array(); + while ($field = mysql_fetch_field($this->result_id)) + { + $field_names[] = $field->name; + } + + return $field_names; + } + + // -------------------------------------------------------------------- + + /** + * Field data + * + * Generates an array of objects containing field meta-data + * + * @access public + * @return array + */ + function field_data() + { + $retval = array(); + while ($field = mysql_fetch_object($this->result_id)) + { + preg_match('/([a-zA-Z]+)(\(\d+\))?/', $field->Type, $matches); + + $type = (array_key_exists(1, $matches)) ? $matches[1] : NULL; + $length = (array_key_exists(2, $matches)) ? preg_replace('/[^\d]/', '', $matches[2]) : NULL; + + $F = new stdClass(); + $F->name = $field->Field; + $F->type = $type; + $F->default = $field->Default; + $F->max_length = $length; + $F->primary_key = ( $field->Key == 'PRI' ? 1 : 0 ); + + $retval[] = $F; + } + + return $retval; + } + + // -------------------------------------------------------------------- + + /** + * Free the result + * + * @return null + */ + function free_result() + { + if (is_resource($this->result_id)) + { + mysql_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 + * + * @access private + * @return array + */ + function _data_seek($n = 0) + { + return mysql_data_seek($this->result_id, $n); + } + + // -------------------------------------------------------------------- + + /** + * Result - associative array + * + * Returns the result set as an array + * + * @access private + * @return array + */ + function _fetch_assoc() + { + return mysql_fetch_assoc($this->result_id); + } + + // -------------------------------------------------------------------- + + /** + * Result - object + * + * Returns the result set as an object + * + * @access private + * @return object + */ + function _fetch_object() + { + return mysql_fetch_object($this->result_id); + } + +} + + +/* End of file mysql_result.php */ +/* Location: ./system/database/drivers/mysql/mysql_result.php */ \ No newline at end of file diff --git a/system/database/drivers/mysql/mysql_utility.php b/system/database/drivers/mysql/mysql_utility.php new file mode 100644 index 0000000..48c4d63 --- /dev/null +++ b/system/database/drivers/mysql/mysql_utility.php @@ -0,0 +1,210 @@ +db->_escape_identifiers($table); + } + + // -------------------------------------------------------------------- + + /** + * Repair table query + * + * Generates a platform-specific query so that a table can be repaired + * + * @access private + * @param string the table name + * @return object + */ + function _repair_table($table) + { + return "REPAIR TABLE ".$this->db->_escape_identifiers($table); + } + + // -------------------------------------------------------------------- + /** + * MySQL Export + * + * @access private + * @param array Preferences + * @return mixed + */ + function _backup($params = array()) + { + if (count($params) == 0) + { + return FALSE; + } + + // Extract the prefs for simplicity + extract($params); + + // Build the output + $output = ''; + foreach ((array)$tables as $table) + { + // Is the table in the "ignore" list? + if (in_array($table, (array)$ignore, TRUE)) + { + continue; + } + + // Get the table schema + $query = $this->db->query("SHOW CREATE TABLE `".$this->db->database.'`.`'.$table.'`'); + + // No result means the table name was invalid + if ($query === FALSE) + { + continue; + } + + // Write out the table schema + $output .= '#'.$newline.'# TABLE STRUCTURE FOR: '.$table.$newline.'#'.$newline.$newline; + + if ($add_drop == TRUE) + { + $output .= 'DROP TABLE IF EXISTS '.$table.';'.$newline.$newline; + } + + $i = 0; + $result = $query->result_array(); + foreach ($result[0] as $val) + { + if ($i++ % 2) + { + $output .= $val.';'.$newline.$newline; + } + } + + // If inserts are not needed we're done... + if ($add_insert == FALSE) + { + continue; + } + + // Grab all the data from the current table + $query = $this->db->query("SELECT * FROM $table"); + + if ($query->num_rows() == 0) + { + continue; + } + + // Fetch the field names and determine if the field is an + // integer type. We use this info to decide whether to + // surround the data with quotes or not + + $i = 0; + $field_str = ''; + $is_int = array(); + while ($field = mysql_fetch_field($query->result_id)) + { + // Most versions of MySQL store timestamp as a string + $is_int[$i] = (in_array( + strtolower(mysql_field_type($query->result_id, $i)), + array('tinyint', 'smallint', 'mediumint', 'int', 'bigint'), //, 'timestamp'), + TRUE) + ) ? TRUE : FALSE; + + // Create a string of field names + $field_str .= '`'.$field->name.'`, '; + $i++; + } + + // Trim off the end comma + $field_str = preg_replace( "/, $/" , "" , $field_str); + + + // Build the insert string + foreach ($query->result_array() as $row) + { + $val_str = ''; + + $i = 0; + foreach ($row as $v) + { + // Is the value NULL? + if ($v === NULL) + { + $val_str .= 'NULL'; + } + else + { + // Escape the data if it's not an integer + if ($is_int[$i] == FALSE) + { + $val_str .= $this->db->escape($v); + } + else + { + $val_str .= $v; + } + } + + // Append a comma + $val_str .= ', '; + $i++; + } + + // Remove the comma at the end of the string + $val_str = preg_replace( "/, $/" , "" , $val_str); + + // Build the INSERT string + $output .= 'INSERT INTO '.$table.' ('.$field_str.') VALUES ('.$val_str.');'.$newline; + } + + $output .= $newline.$newline; + } + + return $output; + } +} + +/* End of file mysql_utility.php */ +/* Location: ./system/database/drivers/mysql/mysql_utility.php */ \ No newline at end of file diff --git a/system/database/drivers/mysqli/index.html b/system/database/drivers/mysqli/index.html new file mode 100644 index 0000000..c942a79 --- /dev/null +++ b/system/database/drivers/mysqli/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/system/database/drivers/mysqli/mysqli_driver.php b/system/database/drivers/mysqli/mysqli_driver.php new file mode 100644 index 0000000..d3200f3 --- /dev/null +++ b/system/database/drivers/mysqli/mysqli_driver.php @@ -0,0 +1,776 @@ +port != '') + { + return @mysqli_connect($this->hostname, $this->username, $this->password, $this->database, $this->port); + } + else + { + return @mysqli_connect($this->hostname, $this->username, $this->password, $this->database); + } + + } + + // -------------------------------------------------------------------- + + /** + * Persistent database connection + * + * @access private called by the base class + * @return resource + */ + function db_pconnect() + { + return $this->db_connect(); + } + + // -------------------------------------------------------------------- + + /** + * Reconnect + * + * Keep / reestablish the db connection if no queries have been + * sent for a length of time exceeding the server's idle timeout + * + * @access public + * @return void + */ + function reconnect() + { + if (mysqli_ping($this->conn_id) === FALSE) + { + $this->conn_id = FALSE; + } + } + + // -------------------------------------------------------------------- + + /** + * Select the database + * + * @access private called by the base class + * @return resource + */ + function db_select() + { + return @mysqli_select_db($this->conn_id, $this->database); + } + + // -------------------------------------------------------------------- + + /** + * Set client character set + * + * @access private + * @param string + * @param string + * @return resource + */ + function _db_set_charset($charset, $collation) + { + if ( ! isset($this->use_set_names)) + { + // mysqli_set_charset() requires MySQL >= 5.0.7, use SET NAMES as fallback + $this->use_set_names = (version_compare(mysqli_get_server_info($this->conn_id), '5.0.7', '>=')) ? FALSE : TRUE; + } + + if ($this->use_set_names === TRUE) + { + return @mysqli_query($this->conn_id, "SET NAMES '".$this->escape_str($charset)."' COLLATE '".$this->escape_str($collation)."'"); + } + else + { + return @mysqli_set_charset($this->conn_id, $charset); + } + } + + // -------------------------------------------------------------------- + + /** + * Version number query string + * + * @access public + * @return string + */ + function _version() + { + return "SELECT version() AS ver"; + } + + // -------------------------------------------------------------------- + + /** + * Execute the query + * + * @access private called by the base class + * @param string an SQL query + * @return resource + */ + function _execute($sql) + { + $sql = $this->_prep_query($sql); + $result = @mysqli_query($this->conn_id, $sql); + return $result; + } + + // -------------------------------------------------------------------- + + /** + * Prep the query + * + * If needed, each database adapter can prep the query string + * + * @access private called by execute() + * @param string an SQL query + * @return string + */ + function _prep_query($sql) + { + // "DELETE FROM TABLE" returns 0 affected rows This hack modifies + // the query so that it returns the number of affected rows + if ($this->delete_hack === TRUE) + { + if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $sql)) + { + $sql = preg_replace("/^\s*DELETE\s+FROM\s+(\S+)\s*$/", "DELETE FROM \\1 WHERE 1=1", $sql); + } + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Begin Transaction + * + * @access public + * @return bool + */ + function trans_begin($test_mode = FALSE) + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + 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) ? TRUE : FALSE; + + $this->simple_query('SET AUTOCOMMIT=0'); + $this->simple_query('START TRANSACTION'); // can also be BEGIN or BEGIN WORK + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Commit Transaction + * + * @access public + * @return bool + */ + function trans_commit() + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + return TRUE; + } + + $this->simple_query('COMMIT'); + $this->simple_query('SET AUTOCOMMIT=1'); + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Rollback Transaction + * + * @access public + * @return bool + */ + function trans_rollback() + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + return TRUE; + } + + $this->simple_query('ROLLBACK'); + $this->simple_query('SET AUTOCOMMIT=1'); + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Escape String + * + * @access public + * @param string + * @param bool whether or not the string will be used in a LIKE condition + * @return string + */ + function escape_str($str, $like = FALSE) + { + if (is_array($str)) + { + foreach ($str as $key => $val) + { + $str[$key] = $this->escape_str($val, $like); + } + + return $str; + } + + if (function_exists('mysqli_real_escape_string') AND is_object($this->conn_id)) + { + $str = mysqli_real_escape_string($this->conn_id, $str); + } + elseif (function_exists('mysql_escape_string')) + { + $str = mysql_escape_string($str); + } + else + { + $str = addslashes($str); + } + + // escape LIKE condition wildcards + if ($like === TRUE) + { + $str = str_replace(array('%', '_'), array('\\%', '\\_'), $str); + } + + return $str; + } + + // -------------------------------------------------------------------- + + /** + * Affected Rows + * + * @access public + * @return integer + */ + function affected_rows() + { + return @mysqli_affected_rows($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * Insert ID + * + * @access public + * @return integer + */ + function insert_id() + { + return @mysqli_insert_id($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * "Count All" query + * + * Generates a platform-specific query string that counts all records in + * the specified database + * + * @access public + * @param string + * @return string + */ + function count_all($table = '') + { + if ($table == '') + { + return 0; + } + + $query = $this->query($this->_count_string . $this->_protect_identifiers('numrows') . " FROM " . $this->_protect_identifiers($table, TRUE, NULL, FALSE)); + + if ($query->num_rows() == 0) + { + return 0; + } + + $row = $query->row(); + $this->_reset_select(); + return (int) $row->numrows; + } + + // -------------------------------------------------------------------- + + /** + * List table query + * + * Generates a platform-specific query string so that the table names can be fetched + * + * @access private + * @param boolean + * @return string + */ + function _list_tables($prefix_limit = FALSE) + { + $sql = "SHOW TABLES FROM ".$this->_escape_char.$this->database.$this->_escape_char; + + if ($prefix_limit !== FALSE AND $this->dbprefix != '') + { + $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 + * + * @access public + * @param string the table name + * @return string + */ + function _list_columns($table = '') + { + return "SHOW COLUMNS FROM ".$this->_protect_identifiers($table, TRUE, NULL, FALSE); + } + + // -------------------------------------------------------------------- + + /** + * Field data query + * + * Generates a platform-specific query so that the column data can be retrieved + * + * @access public + * @param string the table name + * @return object + */ + function _field_data($table) + { + return "DESCRIBE ".$table; + } + + // -------------------------------------------------------------------- + + /** + * The error message string + * + * @access private + * @return string + */ + function _error_message() + { + return mysqli_error($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * The error message number + * + * @access private + * @return integer + */ + function _error_number() + { + return mysqli_errno($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * Escape the SQL Identifiers + * + * This function escapes column and table names + * + * @access private + * @param string + * @return string + */ + function _escape_identifiers($item) + { + if ($this->_escape_char == '') + { + return $item; + } + + foreach ($this->_reserved_identifiers as $id) + { + if (strpos($item, '.'.$id) !== FALSE) + { + $str = $this->_escape_char. str_replace('.', $this->_escape_char.'.', $item); + + // remove duplicates if the user already included the escape + return preg_replace('/['.$this->_escape_char.']+/', $this->_escape_char, $str); + } + } + + if (strpos($item, '.') !== FALSE) + { + $str = $this->_escape_char.str_replace('.', $this->_escape_char.'.'.$this->_escape_char, $item).$this->_escape_char; + } + else + { + $str = $this->_escape_char.$item.$this->_escape_char; + } + + // remove duplicates if the user already included the escape + return preg_replace('/['.$this->_escape_char.']+/', $this->_escape_char, $str); + } + + // -------------------------------------------------------------------- + + /** + * From Tables + * + * This function implicitly groups FROM tables so there is no confusion + * about operator precedence in harmony with SQL standards + * + * @access public + * @param type + * @return type + */ + function _from_tables($tables) + { + if ( ! is_array($tables)) + { + $tables = array($tables); + } + + return '('.implode(', ', $tables).')'; + } + + // -------------------------------------------------------------------- + + /** + * Insert statement + * + * Generates a platform-specific insert string from the supplied data + * + * @access public + * @param string the table name + * @param array the insert keys + * @param array the insert values + * @return string + */ + function _insert($table, $keys, $values) + { + return "INSERT INTO ".$table." (".implode(', ', $keys).") VALUES (".implode(', ', $values).")"; + } + + // -------------------------------------------------------------------- + + /** + * Insert_batch statement + * + * Generates a platform-specific insert string from the supplied data + * + * @access public + * @param string the table name + * @param array the insert keys + * @param array the insert values + * @return string + */ + function _insert_batch($table, $keys, $values) + { + return "INSERT INTO ".$table." (".implode(', ', $keys).") VALUES ".implode(', ', $values); + } + + // -------------------------------------------------------------------- + + + /** + * Replace statement + * + * Generates a platform-specific replace string from the supplied data + * + * @access public + * @param string the table name + * @param array the insert keys + * @param array the insert values + * @return string + */ + function _replace($table, $keys, $values) + { + return "REPLACE INTO ".$table." (".implode(', ', $keys).") VALUES (".implode(', ', $values).")"; + } + + // -------------------------------------------------------------------- + + /** + * Update statement + * + * Generates a platform-specific update string from the supplied data + * + * @access public + * @param string the table name + * @param array the update data + * @param array the where clause + * @param array the orderby clause + * @param array the limit clause + * @return string + */ + function _update($table, $values, $where, $orderby = array(), $limit = FALSE) + { + foreach ($values as $key => $val) + { + $valstr[] = $key." = ".$val; + } + + $limit = ( ! $limit) ? '' : ' LIMIT '.$limit; + + $orderby = (count($orderby) >= 1)?' ORDER BY '.implode(", ", $orderby):''; + + $sql = "UPDATE ".$table." SET ".implode(', ', $valstr); + + $sql .= ($where != '' AND count($where) >=1) ? " WHERE ".implode(" ", $where) : ''; + + $sql .= $orderby.$limit; + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Update_Batch statement + * + * Generates a platform-specific batch update string from the supplied data + * + * @access public + * @param string the table name + * @param array the update data + * @param array the where clause + * @return string + */ + function _update_batch($table, $values, $index, $where = NULL) + { + $ids = array(); + $where = ($where != '' AND count($where) >=1) ? implode(" ", $where).' AND ' : ''; + + foreach ($values as $key => $val) + { + $ids[] = $val[$index]; + + foreach (array_keys($val) as $field) + { + if ($field != $index) + { + $final[$field][] = 'WHEN '.$index.' = '.$val[$index].' THEN '.$val[$field]; + } + } + } + + $sql = "UPDATE ".$table." SET "; + $cases = ''; + + foreach ($final as $k => $v) + { + $cases .= $k.' = CASE '."\n"; + foreach ($v as $row) + { + $cases .= $row."\n"; + } + + $cases .= 'ELSE '.$k.' END, '; + } + + $sql .= substr($cases, 0, -2); + + $sql .= ' WHERE '.$where.$index.' IN ('.implode(',', $ids).')'; + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Truncate statement + * + * Generates a platform-specific truncate string from the supplied data + * If the database does not support the truncate() command + * This function maps to "DELETE FROM table" + * + * @access public + * @param string the table name + * @return string + */ + function _truncate($table) + { + return "TRUNCATE ".$table; + } + + // -------------------------------------------------------------------- + + /** + * Delete statement + * + * Generates a platform-specific delete string from the supplied data + * + * @access public + * @param string the table name + * @param array the where clause + * @param string the limit clause + * @return string + */ + function _delete($table, $where = array(), $like = array(), $limit = FALSE) + { + $conditions = ''; + + if (count($where) > 0 OR count($like) > 0) + { + $conditions = "\nWHERE "; + $conditions .= implode("\n", $this->ar_where); + + if (count($where) > 0 && count($like) > 0) + { + $conditions .= " AND "; + } + $conditions .= implode("\n", $like); + } + + $limit = ( ! $limit) ? '' : ' LIMIT '.$limit; + + return "DELETE FROM ".$table.$conditions.$limit; + } + + // -------------------------------------------------------------------- + + /** + * Limit string + * + * Generates a platform-specific LIMIT clause + * + * @access public + * @param string the sql query string + * @param integer the number of rows to limit the query to + * @param integer the offset value + * @return string + */ + function _limit($sql, $limit, $offset) + { + $sql .= "LIMIT ".$limit; + + if ($offset > 0) + { + $sql .= " OFFSET ".$offset; + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Close DB Connection + * + * @access public + * @param resource + * @return void + */ + function _close($conn_id) + { + @mysqli_close($conn_id); + } + + +} + + +/* End of file mysqli_driver.php */ +/* Location: ./system/database/drivers/mysqli/mysqli_driver.php */ \ No newline at end of file diff --git a/system/database/drivers/mysqli/mysqli_forge.php b/system/database/drivers/mysqli/mysqli_forge.php new file mode 100644 index 0000000..2605494 --- /dev/null +++ b/system/database/drivers/mysqli/mysqli_forge.php @@ -0,0 +1,258 @@ +$attributes) + { + // Numeric field names aren't allowed in databases, so if the key is + // numeric, we know it was assigned by PHP and the developer manually + // entered the field information, so we'll simply add it to the list + if (is_numeric($field)) + { + $sql .= "\n\t$attributes"; + } + else + { + $attributes = array_change_key_case($attributes, CASE_UPPER); + + $sql .= "\n\t".$this->db->_protect_identifiers($field); + + if (array_key_exists('NAME', $attributes)) + { + $sql .= ' '.$this->db->_protect_identifiers($attributes['NAME']).' '; + } + + if (array_key_exists('TYPE', $attributes)) + { + $sql .= ' '.$attributes['TYPE']; + } + + if (array_key_exists('CONSTRAINT', $attributes)) + { + $sql .= '('.$attributes['CONSTRAINT'].')'; + } + + if (array_key_exists('UNSIGNED', $attributes) && $attributes['UNSIGNED'] === TRUE) + { + $sql .= ' UNSIGNED'; + } + + if (array_key_exists('DEFAULT', $attributes)) + { + $sql .= ' DEFAULT \''.$attributes['DEFAULT'].'\''; + } + + if (array_key_exists('NULL', $attributes) && $attributes['NULL'] === TRUE) + { + $sql .= ' NULL'; + } + else + { + $sql .= ' NOT NULL'; + } + + if (array_key_exists('AUTO_INCREMENT', $attributes) && $attributes['AUTO_INCREMENT'] === TRUE) + { + $sql .= ' AUTO_INCREMENT'; + } + } + + // don't add a comma on the end of the last field + if (++$current_field_count < count($fields)) + { + $sql .= ','; + } + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Create Table + * + * @access private + * @param string the table name + * @param mixed the fields + * @param mixed primary key(s) + * @param mixed key(s) + * @param boolean should 'IF NOT EXISTS' be added to the SQL + * @return bool + */ + function _create_table($table, $fields, $primary_keys, $keys, $if_not_exists) + { + $sql = 'CREATE TABLE '; + + if ($if_not_exists === TRUE) + { + $sql .= 'IF NOT EXISTS '; + } + + $sql .= $this->db->_escape_identifiers($table)." ("; + + $sql .= $this->_process_fields($fields); + + if (count($primary_keys) > 0) + { + $key_name = $this->db->_protect_identifiers(implode('_', $primary_keys)); + $primary_keys = $this->db->_protect_identifiers($primary_keys); + $sql .= ",\n\tPRIMARY KEY ".$key_name." (" . implode(', ', $primary_keys) . ")"; + } + + if (is_array($keys) && count($keys) > 0) + { + foreach ($keys as $key) + { + if (is_array($key)) + { + $key_name = $this->db->_protect_identifiers(implode('_', $key)); + $key = $this->db->_protect_identifiers($key); + } + else + { + $key_name = $this->db->_protect_identifiers($key); + $key = array($key_name); + } + + $sql .= ",\n\tKEY {$key_name} (" . implode(', ', $key) . ")"; + } + } + + $sql .= "\n) DEFAULT CHARACTER SET {$this->db->char_set} COLLATE {$this->db->dbcollat};"; + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Drop Table + * + * @access private + * @return string + */ + function _drop_table($table) + { + return "DROP TABLE IF EXISTS ".$this->db->_escape_identifiers($table); + } + + // -------------------------------------------------------------------- + + /** + * Alter table query + * + * Generates a platform-specific query so that a table can be altered + * Called by add_column(), drop_column(), and column_alter(), + * + * @access private + * @param string the ALTER type (ADD, DROP, CHANGE) + * @param string the column name + * @param array fields + * @param string the field after which we should add the new field + * @return object + */ + function _alter_table($alter_type, $table, $fields, $after_field = '') + { + $sql = 'ALTER TABLE '.$this->db->_protect_identifiers($table)." $alter_type "; + + // DROP has everything it needs now. + if ($alter_type == 'DROP') + { + return $sql.$this->db->_protect_identifiers($fields); + } + + $sql .= $this->_process_fields($fields); + + if ($after_field != '') + { + $sql .= ' AFTER ' . $this->db->_protect_identifiers($after_field); + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Rename a table + * + * Generates a platform-specific query so that a table can be renamed + * + * @access private + * @param string the old table name + * @param string the new table name + * @return string + */ + function _rename_table($table_name, $new_table_name) + { + $sql = 'ALTER TABLE '.$this->db->_protect_identifiers($table_name)." RENAME TO ".$this->db->_protect_identifiers($new_table_name); + return $sql; + } + +} + +/* End of file mysqli_forge.php */ +/* Location: ./system/database/drivers/mysqli/mysqli_forge.php */ \ No newline at end of file diff --git a/system/database/drivers/mysqli/mysqli_result.php b/system/database/drivers/mysqli/mysqli_result.php new file mode 100644 index 0000000..124d4e5 --- /dev/null +++ b/system/database/drivers/mysqli/mysqli_result.php @@ -0,0 +1,174 @@ +result_id); + } + + // -------------------------------------------------------------------- + + /** + * Number of fields in the result set + * + * @access public + * @return integer + */ + function num_fields() + { + return @mysqli_num_fields($this->result_id); + } + + // -------------------------------------------------------------------- + + /** + * Fetch Field Names + * + * Generates an array of column names + * + * @access public + * @return array + */ + function list_fields() + { + $field_names = array(); + while ($field = mysqli_fetch_field($this->result_id)) + { + $field_names[] = $field->name; + } + + return $field_names; + } + + // -------------------------------------------------------------------- + + /** + * Field data + * + * Generates an array of objects containing field meta-data + * + * @access public + * @return array + */ + function field_data() + { + $retval = array(); + while ($field = mysqli_fetch_object($this->result_id)) + { + preg_match('/([a-zA-Z]+)(\(\d+\))?/', $field->Type, $matches); + + $type = (array_key_exists(1, $matches)) ? $matches[1] : NULL; + $length = (array_key_exists(2, $matches)) ? preg_replace('/[^\d]/', '', $matches[2]) : NULL; + + $F = new stdClass(); + $F->name = $field->Field; + $F->type = $type; + $F->default = $field->Default; + $F->max_length = $length; + $F->primary_key = ( $field->Key == 'PRI' ? 1 : 0 ); + + $retval[] = $F; + } + + return $retval; + } + + // -------------------------------------------------------------------- + + /** + * Free the result + * + * @return null + */ + function free_result() + { + if (is_object($this->result_id)) + { + mysqli_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 + * + * @access private + * @return array + */ + function _data_seek($n = 0) + { + return mysqli_data_seek($this->result_id, $n); + } + + // -------------------------------------------------------------------- + + /** + * Result - associative array + * + * Returns the result set as an array + * + * @access private + * @return array + */ + function _fetch_assoc() + { + return mysqli_fetch_assoc($this->result_id); + } + + // -------------------------------------------------------------------- + + /** + * Result - object + * + * Returns the result set as an object + * + * @access private + * @return object + */ + function _fetch_object() + { + return mysqli_fetch_object($this->result_id); + } + +} + + +/* End of file mysqli_result.php */ +/* Location: ./system/database/drivers/mysqli/mysqli_result.php */ \ No newline at end of file diff --git a/system/database/drivers/mysqli/mysqli_utility.php b/system/database/drivers/mysqli/mysqli_utility.php new file mode 100644 index 0000000..e17889b --- /dev/null +++ b/system/database/drivers/mysqli/mysqli_utility.php @@ -0,0 +1,87 @@ +db->_escape_identifiers($table); + } + + // -------------------------------------------------------------------- + + /** + * Repair table query + * + * Generates a platform-specific query so that a table can be repaired + * + * @access private + * @param string the table name + * @return object + */ + function _repair_table($table) + { + return "REPAIR TABLE ".$this->db->_escape_identifiers($table); + } + + // -------------------------------------------------------------------- + + /** + * MySQLi Export + * + * @access private + * @param array Preferences + * @return mixed + */ + function _backup($params = array()) + { + // Currently unsupported + return $this->db->display_error('db_unsuported_feature'); + } +} + +/* End of file mysqli_utility.php */ +/* Location: ./system/database/drivers/mysqli/mysqli_utility.php */ \ No newline at end of file diff --git a/system/database/drivers/oci8/index.html b/system/database/drivers/oci8/index.html new file mode 100644 index 0000000..c942a79 --- /dev/null +++ b/system/database/drivers/oci8/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/system/database/drivers/oci8/oci8_driver.php b/system/database/drivers/oci8/oci8_driver.php new file mode 100644 index 0000000..930177e --- /dev/null +++ b/system/database/drivers/oci8/oci8_driver.php @@ -0,0 +1,808 @@ +username, $this->password, $this->hostname, $this->char_set); + } + + // -------------------------------------------------------------------- + + /** + * Persistent database connection + * + * @access private called by the base class + * @return resource + */ + public function db_pconnect() + { + return @oci_pconnect($this->username, $this->password, $this->hostname, $this->char_set); + } + + // -------------------------------------------------------------------- + + /** + * Reconnect + * + * Keep / reestablish the db connection if no queries have been + * sent for a length of time exceeding the server's idle timeout + * + * @access public + * @return void + */ + public function reconnect() + { + // not implemented in oracle + return; + } + + // -------------------------------------------------------------------- + + /** + * Select the database + * + * @access private called by the base class + * @return resource + */ + public function db_select() + { + // Not in Oracle - schemas are actually usernames + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Set client character set + * + * @access public + * @param string + * @param string + * @return resource + */ + public function db_set_charset($charset, $collation) + { + // @todo - add support if needed + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Version number query string + * + * @access protected + * @return string + */ + protected function _version() + { + return oci_server_version($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * Execute the query + * + * @access protected called by the base class + * @param string an SQL query + * @return resource + */ + protected function _execute($sql) + { + // oracle must parse the query before it is run. All of the actions with + // the query are based on the statement id returned by ociparse + $this->stmt_id = FALSE; + $this->_set_stmt_id($sql); + oci_set_prefetch($this->stmt_id, 1000); + return @oci_execute($this->stmt_id, $this->_commit); + } + + /** + * Generate a statement ID + * + * @access private + * @param string an SQL query + * @return none + */ + private function _set_stmt_id($sql) + { + if ( ! is_resource($this->stmt_id)) + { + $this->stmt_id = oci_parse($this->conn_id, $this->_prep_query($sql)); + } + } + + // -------------------------------------------------------------------- + + /** + * Prep the query + * + * If needed, each database adapter can prep the query string + * + * @access private called by execute() + * @param string an SQL query + * @return string + */ + private function _prep_query($sql) + { + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * getCursor. Returns a cursor from the datbase + * + * @access public + * @return cursor id + */ + public function get_cursor() + { + $this->curs_id = oci_new_cursor($this->conn_id); + return $this->curs_id; + } + + // -------------------------------------------------------------------- + + /** + * Stored Procedure. Executes a stored procedure + * + * @access public + * @param package package stored procedure is in + * @param procedure stored procedure to execute + * @param params array of parameters + * @return array + * + * params array keys + * + * KEY OPTIONAL NOTES + * name no the name of the parameter should be in : format + * value no the value of the parameter. If this is an OUT or IN OUT parameter, + * this should be a reference to a variable + * type yes the type of the parameter + * length yes the max size of the parameter + */ + public function stored_procedure($package, $procedure, $params) + { + if ($package == '' OR $procedure == '' OR ! is_array($params)) + { + if ($this->db_debug) + { + log_message('error', 'Invalid query: '.$package.'.'.$procedure); + return $this->display_error('db_invalid_query'); + } + return FALSE; + } + + // build the query string + $sql = "begin $package.$procedure("; + + $have_cursor = FALSE; + foreach ($params as $param) + { + $sql .= $param['name'] . ","; + + if (array_key_exists('type', $param) && ($param['type'] === OCI_B_CURSOR)) + { + $have_cursor = TRUE; + } + } + $sql = trim($sql, ",") . "); end;"; + + $this->stmt_id = FALSE; + $this->_set_stmt_id($sql); + $this->_bind_params($params); + $this->query($sql, FALSE, $have_cursor); + } + + // -------------------------------------------------------------------- + + /** + * Bind parameters + * + * @access private + * @return none + */ + private function _bind_params($params) + { + if ( ! is_array($params) OR ! is_resource($this->stmt_id)) + { + return; + } + + foreach ($params as $param) + { + foreach (array('name', 'value', 'type', 'length') as $val) + { + if ( ! isset($param[$val])) + { + $param[$val] = ''; + } + } + + oci_bind_by_name($this->stmt_id, $param['name'], $param['value'], $param['length'], $param['type']); + } + } + + // -------------------------------------------------------------------- + + /** + * Begin Transaction + * + * @access public + * @return bool + */ + public function trans_begin($test_mode = FALSE) + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + 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) ? TRUE : FALSE; + + $this->_commit = OCI_DEFAULT; + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Commit Transaction + * + * @access public + * @return bool + */ + public function trans_commit() + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + return TRUE; + } + + $ret = oci_commit($this->conn_id); + $this->_commit = OCI_COMMIT_ON_SUCCESS; + return $ret; + } + + // -------------------------------------------------------------------- + + /** + * Rollback Transaction + * + * @access public + * @return bool + */ + public function trans_rollback() + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + return TRUE; + } + + $ret = oci_rollback($this->conn_id); + $this->_commit = OCI_COMMIT_ON_SUCCESS; + return $ret; + } + + // -------------------------------------------------------------------- + + /** + * Escape String + * + * @access public + * @param string + * @param bool 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 = remove_invisible_characters($str); + + // escape LIKE condition wildcards + if ($like === TRUE) + { + $str = 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; + } + + // -------------------------------------------------------------------- + + /** + * Affected Rows + * + * @access public + * @return integer + */ + public function affected_rows() + { + return @oci_num_rows($this->stmt_id); + } + + // -------------------------------------------------------------------- + + /** + * Insert ID + * + * @access public + * @return integer + */ + public function insert_id() + { + // not supported in oracle + return $this->display_error('db_unsupported_function'); + } + + // -------------------------------------------------------------------- + + /** + * "Count All" query + * + * Generates a platform-specific query string that counts all records in + * the specified database + * + * @access public + * @param string + * @return string + */ + public function count_all($table = '') + { + if ($table == '') + { + return 0; + } + + $query = $this->query($this->_count_string . $this->_protect_identifiers('numrows') . " FROM " . $this->_protect_identifiers($table, TRUE, NULL, FALSE)); + + if ($query == FALSE) + { + return 0; + } + + $row = $query->row(); + $this->_reset_select(); + return (int) $row->numrows; + } + + // -------------------------------------------------------------------- + + /** + * Show table query + * + * Generates a platform-specific query string so that the table names can be fetched + * + * @access protected + * @param boolean + * @return string + */ + protected function _list_tables($prefix_limit = FALSE) + { + $sql = "SELECT TABLE_NAME FROM ALL_TABLES"; + + if ($prefix_limit !== FALSE AND $this->dbprefix != '') + { + $sql .= " WHERE 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 + * + * @access protected + * @param string the table name + * @return string + */ + protected function _list_columns($table = '') + { + return "SELECT COLUMN_NAME FROM all_tab_columns WHERE table_name = '$table'"; + } + + // -------------------------------------------------------------------- + + /** + * Field data query + * + * Generates a platform-specific query so that the column data can be retrieved + * + * @access public + * @param string the table name + * @return object + */ + protected function _field_data($table) + { + return "SELECT * FROM ".$table." where rownum = 1"; + } + + // -------------------------------------------------------------------- + + /** + * The error message string + * + * @access protected + * @return string + */ + protected function _error_message() + { + // If the error was during connection, no conn_id should be passed + $error = is_resource($this->conn_id) ? oci_error($this->conn_id) : oci_error(); + return $error['message']; + } + + // -------------------------------------------------------------------- + + /** + * The error message number + * + * @access protected + * @return integer + */ + protected function _error_number() + { + // Same as _error_message() + $error = is_resource($this->conn_id) ? oci_error($this->conn_id) : oci_error(); + return $error['code']; + } + + // -------------------------------------------------------------------- + + /** + * Escape the SQL Identifiers + * + * This function escapes column and table names + * + * @access protected + * @param string + * @return string + */ + protected function _escape_identifiers($item) + { + if ($this->_escape_char == '') + { + return $item; + } + + foreach ($this->_reserved_identifiers as $id) + { + if (strpos($item, '.'.$id) !== FALSE) + { + $str = $this->_escape_char. str_replace('.', $this->_escape_char.'.', $item); + + // remove duplicates if the user already included the escape + return preg_replace('/['.$this->_escape_char.']+/', $this->_escape_char, $str); + } + } + + if (strpos($item, '.') !== FALSE) + { + $str = $this->_escape_char.str_replace('.', $this->_escape_char.'.'.$this->_escape_char, $item).$this->_escape_char; + } + else + { + $str = $this->_escape_char.$item.$this->_escape_char; + } + + // remove duplicates if the user already included the escape + return preg_replace('/['.$this->_escape_char.']+/', $this->_escape_char, $str); + } + + // -------------------------------------------------------------------- + + /** + * From Tables + * + * This function implicitly groups FROM tables so there is no confusion + * about operator precedence in harmony with SQL standards + * + * @access protected + * @param type + * @return type + */ + protected function _from_tables($tables) + { + if ( ! is_array($tables)) + { + $tables = array($tables); + } + + return implode(', ', $tables); + } + + // -------------------------------------------------------------------- + + /** + * Insert statement + * + * Generates a platform-specific insert string from the supplied data + * + * @access public + * @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).")"; + } + + // -------------------------------------------------------------------- + + /** + * Insert_batch statement + * + * Generates a platform-specific insert string from the supplied data + * + * @access protected + * @param string the table name + * @param array the insert keys + * @param array the insert values + * @return string + */ + protected function _insert_batch($table, $keys, $values) + { + $keys = implode(', ', $keys); + $sql = "INSERT ALL\n"; + + for ($i = 0, $c = count($values); $i < $c; $i++) + { + $sql .= ' INTO ' . $table . ' (' . $keys . ') VALUES ' . $values[$i] . "\n"; + } + + $sql .= 'SELECT * FROM dual'; + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Update statement + * + * Generates a platform-specific update string from the supplied data + * + * @access protected + * @param string the table name + * @param array the update data + * @param array the where clause + * @param array the orderby clause + * @param array the limit clause + * @return string + */ + protected function _update($table, $values, $where, $orderby = array(), $limit = FALSE) + { + foreach ($values as $key => $val) + { + $valstr[] = $key." = ".$val; + } + + $limit = ( ! $limit) ? '' : ' LIMIT '.$limit; + + $orderby = (count($orderby) >= 1)?' ORDER BY '.implode(", ", $orderby):''; + + $sql = "UPDATE ".$table." SET ".implode(', ', $valstr); + + $sql .= ($where != '' AND count($where) >=1) ? " WHERE ".implode(" ", $where) : ''; + + $sql .= $orderby.$limit; + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Truncate statement + * + * Generates a platform-specific truncate string from the supplied data + * If the database does not support the truncate() command + * This function maps to "DELETE FROM table" + * + * @access protected + * @param string the table name + * @return string + */ + protected function _truncate($table) + { + return "TRUNCATE TABLE ".$table; + } + + // -------------------------------------------------------------------- + + /** + * Delete statement + * + * Generates a platform-specific delete string from the supplied data + * + * @access protected + * @param string the table name + * @param array the where clause + * @param string the limit clause + * @return string + */ + protected function _delete($table, $where = array(), $like = array(), $limit = FALSE) + { + $conditions = ''; + + if (count($where) > 0 OR count($like) > 0) + { + $conditions = "\nWHERE "; + $conditions .= implode("\n", $this->ar_where); + + if (count($where) > 0 && count($like) > 0) + { + $conditions .= " AND "; + } + $conditions .= implode("\n", $like); + } + + $limit = ( ! $limit) ? '' : ' LIMIT '.$limit; + + return "DELETE FROM ".$table.$conditions.$limit; + } + + // -------------------------------------------------------------------- + + /** + * Limit string + * + * Generates a platform-specific LIMIT clause + * + * @access protected + * @param string the sql query string + * @param integer the number of rows to limit the query to + * @param integer the offset value + * @return string + */ + protected function _limit($sql, $limit, $offset) + { + $limit = $offset + $limit; + $newsql = "SELECT * FROM (select inner_query.*, rownum rnum FROM ($sql) inner_query WHERE rownum < $limit)"; + + if ($offset != 0) + { + $newsql .= " WHERE rnum >= $offset"; + } + + // remember that we used limits + $this->limit_used = TRUE; + + return $newsql; + } + + // -------------------------------------------------------------------- + + /** + * Close DB Connection + * + * @access protected + * @param resource + * @return void + */ + protected function _close($conn_id) + { + @oci_close($conn_id); + } + + +} + + + +/* End of file oci8_driver.php */ +/* Location: ./system/database/drivers/oci8/oci8_driver.php */ diff --git a/system/database/drivers/oci8/oci8_forge.php b/system/database/drivers/oci8/oci8_forge.php new file mode 100644 index 0000000..3cd1758 --- /dev/null +++ b/system/database/drivers/oci8/oci8_forge.php @@ -0,0 +1,248 @@ +db->_escape_identifiers($table)." ("; + $current_field_count = 0; + + foreach ($fields as $field=>$attributes) + { + // Numeric field names aren't allowed in databases, so if the key is + // numeric, we know it was assigned by PHP and the developer manually + // entered the field information, so we'll simply add it to the list + if (is_numeric($field)) + { + $sql .= "\n\t$attributes"; + } + else + { + $attributes = array_change_key_case($attributes, CASE_UPPER); + + $sql .= "\n\t".$this->db->_protect_identifiers($field); + + $sql .= ' '.$attributes['TYPE']; + + if (array_key_exists('CONSTRAINT', $attributes)) + { + $sql .= '('.$attributes['CONSTRAINT'].')'; + } + + if (array_key_exists('UNSIGNED', $attributes) && $attributes['UNSIGNED'] === TRUE) + { + $sql .= ' UNSIGNED'; + } + + if (array_key_exists('DEFAULT', $attributes)) + { + $sql .= ' DEFAULT \''.$attributes['DEFAULT'].'\''; + } + + if (array_key_exists('NULL', $attributes) && $attributes['NULL'] === TRUE) + { + $sql .= ' NULL'; + } + else + { + $sql .= ' NOT NULL'; + } + + if (array_key_exists('AUTO_INCREMENT', $attributes) && $attributes['AUTO_INCREMENT'] === TRUE) + { + $sql .= ' AUTO_INCREMENT'; + } + } + + // don't add a comma on the end of the last field + if (++$current_field_count < count($fields)) + { + $sql .= ','; + } + } + + if (count($primary_keys) > 0) + { + $primary_keys = $this->db->_protect_identifiers($primary_keys); + $sql .= ",\n\tPRIMARY KEY (" . implode(', ', $primary_keys) . ")"; + } + + if (is_array($keys) && count($keys) > 0) + { + foreach ($keys as $key) + { + if (is_array($key)) + { + $key = $this->db->_protect_identifiers($key); + } + else + { + $key = array($this->db->_protect_identifiers($key)); + } + + $sql .= ",\n\tUNIQUE COLUMNS (" . implode(', ', $key) . ")"; + } + } + + $sql .= "\n)"; + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Drop Table + * + * @access private + * @return bool + */ + function _drop_table($table) + { + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Alter table query + * + * Generates a platform-specific query so that a table can be altered + * Called by add_column(), drop_column(), and column_alter(), + * + * @access private + * @param string the ALTER type (ADD, DROP, CHANGE) + * @param string the column name + * @param string the table name + * @param string the column definition + * @param string the default value + * @param boolean should 'NOT NULL' be added + * @param string the field after which we should add the new field + * @return object + */ + function _alter_table($alter_type, $table, $column_name, $column_definition = '', $default_value = '', $null = '', $after_field = '') + { + $sql = 'ALTER TABLE '.$this->db->_protect_identifiers($table)." $alter_type ".$this->db->_protect_identifiers($column_name); + + // DROP has everything it needs now. + if ($alter_type == 'DROP') + { + return $sql; + } + + $sql .= " $column_definition"; + + if ($default_value != '') + { + $sql .= " DEFAULT \"$default_value\""; + } + + if ($null === NULL) + { + $sql .= ' NULL'; + } + else + { + $sql .= ' NOT NULL'; + } + + if ($after_field != '') + { + $sql .= ' AFTER ' . $this->db->_protect_identifiers($after_field); + } + + return $sql; + + } + + // -------------------------------------------------------------------- + + /** + * Rename a table + * + * Generates a platform-specific query so that a table can be renamed + * + * @access private + * @param string the old table name + * @param string the new table name + * @return string + */ + function _rename_table($table_name, $new_table_name) + { + $sql = 'ALTER TABLE '.$this->db->_protect_identifiers($table_name)." RENAME TO ".$this->db->_protect_identifiers($new_table_name); + return $sql; + } + + +} + +/* End of file oci8_forge.php */ +/* Location: ./system/database/drivers/oci8/oci8_forge.php */ \ No newline at end of file diff --git a/system/database/drivers/oci8/oci8_result.php b/system/database/drivers/oci8/oci8_result.php new file mode 100644 index 0000000..3421278 --- /dev/null +++ b/system/database/drivers/oci8/oci8_result.php @@ -0,0 +1,217 @@ +num_rows === 0 && count($this->result_array()) > 0) + { + $this->num_rows = count($this->result_array()); + @oci_execute($this->stmt_id); + + if ($this->curs_id) + { + @oci_execute($this->curs_id); + } + } + + return $this->num_rows; + } + + // -------------------------------------------------------------------- + + /** + * Number of fields in the result set + * + * @access public + * @return integer + */ + public function num_fields() + { + $count = @oci_num_fields($this->stmt_id); + + // if we used a limit we subtract it + if ($this->limit_used) + { + $count = $count - 1; + } + + return $count; + } + + // -------------------------------------------------------------------- + + /** + * Fetch Field Names + * + * Generates an array of column names + * + * @access public + * @return array + */ + public function list_fields() + { + $field_names = array(); + for ($c = 1, $fieldCount = $this->num_fields(); $c <= $fieldCount; $c++) + { + $field_names[] = oci_field_name($this->stmt_id, $c); + } + return $field_names; + } + + // -------------------------------------------------------------------- + + /** + * Field data + * + * Generates an array of objects containing field meta-data + * + * @access public + * @return array + */ + public function field_data() + { + $retval = array(); + for ($c = 1, $fieldCount = $this->num_fields(); $c <= $fieldCount; $c++) + { + $F = new stdClass(); + $F->name = oci_field_name($this->stmt_id, $c); + $F->type = oci_field_type($this->stmt_id, $c); + $F->max_length = oci_field_size($this->stmt_id, $c); + + $retval[] = $F; + } + + return $retval; + } + + // -------------------------------------------------------------------- + + /** + * Free the result + * + * @return null + */ + public function free_result() + { + if (is_resource($this->result_id)) + { + oci_free_statement($this->result_id); + $this->result_id = FALSE; + } + } + + // -------------------------------------------------------------------- + + /** + * Result - associative array + * + * Returns the result set as an array + * + * @access protected + * @return array + */ + protected function _fetch_assoc() + { + $id = ($this->curs_id) ? $this->curs_id : $this->stmt_id; + return oci_fetch_assoc($id); + } + + // -------------------------------------------------------------------- + + /** + * Result - object + * + * Returns the result set as an object + * + * @access protected + * @return object + */ + protected function _fetch_object() + { + $id = ($this->curs_id) ? $this->curs_id : $this->stmt_id; + return @oci_fetch_object($id); + } + + // -------------------------------------------------------------------- + + /** + * Query result. "array" version. + * + * @access public + * @return array + */ + public function result_array() + { + if (count($this->result_array) > 0) + { + return $this->result_array; + } + + $row = NULL; + while ($row = $this->_fetch_assoc()) + { + $this->result_array[] = $row; + } + + return $this->result_array; + } + + // -------------------------------------------------------------------- + + /** + * 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 + * + * @access protected + * @return array + */ + protected function _data_seek($n = 0) + { + return FALSE; // Not needed + } + +} + + +/* End of file oci8_result.php */ +/* Location: ./system/database/drivers/oci8/oci8_result.php */ diff --git a/system/database/drivers/oci8/oci8_utility.php b/system/database/drivers/oci8/oci8_utility.php new file mode 100644 index 0000000..854b467 --- /dev/null +++ b/system/database/drivers/oci8/oci8_utility.php @@ -0,0 +1,87 @@ +db->display_error('db_unsuported_feature'); + } +} + +/* End of file oci8_utility.php */ +/* Location: ./system/database/drivers/oci8/oci8_utility.php */ \ No newline at end of file diff --git a/system/database/drivers/odbc/index.html b/system/database/drivers/odbc/index.html new file mode 100644 index 0000000..c942a79 --- /dev/null +++ b/system/database/drivers/odbc/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/system/database/drivers/odbc/odbc_driver.php b/system/database/drivers/odbc/odbc_driver.php new file mode 100644 index 0000000..bcd7937 --- /dev/null +++ b/system/database/drivers/odbc/odbc_driver.php @@ -0,0 +1,637 @@ +_random_keyword = ' RND('.time().')'; // database specific random keyword + } + + /** + * Non-persistent database connection + * + * @access private called by the base class + * @return resource + */ + function db_connect() + { + return @odbc_connect($this->hostname, $this->username, $this->password); + } + + // -------------------------------------------------------------------- + + /** + * Persistent database connection + * + * @access private called by the base class + * @return resource + */ + function db_pconnect() + { + return @odbc_pconnect($this->hostname, $this->username, $this->password); + } + + // -------------------------------------------------------------------- + + /** + * Reconnect + * + * Keep / reestablish the db connection if no queries have been + * sent for a length of time exceeding the server's idle timeout + * + * @access public + * @return void + */ + function reconnect() + { + // not implemented in odbc + } + + // -------------------------------------------------------------------- + + /** + * Select the database + * + * @access private called by the base class + * @return resource + */ + function db_select() + { + // Not needed for ODBC + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Set client character set + * + * @access public + * @param string + * @param string + * @return resource + */ + function db_set_charset($charset, $collation) + { + // @todo - add support if needed + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Version number query string + * + * @access public + * @return string + */ + function _version() + { + return "SELECT version() AS ver"; + } + + // -------------------------------------------------------------------- + + /** + * Execute the query + * + * @access private called by the base class + * @param string an SQL query + * @return resource + */ + function _execute($sql) + { + $sql = $this->_prep_query($sql); + return @odbc_exec($this->conn_id, $sql); + } + + // -------------------------------------------------------------------- + + /** + * Prep the query + * + * If needed, each database adapter can prep the query string + * + * @access private called by execute() + * @param string an SQL query + * @return string + */ + function _prep_query($sql) + { + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Begin Transaction + * + * @access public + * @return bool + */ + function trans_begin($test_mode = FALSE) + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + 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) ? TRUE : FALSE; + + return odbc_autocommit($this->conn_id, FALSE); + } + + // -------------------------------------------------------------------- + + /** + * Commit Transaction + * + * @access public + * @return bool + */ + function trans_commit() + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + return TRUE; + } + + $ret = odbc_commit($this->conn_id); + odbc_autocommit($this->conn_id, TRUE); + return $ret; + } + + // -------------------------------------------------------------------- + + /** + * Rollback Transaction + * + * @access public + * @return bool + */ + function trans_rollback() + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + return TRUE; + } + + $ret = odbc_rollback($this->conn_id); + odbc_autocommit($this->conn_id, TRUE); + return $ret; + } + + // -------------------------------------------------------------------- + + /** + * Escape String + * + * @access public + * @param string + * @param bool whether or not the string will be used in a LIKE condition + * @return string + */ + function escape_str($str, $like = FALSE) + { + if (is_array($str)) + { + foreach ($str as $key => $val) + { + $str[$key] = $this->escape_str($val, $like); + } + + return $str; + } + + // ODBC doesn't require escaping + $str = remove_invisible_characters($str); + + // escape LIKE condition wildcards + if ($like === TRUE) + { + $str = 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; + } + + // -------------------------------------------------------------------- + + /** + * Affected Rows + * + * @access public + * @return integer + */ + function affected_rows() + { + return @odbc_num_rows($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * Insert ID + * + * @access public + * @return integer + */ + function insert_id() + { + return @odbc_insert_id($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * "Count All" query + * + * Generates a platform-specific query string that counts all records in + * the specified database + * + * @access public + * @param string + * @return string + */ + function count_all($table = '') + { + if ($table == '') + { + return 0; + } + + $query = $this->query($this->_count_string . $this->_protect_identifiers('numrows') . " FROM " . $this->_protect_identifiers($table, TRUE, NULL, FALSE)); + + if ($query->num_rows() == 0) + { + return 0; + } + + $row = $query->row(); + $this->_reset_select(); + return (int) $row->numrows; + } + + // -------------------------------------------------------------------- + + /** + * Show table query + * + * Generates a platform-specific query string so that the table names can be fetched + * + * @access private + * @param boolean + * @return string + */ + function _list_tables($prefix_limit = FALSE) + { + $sql = "SHOW TABLES FROM `".$this->database."`"; + + if ($prefix_limit !== FALSE AND $this->dbprefix != '') + { + //$sql .= " LIKE '".$this->escape_like_str($this->dbprefix)."%' ".sprintf($this->_like_escape_str, $this->_like_escape_chr); + return FALSE; // not currently supported + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Show column query + * + * Generates a platform-specific query string so that the column names can be fetched + * + * @access public + * @param string the table name + * @return string + */ + function _list_columns($table = '') + { + return "SHOW COLUMNS FROM ".$table; + } + + // -------------------------------------------------------------------- + + /** + * Field data query + * + * Generates a platform-specific query so that the column data can be retrieved + * + * @access public + * @param string the table name + * @return object + */ + function _field_data($table) + { + return "SELECT TOP 1 FROM ".$table; + } + + // -------------------------------------------------------------------- + + /** + * The error message string + * + * @access private + * @return string + */ + function _error_message() + { + return odbc_errormsg($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * The error message number + * + * @access private + * @return integer + */ + function _error_number() + { + return odbc_error($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * Escape the SQL Identifiers + * + * This function escapes column and table names + * + * @access private + * @param string + * @return string + */ + function _escape_identifiers($item) + { + if ($this->_escape_char == '') + { + return $item; + } + + foreach ($this->_reserved_identifiers as $id) + { + if (strpos($item, '.'.$id) !== FALSE) + { + $str = $this->_escape_char. str_replace('.', $this->_escape_char.'.', $item); + + // remove duplicates if the user already included the escape + return preg_replace('/['.$this->_escape_char.']+/', $this->_escape_char, $str); + } + } + + if (strpos($item, '.') !== FALSE) + { + $str = $this->_escape_char.str_replace('.', $this->_escape_char.'.'.$this->_escape_char, $item).$this->_escape_char; + } + else + { + $str = $this->_escape_char.$item.$this->_escape_char; + } + + // remove duplicates if the user already included the escape + return preg_replace('/['.$this->_escape_char.']+/', $this->_escape_char, $str); + } + + // -------------------------------------------------------------------- + + /** + * From Tables + * + * This function implicitly groups FROM tables so there is no confusion + * about operator precedence in harmony with SQL standards + * + * @access public + * @param type + * @return type + */ + function _from_tables($tables) + { + if ( ! is_array($tables)) + { + $tables = array($tables); + } + + return '('.implode(', ', $tables).')'; + } + + // -------------------------------------------------------------------- + + /** + * Insert statement + * + * Generates a platform-specific insert string from the supplied data + * + * @access public + * @param string the table name + * @param array the insert keys + * @param array the insert values + * @return string + */ + function _insert($table, $keys, $values) + { + return "INSERT INTO ".$table." (".implode(', ', $keys).") VALUES (".implode(', ', $values).")"; + } + + // -------------------------------------------------------------------- + + /** + * Update statement + * + * Generates a platform-specific update string from the supplied data + * + * @access public + * @param string the table name + * @param array the update data + * @param array the where clause + * @param array the orderby clause + * @param array the limit clause + * @return string + */ + function _update($table, $values, $where, $orderby = array(), $limit = FALSE) + { + foreach ($values as $key => $val) + { + $valstr[] = $key." = ".$val; + } + + $limit = ( ! $limit) ? '' : ' LIMIT '.$limit; + + $orderby = (count($orderby) >= 1)?' ORDER BY '.implode(", ", $orderby):''; + + $sql = "UPDATE ".$table." SET ".implode(', ', $valstr); + + $sql .= ($where != '' AND count($where) >=1) ? " WHERE ".implode(" ", $where) : ''; + + $sql .= $orderby.$limit; + + return $sql; + } + + + // -------------------------------------------------------------------- + + /** + * Truncate statement + * + * Generates a platform-specific truncate string from the supplied data + * If the database does not support the truncate() command + * This function maps to "DELETE FROM table" + * + * @access public + * @param string the table name + * @return string + */ + function _truncate($table) + { + return $this->_delete($table); + } + + // -------------------------------------------------------------------- + + /** + * Delete statement + * + * Generates a platform-specific delete string from the supplied data + * + * @access public + * @param string the table name + * @param array the where clause + * @param string the limit clause + * @return string + */ + function _delete($table, $where = array(), $like = array(), $limit = FALSE) + { + $conditions = ''; + + if (count($where) > 0 OR count($like) > 0) + { + $conditions = "\nWHERE "; + $conditions .= implode("\n", $this->ar_where); + + if (count($where) > 0 && count($like) > 0) + { + $conditions .= " AND "; + } + $conditions .= implode("\n", $like); + } + + $limit = ( ! $limit) ? '' : ' LIMIT '.$limit; + + return "DELETE FROM ".$table.$conditions.$limit; + } + + // -------------------------------------------------------------------- + + /** + * Limit string + * + * Generates a platform-specific LIMIT clause + * + * @access public + * @param string the sql query string + * @param integer the number of rows to limit the query to + * @param integer the offset value + * @return string + */ + function _limit($sql, $limit, $offset) + { + // Does ODBC doesn't use the LIMIT clause? + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Close DB Connection + * + * @access public + * @param resource + * @return void + */ + function _close($conn_id) + { + @odbc_close($conn_id); + } + + +} + + + +/* End of file odbc_driver.php */ +/* Location: ./system/database/drivers/odbc/odbc_driver.php */ \ No newline at end of file diff --git a/system/database/drivers/odbc/odbc_forge.php b/system/database/drivers/odbc/odbc_forge.php new file mode 100644 index 0000000..3ec86b4 --- /dev/null +++ b/system/database/drivers/odbc/odbc_forge.php @@ -0,0 +1,266 @@ +db->db_debug) + { + return $this->db->display_error('db_unsuported_feature'); + } + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Drop database + * + * @access private + * @param string the database name + * @return bool + */ + function _drop_database($name) + { + // ODBC has no "drop database" command since it's + // designed to connect to an existing database + if ($this->db->db_debug) + { + return $this->db->display_error('db_unsuported_feature'); + } + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Create Table + * + * @access private + * @param string the table name + * @param array the fields + * @param mixed primary key(s) + * @param mixed key(s) + * @param boolean should 'IF NOT EXISTS' be added to the SQL + * @return bool + */ + function _create_table($table, $fields, $primary_keys, $keys, $if_not_exists) + { + $sql = 'CREATE TABLE '; + + if ($if_not_exists === TRUE) + { + $sql .= 'IF NOT EXISTS '; + } + + $sql .= $this->db->_escape_identifiers($table)." ("; + $current_field_count = 0; + + foreach ($fields as $field=>$attributes) + { + // Numeric field names aren't allowed in databases, so if the key is + // numeric, we know it was assigned by PHP and the developer manually + // entered the field information, so we'll simply add it to the list + if (is_numeric($field)) + { + $sql .= "\n\t$attributes"; + } + else + { + $attributes = array_change_key_case($attributes, CASE_UPPER); + + $sql .= "\n\t".$this->db->_protect_identifiers($field); + + $sql .= ' '.$attributes['TYPE']; + + if (array_key_exists('CONSTRAINT', $attributes)) + { + $sql .= '('.$attributes['CONSTRAINT'].')'; + } + + if (array_key_exists('UNSIGNED', $attributes) && $attributes['UNSIGNED'] === TRUE) + { + $sql .= ' UNSIGNED'; + } + + if (array_key_exists('DEFAULT', $attributes)) + { + $sql .= ' DEFAULT \''.$attributes['DEFAULT'].'\''; + } + + if (array_key_exists('NULL', $attributes) && $attributes['NULL'] === TRUE) + { + $sql .= ' NULL'; + } + else + { + $sql .= ' NOT NULL'; + } + + if (array_key_exists('AUTO_INCREMENT', $attributes) && $attributes['AUTO_INCREMENT'] === TRUE) + { + $sql .= ' AUTO_INCREMENT'; + } + } + + // don't add a comma on the end of the last field + if (++$current_field_count < count($fields)) + { + $sql .= ','; + } + } + + if (count($primary_keys) > 0) + { + $primary_keys = $this->db->_protect_identifiers($primary_keys); + $sql .= ",\n\tPRIMARY KEY (" . implode(', ', $primary_keys) . ")"; + } + + if (is_array($keys) && count($keys) > 0) + { + foreach ($keys as $key) + { + if (is_array($key)) + { + $key = $this->db->_protect_identifiers($key); + } + else + { + $key = array($this->db->_protect_identifiers($key)); + } + + $sql .= ",\n\tFOREIGN KEY (" . implode(', ', $key) . ")"; + } + } + + $sql .= "\n)"; + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Drop Table + * + * @access private + * @return bool + */ + function _drop_table($table) + { + // Not a supported ODBC feature + if ($this->db->db_debug) + { + return $this->db->display_error('db_unsuported_feature'); + } + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Alter table query + * + * Generates a platform-specific query so that a table can be altered + * Called by add_column(), drop_column(), and column_alter(), + * + * @access private + * @param string the ALTER type (ADD, DROP, CHANGE) + * @param string the column name + * @param string the table name + * @param string the column definition + * @param string the default value + * @param boolean should 'NOT NULL' be added + * @param string the field after which we should add the new field + * @return object + */ + function _alter_table($alter_type, $table, $column_name, $column_definition = '', $default_value = '', $null = '', $after_field = '') + { + $sql = 'ALTER TABLE '.$this->db->_protect_identifiers($table)." $alter_type ".$this->db->_protect_identifiers($column_name); + + // DROP has everything it needs now. + if ($alter_type == 'DROP') + { + return $sql; + } + + $sql .= " $column_definition"; + + if ($default_value != '') + { + $sql .= " DEFAULT \"$default_value\""; + } + + if ($null === NULL) + { + $sql .= ' NULL'; + } + else + { + $sql .= ' NOT NULL'; + } + + if ($after_field != '') + { + $sql .= ' AFTER ' . $this->db->_protect_identifiers($after_field); + } + + return $sql; + + } + + + // -------------------------------------------------------------------- + + /** + * Rename a table + * + * Generates a platform-specific query so that a table can be renamed + * + * @access private + * @param string the old table name + * @param string the new table name + * @return string + */ + function _rename_table($table_name, $new_table_name) + { + $sql = 'ALTER TABLE '.$this->db->_protect_identifiers($table_name)." RENAME TO ".$this->db->_protect_identifiers($new_table_name); + return $sql; + } + + +} + +/* End of file odbc_forge.php */ +/* Location: ./system/database/drivers/odbc/odbc_forge.php */ \ No newline at end of file diff --git a/system/database/drivers/odbc/odbc_result.php b/system/database/drivers/odbc/odbc_result.php new file mode 100644 index 0000000..5d64a46 --- /dev/null +++ b/system/database/drivers/odbc/odbc_result.php @@ -0,0 +1,228 @@ +result_id); + } + + // -------------------------------------------------------------------- + + /** + * Number of fields in the result set + * + * @access public + * @return integer + */ + function num_fields() + { + return @odbc_num_fields($this->result_id); + } + + // -------------------------------------------------------------------- + + /** + * Fetch Field Names + * + * Generates an array of column names + * + * @access public + * @return array + */ + function list_fields() + { + $field_names = array(); + for ($i = 0; $i < $this->num_fields(); $i++) + { + $field_names[] = odbc_field_name($this->result_id, $i); + } + + return $field_names; + } + + // -------------------------------------------------------------------- + + /** + * Field data + * + * Generates an array of objects containing field meta-data + * + * @access public + * @return array + */ + function field_data() + { + $retval = array(); + for ($i = 0; $i < $this->num_fields(); $i++) + { + $F = new stdClass(); + $F->name = odbc_field_name($this->result_id, $i); + $F->type = odbc_field_type($this->result_id, $i); + $F->max_length = odbc_field_len($this->result_id, $i); + $F->primary_key = 0; + $F->default = ''; + + $retval[] = $F; + } + + return $retval; + } + + // -------------------------------------------------------------------- + + /** + * Free the result + * + * @return null + */ + function free_result() + { + if (is_resource($this->result_id)) + { + odbc_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 + * + * @access private + * @return array + */ + function _data_seek($n = 0) + { + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Result - associative array + * + * Returns the result set as an array + * + * @access private + * @return array + */ + function _fetch_assoc() + { + if (function_exists('odbc_fetch_object')) + { + return odbc_fetch_array($this->result_id); + } + else + { + return $this->_odbc_fetch_array($this->result_id); + } + } + + // -------------------------------------------------------------------- + + /** + * Result - object + * + * Returns the result set as an object + * + * @access private + * @return object + */ + function _fetch_object() + { + if (function_exists('odbc_fetch_object')) + { + return odbc_fetch_object($this->result_id); + } + else + { + return $this->_odbc_fetch_object($this->result_id); + } + } + + + /** + * Result - object + * + * subsititutes the odbc_fetch_object function when + * not available (odbc_fetch_object requires unixODBC) + * + * @access private + * @return object + */ + function _odbc_fetch_object(& $odbc_result) { + $rs = array(); + $rs_obj = FALSE; + if (odbc_fetch_into($odbc_result, $rs)) { + foreach ($rs as $k=>$v) { + $field_name= odbc_field_name($odbc_result, $k+1); + $rs_obj->$field_name = $v; + } + } + return $rs_obj; + } + + + /** + * Result - array + * + * subsititutes the odbc_fetch_array function when + * not available (odbc_fetch_array requires unixODBC) + * + * @access private + * @return array + */ + function _odbc_fetch_array(& $odbc_result) { + $rs = array(); + $rs_assoc = FALSE; + if (odbc_fetch_into($odbc_result, $rs)) { + $rs_assoc=array(); + foreach ($rs as $k=>$v) { + $field_name= odbc_field_name($odbc_result, $k+1); + $rs_assoc[$field_name] = $v; + } + } + return $rs_assoc; + } + +} + + +/* End of file odbc_result.php */ +/* Location: ./system/database/drivers/odbc/odbc_result.php */ \ No newline at end of file diff --git a/system/database/drivers/odbc/odbc_utility.php b/system/database/drivers/odbc/odbc_utility.php new file mode 100644 index 0000000..d335bed --- /dev/null +++ b/system/database/drivers/odbc/odbc_utility.php @@ -0,0 +1,103 @@ +db->db_debug) + { + return $this->db->display_error('db_unsuported_feature'); + } + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Optimize table query + * + * Generates a platform-specific query so that a table can be optimized + * + * @access private + * @param string the table name + * @return object + */ + function _optimize_table($table) + { + // Not a supported ODBC feature + if ($this->db->db_debug) + { + return $this->db->display_error('db_unsuported_feature'); + } + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Repair table query + * + * Generates a platform-specific query so that a table can be repaired + * + * @access private + * @param string the table name + * @return object + */ + function _repair_table($table) + { + // Not a supported ODBC feature + if ($this->db->db_debug) + { + return $this->db->display_error('db_unsuported_feature'); + } + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * ODBC Export + * + * @access private + * @param array Preferences + * @return mixed + */ + function _backup($params = array()) + { + // Currently unsupported + return $this->db->display_error('db_unsuported_feature'); + } + +} + +/* End of file odbc_utility.php */ +/* Location: ./system/database/drivers/odbc/odbc_utility.php */ \ No newline at end of file diff --git a/system/database/drivers/pdo/index.html b/system/database/drivers/pdo/index.html new file mode 100644 index 0000000..c942a79 --- /dev/null +++ b/system/database/drivers/pdo/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/system/database/drivers/pdo/pdo_driver.php b/system/database/drivers/pdo/pdo_driver.php new file mode 100644 index 0000000..1ecc90a --- /dev/null +++ b/system/database/drivers/pdo/pdo_driver.php @@ -0,0 +1,812 @@ +hostname, 'mysql') !== FALSE) + { + $this->_like_escape_str = ''; + $this->_like_escape_chr = ''; + + //Prior to this version, the charset can't be set in the dsn + if(is_php('5.3.6')) + { + $this->hostname .= ";charset={$this->char_set}"; + } + + //Set the charset with the connection options + $this->options['PDO::MYSQL_ATTR_INIT_COMMAND'] = "SET NAMES {$this->char_set}"; + } + elseif (strpos($this->hostname, 'odbc') !== FALSE) + { + $this->_like_escape_str = " {escape '%s'} "; + $this->_like_escape_chr = '!'; + } + else + { + $this->_like_escape_str = " ESCAPE '%s' "; + $this->_like_escape_chr = '!'; + } + + empty($this->database) OR $this->hostname .= ';dbname='.$this->database; + + $this->trans_enabled = FALSE; + + $this->_random_keyword = ' RND('.time().')'; // database specific random keyword + } + + /** + * Non-persistent database connection + * + * @access private called by the base class + * @return resource + */ + function db_connect() + { + $this->options['PDO::ATTR_ERRMODE'] = PDO::ERRMODE_SILENT; + + return new PDO($this->hostname, $this->username, $this->password, $this->options); + } + + // -------------------------------------------------------------------- + + /** + * Persistent database connection + * + * @access private called by the base class + * @return resource + */ + function db_pconnect() + { + $this->options['PDO::ATTR_ERRMODE'] = PDO::ERRMODE_SILENT; + $this->options['PDO::ATTR_PERSISTENT'] = TRUE; + + return new PDO($this->hostname, $this->username, $this->password, $this->options); + } + + // -------------------------------------------------------------------- + + /** + * Reconnect + * + * Keep / reestablish the db connection if no queries have been + * sent for a length of time exceeding the server's idle timeout + * + * @access public + * @return void + */ + function reconnect() + { + if ($this->db->db_debug) + { + return $this->db->display_error('db_unsuported_feature'); + } + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Select the database + * + * @access private called by the base class + * @return resource + */ + function db_select() + { + // Not needed for PDO + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Set client character set + * + * @access public + * @param string + * @param string + * @return resource + */ + function db_set_charset($charset, $collation) + { + // @todo - add support if needed + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Version number query string + * + * @access public + * @return string + */ + function _version() + { + return $this->conn_id->getAttribute(PDO::ATTR_CLIENT_VERSION); + } + + // -------------------------------------------------------------------- + + /** + * Execute the query + * + * @access private called by the base class + * @param string an SQL query + * @return object + */ + function _execute($sql) + { + $sql = $this->_prep_query($sql); + $result_id = $this->conn_id->prepare($sql); + $result_id->execute(); + + if (is_object($result_id)) + { + if (is_numeric(stripos($sql, 'SELECT'))) + { + $this->affect_rows = count($result_id->fetchAll()); + $result_id->execute(); + } + else + { + $this->affect_rows = $result_id->rowCount(); + } + } + else + { + $this->affect_rows = 0; + } + + return $result_id; + } + + // -------------------------------------------------------------------- + + /** + * Prep the query + * + * If needed, each database adapter can prep the query string + * + * @access private called by execute() + * @param string an SQL query + * @return string + */ + function _prep_query($sql) + { + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Begin Transaction + * + * @access public + * @return bool + */ + function trans_begin($test_mode = FALSE) + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + 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 = (bool) ($test_mode === TRUE); + + return $this->conn_id->beginTransaction(); + } + + // -------------------------------------------------------------------- + + /** + * Commit Transaction + * + * @access public + * @return bool + */ + function trans_commit() + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + return TRUE; + } + + $ret = $this->conn->commit(); + return $ret; + } + + // -------------------------------------------------------------------- + + /** + * Rollback Transaction + * + * @access public + * @return bool + */ + function trans_rollback() + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + return TRUE; + } + + $ret = $this->conn_id->rollBack(); + return $ret; + } + + // -------------------------------------------------------------------- + + /** + * Escape String + * + * @access public + * @param string + * @param bool whether or not the string will be used in a LIKE condition + * @return string + */ + function escape_str($str, $like = FALSE) + { + if (is_array($str)) + { + foreach ($str as $key => $val) + { + $str[$key] = $this->escape_str($val, $like); + } + + return $str; + } + + //Escape the string + $str = $this->conn_id->quote($str); + + //If there are duplicated quotes, trim them away + if (strpos($str, "'") === 0) + { + $str = substr($str, 1, -1); + } + + // escape LIKE condition wildcards + if ($like === TRUE) + { + $str = 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; + } + + // -------------------------------------------------------------------- + + /** + * Affected Rows + * + * @access public + * @return integer + */ + function affected_rows() + { + return $this->affect_rows; + } + + // -------------------------------------------------------------------- + + /** + * Insert ID + * + * @access public + * @return integer + */ + function insert_id($name=NULL) + { + //Convenience method for postgres insertid + if (strpos($this->hostname, 'pgsql') !== FALSE) + { + $v = $this->_version(); + + $table = func_num_args() > 0 ? func_get_arg(0) : NULL; + + if ($table == NULL && $v >= '8.1') + { + $sql='SELECT LASTVAL() as ins_id'; + } + $query = $this->query($sql); + $row = $query->row(); + return $row->ins_id; + } + else + { + return $this->conn_id->lastInsertId($name); + } + } + + // -------------------------------------------------------------------- + + /** + * "Count All" query + * + * Generates a platform-specific query string that counts all records in + * the specified database + * + * @access public + * @param string + * @return string + */ + function count_all($table = '') + { + if ($table == '') + { + return 0; + } + + $query = $this->query($this->_count_string . $this->_protect_identifiers('numrows') . " FROM " . $this->_protect_identifiers($table, TRUE, NULL, FALSE)); + + if ($query->num_rows() == 0) + { + return 0; + } + + $row = $query->row(); + $this->_reset_select(); + return (int) $row->numrows; + } + + // -------------------------------------------------------------------- + + /** + * Show table query + * + * Generates a platform-specific query string so that the table names can be fetched + * + * @access private + * @param boolean + * @return string + */ + function _list_tables($prefix_limit = FALSE) + { + $sql = "SHOW TABLES FROM `".$this->database."`"; + + if ($prefix_limit !== FALSE AND $this->dbprefix != '') + { + //$sql .= " LIKE '".$this->escape_like_str($this->dbprefix)."%' ".sprintf($this->_like_escape_str, $this->_like_escape_chr); + return FALSE; // not currently supported + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Show column query + * + * Generates a platform-specific query string so that the column names can be fetched + * + * @access public + * @param string the table name + * @return string + */ + function _list_columns($table = '') + { + return "SHOW COLUMNS FROM ".$table; + } + + // -------------------------------------------------------------------- + + /** + * Field data query + * + * Generates a platform-specific query so that the column data can be retrieved + * + * @access public + * @param string the table name + * @return object + */ + function _field_data($table) + { + return "SELECT TOP 1 FROM ".$table; + } + + // -------------------------------------------------------------------- + + /** + * The error message string + * + * @access private + * @return string + */ + function _error_message() + { + $error_array = $this->conn_id->errorInfo(); + return $error_array[2]; + } + + // -------------------------------------------------------------------- + + /** + * The error message number + * + * @access private + * @return integer + */ + function _error_number() + { + return $this->conn_id->errorCode(); + } + + // -------------------------------------------------------------------- + + /** + * Escape the SQL Identifiers + * + * This function escapes column and table names + * + * @access private + * @param string + * @return string + */ + function _escape_identifiers($item) + { + if ($this->_escape_char == '') + { + return $item; + } + + foreach ($this->_reserved_identifiers as $id) + { + if (strpos($item, '.'.$id) !== FALSE) + { + $str = $this->_escape_char. str_replace('.', $this->_escape_char.'.', $item); + + // remove duplicates if the user already included the escape + return preg_replace('/['.$this->_escape_char.']+/', $this->_escape_char, $str); + } + } + + if (strpos($item, '.') !== FALSE) + { + $str = $this->_escape_char.str_replace('.', $this->_escape_char.'.'.$this->_escape_char, $item).$this->_escape_char; + + } + else + { + $str = $this->_escape_char.$item.$this->_escape_char; + } + + // remove duplicates if the user already included the escape + return preg_replace('/['.$this->_escape_char.']+/', $this->_escape_char, $str); + } + + // -------------------------------------------------------------------- + + /** + * From Tables + * + * This function implicitly groups FROM tables so there is no confusion + * about operator precedence in harmony with SQL standards + * + * @access public + * @param type + * @return type + */ + function _from_tables($tables) + { + if ( ! is_array($tables)) + { + $tables = array($tables); + } + + return (count($tables) == 1) ? $tables[0] : '('.implode(', ', $tables).')'; + } + + // -------------------------------------------------------------------- + + /** + * Insert statement + * + * Generates a platform-specific insert string from the supplied data + * + * @access public + * @param string the table name + * @param array the insert keys + * @param array the insert values + * @return string + */ + function _insert($table, $keys, $values) + { + return "INSERT INTO ".$table." (".implode(', ', $keys).") VALUES (".implode(', ', $values).")"; + } + + // -------------------------------------------------------------------- + + /** + * Insert_batch statement + * + * Generates a platform-specific insert string from the supplied data + * + * @access public + * @param string the table name + * @param array the insert keys + * @param array the insert values + * @return string + */ + function _insert_batch($table, $keys, $values) + { + return "INSERT INTO ".$table." (".implode(', ', $keys).") VALUES ".implode(', ', $values); + } + + // -------------------------------------------------------------------- + + /** + * Update statement + * + * Generates a platform-specific update string from the supplied data + * + * @access public + * @param string the table name + * @param array the update data + * @param array the where clause + * @param array the orderby clause + * @param array the limit clause + * @return string + */ + function _update($table, $values, $where, $orderby = array(), $limit = FALSE) + { + foreach ($values as $key => $val) + { + $valstr[] = $key." = ".$val; + } + + $limit = ( ! $limit) ? '' : ' LIMIT '.$limit; + + $orderby = (count($orderby) >= 1)?' ORDER BY '.implode(", ", $orderby):''; + + $sql = "UPDATE ".$table." SET ".implode(', ', $valstr); + + $sql .= ($where != '' AND count($where) >=1) ? " WHERE ".implode(" ", $where) : ''; + + $sql .= $orderby.$limit; + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Update_Batch statement + * + * Generates a platform-specific batch update string from the supplied data + * + * @access public + * @param string the table name + * @param array the update data + * @param array the where clause + * @return string + */ + function _update_batch($table, $values, $index, $where = NULL) + { + $ids = array(); + $where = ($where != '' AND count($where) >=1) ? implode(" ", $where).' AND ' : ''; + + foreach ($values as $key => $val) + { + $ids[] = $val[$index]; + + foreach (array_keys($val) as $field) + { + if ($field != $index) + { + $final[$field][] = 'WHEN '.$index.' = '.$val[$index].' THEN '.$val[$field]; + } + } + } + + $sql = "UPDATE ".$table." SET "; + $cases = ''; + + foreach ($final as $k => $v) + { + $cases .= $k.' = CASE '."\n"; + foreach ($v as $row) + { + $cases .= $row."\n"; + } + + $cases .= 'ELSE '.$k.' END, '; + } + + $sql .= substr($cases, 0, -2); + + $sql .= ' WHERE '.$where.$index.' IN ('.implode(',', $ids).')'; + + return $sql; + } + + + // -------------------------------------------------------------------- + + /** + * Truncate statement + * + * Generates a platform-specific truncate string from the supplied data + * If the database does not support the truncate() command + * This function maps to "DELETE FROM table" + * + * @access public + * @param string the table name + * @return string + */ + function _truncate($table) + { + return $this->_delete($table); + } + + // -------------------------------------------------------------------- + + /** + * Delete statement + * + * Generates a platform-specific delete string from the supplied data + * + * @access public + * @param string the table name + * @param array the where clause + * @param string the limit clause + * @return string + */ + function _delete($table, $where = array(), $like = array(), $limit = FALSE) + { + $conditions = ''; + + if (count($where) > 0 OR count($like) > 0) + { + $conditions = "\nWHERE "; + $conditions .= implode("\n", $this->ar_where); + + if (count($where) > 0 && count($like) > 0) + { + $conditions .= " AND "; + } + $conditions .= implode("\n", $like); + } + + $limit = ( ! $limit) ? '' : ' LIMIT '.$limit; + + return "DELETE FROM ".$table.$conditions.$limit; + } + + // -------------------------------------------------------------------- + + /** + * Limit string + * + * Generates a platform-specific LIMIT clause + * + * @access public + * @param string the sql query string + * @param integer the number of rows to limit the query to + * @param integer the offset value + * @return string + */ + function _limit($sql, $limit, $offset) + { + if (strpos($this->hostname, 'cubrid') !== FALSE || strpos($this->hostname, 'sqlite') !== FALSE) + { + if ($offset == 0) + { + $offset = ''; + } + else + { + $offset .= ", "; + } + + return $sql."LIMIT ".$offset.$limit; + } + else + { + $sql .= "LIMIT ".$limit; + + if ($offset > 0) + { + $sql .= " OFFSET ".$offset; + } + + return $sql; + } + } + + // -------------------------------------------------------------------- + + /** + * Close DB Connection + * + * @access public + * @param resource + * @return void + */ + function _close($conn_id) + { + $this->conn_id = null; + } + + +} + + + +/* End of file pdo_driver.php */ +/* Location: ./system/database/drivers/pdo/pdo_driver.php */ \ No newline at end of file diff --git a/system/database/drivers/pdo/pdo_forge.php b/system/database/drivers/pdo/pdo_forge.php new file mode 100644 index 0000000..9a78220 --- /dev/null +++ b/system/database/drivers/pdo/pdo_forge.php @@ -0,0 +1,266 @@ +db->db_debug) + { + return $this->db->display_error('db_unsuported_feature'); + } + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Drop database + * + * @access private + * @param string the database name + * @return bool + */ + function _drop_database($name) + { + // PDO has no "drop database" command since it's + // designed to connect to an existing database + if ($this->db->db_debug) + { + return $this->db->display_error('db_unsuported_feature'); + } + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Create Table + * + * @access private + * @param string the table name + * @param array the fields + * @param mixed primary key(s) + * @param mixed key(s) + * @param boolean should 'IF NOT EXISTS' be added to the SQL + * @return bool + */ + function _create_table($table, $fields, $primary_keys, $keys, $if_not_exists) + { + $sql = 'CREATE TABLE '; + + if ($if_not_exists === TRUE) + { + $sql .= 'IF NOT EXISTS '; + } + + $sql .= $this->db->_escape_identifiers($table)." ("; + $current_field_count = 0; + + foreach ($fields as $field=>$attributes) + { + // Numeric field names aren't allowed in databases, so if the key is + // numeric, we know it was assigned by PHP and the developer manually + // entered the field information, so we'll simply add it to the list + if (is_numeric($field)) + { + $sql .= "\n\t$attributes"; + } + else + { + $attributes = array_change_key_case($attributes, CASE_UPPER); + + $sql .= "\n\t".$this->db->_protect_identifiers($field); + + $sql .= ' '.$attributes['TYPE']; + + if (array_key_exists('CONSTRAINT', $attributes)) + { + $sql .= '('.$attributes['CONSTRAINT'].')'; + } + + if (array_key_exists('UNSIGNED', $attributes) && $attributes['UNSIGNED'] === TRUE) + { + $sql .= ' UNSIGNED'; + } + + if (array_key_exists('DEFAULT', $attributes)) + { + $sql .= ' DEFAULT \''.$attributes['DEFAULT'].'\''; + } + + if (array_key_exists('NULL', $attributes) && $attributes['NULL'] === TRUE) + { + $sql .= ' NULL'; + } + else + { + $sql .= ' NOT NULL'; + } + + if (array_key_exists('AUTO_INCREMENT', $attributes) && $attributes['AUTO_INCREMENT'] === TRUE) + { + $sql .= ' AUTO_INCREMENT'; + } + } + + // don't add a comma on the end of the last field + if (++$current_field_count < count($fields)) + { + $sql .= ','; + } + } + + if (count($primary_keys) > 0) + { + $primary_keys = $this->db->_protect_identifiers($primary_keys); + $sql .= ",\n\tPRIMARY KEY (" . implode(', ', $primary_keys) . ")"; + } + + if (is_array($keys) && count($keys) > 0) + { + foreach ($keys as $key) + { + if (is_array($key)) + { + $key = $this->db->_protect_identifiers($key); + } + else + { + $key = array($this->db->_protect_identifiers($key)); + } + + $sql .= ",\n\tFOREIGN KEY (" . implode(', ', $key) . ")"; + } + } + + $sql .= "\n)"; + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Drop Table + * + * @access private + * @return bool + */ + function _drop_table($table) + { + // Not a supported PDO feature + if ($this->db->db_debug) + { + return $this->db->display_error('db_unsuported_feature'); + } + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Alter table query + * + * Generates a platform-specific query so that a table can be altered + * Called by add_column(), drop_column(), and column_alter(), + * + * @access private + * @param string the ALTER type (ADD, DROP, CHANGE) + * @param string the column name + * @param string the table name + * @param string the column definition + * @param string the default value + * @param boolean should 'NOT NULL' be added + * @param string the field after which we should add the new field + * @return object + */ + function _alter_table($alter_type, $table, $column_name, $column_definition = '', $default_value = '', $null = '', $after_field = '') + { + $sql = 'ALTER TABLE '.$this->db->_protect_identifiers($table)." $alter_type ".$this->db->_protect_identifiers($column_name); + + // DROP has everything it needs now. + if ($alter_type == 'DROP') + { + return $sql; + } + + $sql .= " $column_definition"; + + if ($default_value != '') + { + $sql .= " DEFAULT \"$default_value\""; + } + + if ($null === NULL) + { + $sql .= ' NULL'; + } + else + { + $sql .= ' NOT NULL'; + } + + if ($after_field != '') + { + $sql .= ' AFTER ' . $this->db->_protect_identifiers($after_field); + } + + return $sql; + + } + + + // -------------------------------------------------------------------- + + /** + * Rename a table + * + * Generates a platform-specific query so that a table can be renamed + * + * @access private + * @param string the old table name + * @param string the new table name + * @return string + */ + function _rename_table($table_name, $new_table_name) + { + $sql = 'ALTER TABLE '.$this->db->_protect_identifiers($table_name)." RENAME TO ".$this->db->_protect_identifiers($new_table_name); + return $sql; + } + + +} + +/* End of file pdo_forge.php */ +/* Location: ./system/database/drivers/pdo/pdo_forge.php */ \ No newline at end of file diff --git a/system/database/drivers/pdo/pdo_result.php b/system/database/drivers/pdo/pdo_result.php new file mode 100644 index 0000000..44fdd6d --- /dev/null +++ b/system/database/drivers/pdo/pdo_result.php @@ -0,0 +1,183 @@ +num_rows)) + { + return $this->num_rows; + } + elseif (($this->num_rows = $this->result_id->rowCount()) > 0) + { + return $this->num_rows; + } + + $this->num_rows = count($this->result_id->fetchAll()); + $this->result_id->execute(); + return $this->num_rows; + } + + // -------------------------------------------------------------------- + + /** + * Number of fields in the result set + * + * @access public + * @return integer + */ + function num_fields() + { + return $this->result_id->columnCount(); + } + + // -------------------------------------------------------------------- + + /** + * Fetch Field Names + * + * Generates an array of column names + * + * @access public + * @return array + */ + function list_fields() + { + if ($this->db->db_debug) + { + return $this->db->display_error('db_unsuported_feature'); + } + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Field data + * + * Generates an array of objects containing field meta-data + * + * @access public + * @return array + */ + function field_data() + { + $data = array(); + + try + { + for($i = 0; $i < $this->num_fields(); $i++) + { + $data[] = $this->result_id->getColumnMeta($i); + } + + return $data; + } + catch (Exception $e) + { + if ($this->db->db_debug) + { + return $this->db->display_error('db_unsuported_feature'); + } + return FALSE; + } + } + + // -------------------------------------------------------------------- + + /** + * Free the result + * + * @return null + */ + function free_result() + { + if (is_object($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 + * + * @access private + * @return array + */ + function _data_seek($n = 0) + { + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Result - associative array + * + * Returns the result set as an array + * + * @access private + * @return array + */ + function _fetch_assoc() + { + return $this->result_id->fetch(PDO::FETCH_ASSOC); + } + + // -------------------------------------------------------------------- + + /** + * Result - object + * + * Returns the result set as an object + * + * @access private + * @return object + */ + function _fetch_object() + { + return $this->result_id->fetchObject(); + } + +} + + +/* End of file pdo_result.php */ +/* Location: ./system/database/drivers/pdo/pdo_result.php */ \ No newline at end of file diff --git a/system/database/drivers/pdo/pdo_utility.php b/system/database/drivers/pdo/pdo_utility.php new file mode 100644 index 0000000..88ce033 --- /dev/null +++ b/system/database/drivers/pdo/pdo_utility.php @@ -0,0 +1,103 @@ +db->db_debug) + { + return $this->db->display_error('db_unsuported_feature'); + } + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Optimize table query + * + * Generates a platform-specific query so that a table can be optimized + * + * @access private + * @param string the table name + * @return object + */ + function _optimize_table($table) + { + // Not a supported PDO feature + if ($this->db->db_debug) + { + return $this->db->display_error('db_unsuported_feature'); + } + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Repair table query + * + * Generates a platform-specific query so that a table can be repaired + * + * @access private + * @param string the table name + * @return object + */ + function _repair_table($table) + { + // Not a supported PDO feature + if ($this->db->db_debug) + { + return $this->db->display_error('db_unsuported_feature'); + } + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * PDO Export + * + * @access private + * @param array Preferences + * @return mixed + */ + function _backup($params = array()) + { + // Currently unsupported + return $this->db->display_error('db_unsuported_feature'); + } + +} + +/* End of file pdo_utility.php */ +/* Location: ./system/database/drivers/pdo/pdo_utility.php */ \ No newline at end of file diff --git a/system/database/drivers/postgre/index.html b/system/database/drivers/postgre/index.html new file mode 100644 index 0000000..c942a79 --- /dev/null +++ b/system/database/drivers/postgre/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/system/database/drivers/postgre/postgre_driver.php b/system/database/drivers/postgre/postgre_driver.php new file mode 100644 index 0000000..5367f97 --- /dev/null +++ b/system/database/drivers/postgre/postgre_driver.php @@ -0,0 +1,703 @@ + 'host', + 'port' => 'port', + 'database' => 'dbname', + 'username' => 'user', + 'password' => 'password' + ); + + $connect_string = ""; + foreach ($components as $key => $val) + { + if (isset($this->$key) && $this->$key != '') + { + $connect_string .= " $val=".$this->$key; + } + } + return trim($connect_string); + } + + // -------------------------------------------------------------------- + + /** + * Non-persistent database connection + * + * @access private called by the base class + * @return resource + */ + function db_connect() + { + return @pg_connect($this->_connect_string()); + } + + // -------------------------------------------------------------------- + + /** + * Persistent database connection + * + * @access private called by the base class + * @return resource + */ + function db_pconnect() + { + return @pg_pconnect($this->_connect_string()); + } + + // -------------------------------------------------------------------- + + /** + * Reconnect + * + * Keep / reestablish the db connection if no queries have been + * sent for a length of time exceeding the server's idle timeout + * + * @access public + * @return void + */ + function reconnect() + { + if (pg_ping($this->conn_id) === FALSE) + { + $this->conn_id = FALSE; + } + } + + // -------------------------------------------------------------------- + + /** + * Select the database + * + * @access private called by the base class + * @return resource + */ + function db_select() + { + // Not needed for Postgre so we'll return TRUE + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Set client character set + * + * @access public + * @param string + * @param string + * @return resource + */ + function db_set_charset($charset, $collation) + { + // @todo - add support if needed + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Version number query string + * + * @access public + * @return string + */ + function _version() + { + return "SELECT version() AS ver"; + } + + // -------------------------------------------------------------------- + + /** + * Execute the query + * + * @access private called by the base class + * @param string an SQL query + * @return resource + */ + function _execute($sql) + { + $sql = $this->_prep_query($sql); + return @pg_query($this->conn_id, $sql); + } + + // -------------------------------------------------------------------- + + /** + * Prep the query + * + * If needed, each database adapter can prep the query string + * + * @access private called by execute() + * @param string an SQL query + * @return string + */ + function _prep_query($sql) + { + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Begin Transaction + * + * @access public + * @return bool + */ + function trans_begin($test_mode = FALSE) + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + 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) ? TRUE : FALSE; + + return @pg_exec($this->conn_id, "begin"); + } + + // -------------------------------------------------------------------- + + /** + * Commit Transaction + * + * @access public + * @return bool + */ + function trans_commit() + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + return TRUE; + } + + return @pg_exec($this->conn_id, "commit"); + } + + // -------------------------------------------------------------------- + + /** + * Rollback Transaction + * + * @access public + * @return bool + */ + function trans_rollback() + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + return TRUE; + } + + return @pg_exec($this->conn_id, "rollback"); + } + + // -------------------------------------------------------------------- + + /** + * Escape String + * + * @access public + * @param string + * @param bool whether or not the string will be used in a LIKE condition + * @return string + */ + 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 = pg_escape_string($str); + + // escape LIKE condition wildcards + if ($like === TRUE) + { + $str = 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; + } + + // -------------------------------------------------------------------- + + /** + * Affected Rows + * + * @access public + * @return integer + */ + function affected_rows() + { + return @pg_affected_rows($this->result_id); + } + + // -------------------------------------------------------------------- + + /** + * Insert ID + * + * @access public + * @return integer + */ + function insert_id() + { + $v = $this->_version(); + $v = $v['server']; + + $table = func_num_args() > 0 ? func_get_arg(0) : NULL; + $column = func_num_args() > 1 ? func_get_arg(1) : NULL; + + if ($table == NULL && $v >= '8.1') + { + $sql='SELECT LASTVAL() as ins_id'; + } + elseif ($table != NULL && $column != NULL && $v >= '8.0') + { + $sql = sprintf("SELECT pg_get_serial_sequence('%s','%s') as seq", $table, $column); + $query = $this->query($sql); + $row = $query->row(); + $sql = sprintf("SELECT CURRVAL('%s') as ins_id", $row->seq); + } + elseif ($table != NULL) + { + // seq_name passed in table parameter + $sql = sprintf("SELECT CURRVAL('%s') as ins_id", $table); + } + else + { + return pg_last_oid($this->result_id); + } + $query = $this->query($sql); + $row = $query->row(); + return $row->ins_id; + } + + // -------------------------------------------------------------------- + + /** + * "Count All" query + * + * Generates a platform-specific query string that counts all records in + * the specified database + * + * @access public + * @param string + * @return string + */ + function count_all($table = '') + { + if ($table == '') + { + return 0; + } + + $query = $this->query($this->_count_string . $this->_protect_identifiers('numrows') . " FROM " . $this->_protect_identifiers($table, TRUE, NULL, FALSE)); + + if ($query->num_rows() == 0) + { + return 0; + } + + $row = $query->row(); + $this->_reset_select(); + return (int) $row->numrows; + } + + // -------------------------------------------------------------------- + + /** + * Show table query + * + * Generates a platform-specific query string so that the table names can be fetched + * + * @access private + * @param boolean + * @return string + */ + function _list_tables($prefix_limit = FALSE) + { + $sql = "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'"; + + if ($prefix_limit !== FALSE AND $this->dbprefix != '') + { + $sql .= " AND 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 + * + * @access public + * @param string the table name + * @return string + */ + function _list_columns($table = '') + { + return "SELECT column_name FROM information_schema.columns WHERE table_name ='".$table."'"; + } + + // -------------------------------------------------------------------- + + /** + * Field data query + * + * Generates a platform-specific query so that the column data can be retrieved + * + * @access public + * @param string the table name + * @return object + */ + function _field_data($table) + { + return "SELECT * FROM ".$table." LIMIT 1"; + } + + // -------------------------------------------------------------------- + + /** + * The error message string + * + * @access private + * @return string + */ + function _error_message() + { + return pg_last_error($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * The error message number + * + * @access private + * @return integer + */ + function _error_number() + { + return ''; + } + + // -------------------------------------------------------------------- + + /** + * Escape the SQL Identifiers + * + * This function escapes column and table names + * + * @access private + * @param string + * @return string + */ + function _escape_identifiers($item) + { + if ($this->_escape_char == '') + { + return $item; + } + + foreach ($this->_reserved_identifiers as $id) + { + if (strpos($item, '.'.$id) !== FALSE) + { + $str = $this->_escape_char. str_replace('.', $this->_escape_char.'.', $item); + + // remove duplicates if the user already included the escape + return preg_replace('/['.$this->_escape_char.']+/', $this->_escape_char, $str); + } + } + + if (strpos($item, '.') !== FALSE) + { + $str = $this->_escape_char.str_replace('.', $this->_escape_char.'.'.$this->_escape_char, $item).$this->_escape_char; + } + else + { + $str = $this->_escape_char.$item.$this->_escape_char; + } + + // remove duplicates if the user already included the escape + return preg_replace('/['.$this->_escape_char.']+/', $this->_escape_char, $str); + } + + // -------------------------------------------------------------------- + + /** + * From Tables + * + * This function implicitly groups FROM tables so there is no confusion + * about operator precedence in harmony with SQL standards + * + * @access public + * @param type + * @return type + */ + function _from_tables($tables) + { + if ( ! is_array($tables)) + { + $tables = array($tables); + } + + return implode(', ', $tables); + } + + // -------------------------------------------------------------------- + + /** + * Insert statement + * + * Generates a platform-specific insert string from the supplied data + * + * @access public + * @param string the table name + * @param array the insert keys + * @param array the insert values + * @return string + */ + function _insert($table, $keys, $values) + { + return "INSERT INTO ".$table." (".implode(', ', $keys).") VALUES (".implode(', ', $values).")"; + } + + // -------------------------------------------------------------------- + + /** + * Insert_batch statement + * + * Generates a platform-specific insert string from the supplied data + * + * @access public + * @param string the table name + * @param array the insert keys + * @param array the insert values + * @return string + */ + function _insert_batch($table, $keys, $values) + { + return "INSERT INTO ".$table." (".implode(', ', $keys).") VALUES ".implode(', ', $values); + } + + // -------------------------------------------------------------------- + + /** + * Update statement + * + * Generates a platform-specific update string from the supplied data + * + * @access public + * @param string the table name + * @param array the update data + * @param array the where clause + * @param array the orderby clause + * @param array the limit clause + * @return string + */ + function _update($table, $values, $where, $orderby = array(), $limit = FALSE) + { + foreach ($values as $key => $val) + { + $valstr[] = $key." = ".$val; + } + + $limit = ( ! $limit) ? '' : ' LIMIT '.$limit; + + $orderby = (count($orderby) >= 1)?' ORDER BY '.implode(", ", $orderby):''; + + $sql = "UPDATE ".$table." SET ".implode(', ', $valstr); + + $sql .= ($where != '' AND count($where) >=1) ? " WHERE ".implode(" ", $where) : ''; + + $sql .= $orderby.$limit; + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Truncate statement + * + * Generates a platform-specific truncate string from the supplied data + * If the database does not support the truncate() command + * This function maps to "DELETE FROM table" + * + * @access public + * @param string the table name + * @return string + */ + function _truncate($table) + { + return "TRUNCATE ".$table; + } + + // -------------------------------------------------------------------- + + /** + * Delete statement + * + * Generates a platform-specific delete string from the supplied data + * + * @access public + * @param string the table name + * @param array the where clause + * @param string the limit clause + * @return string + */ + function _delete($table, $where = array(), $like = array(), $limit = FALSE) + { + $conditions = ''; + + if (count($where) > 0 OR count($like) > 0) + { + $conditions = "\nWHERE "; + $conditions .= implode("\n", $this->ar_where); + + if (count($where) > 0 && count($like) > 0) + { + $conditions .= " AND "; + } + $conditions .= implode("\n", $like); + } + + $limit = ( ! $limit) ? '' : ' LIMIT '.$limit; + + return "DELETE FROM ".$table.$conditions.$limit; + } + + // -------------------------------------------------------------------- + /** + * Limit string + * + * Generates a platform-specific LIMIT clause + * + * @access public + * @param string the sql query string + * @param integer the number of rows to limit the query to + * @param integer the offset value + * @return string + */ + function _limit($sql, $limit, $offset) + { + $sql .= "LIMIT ".$limit; + + if ($offset > 0) + { + $sql .= " OFFSET ".$offset; + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Close DB Connection + * + * @access public + * @param resource + * @return void + */ + function _close($conn_id) + { + @pg_close($conn_id); + } + + +} + + +/* End of file postgre_driver.php */ +/* Location: ./system/database/drivers/postgre/postgre_driver.php */ \ No newline at end of file diff --git a/system/database/drivers/postgre/postgre_forge.php b/system/database/drivers/postgre/postgre_forge.php new file mode 100644 index 0000000..91a1c68 --- /dev/null +++ b/system/database/drivers/postgre/postgre_forge.php @@ -0,0 +1,299 @@ +db->table_exists($table)) + { + return "SELECT * FROM $table"; // Needs to return innocous but valid SQL statement + } + } + + $sql .= $this->db->_escape_identifiers($table)." ("; + $current_field_count = 0; + + foreach ($fields as $field=>$attributes) + { + // Numeric field names aren't allowed in databases, so if the key is + // numeric, we know it was assigned by PHP and the developer manually + // entered the field information, so we'll simply add it to the list + if (is_numeric($field)) + { + $sql .= "\n\t$attributes"; + } + else + { + $attributes = array_change_key_case($attributes, CASE_UPPER); + + $sql .= "\n\t".$this->db->_protect_identifiers($field); + + $is_unsigned = (array_key_exists('UNSIGNED', $attributes) && $attributes['UNSIGNED'] === TRUE); + + // Convert datatypes to be PostgreSQL-compatible + switch (strtoupper($attributes['TYPE'])) + { + case 'TINYINT': + $attributes['TYPE'] = 'SMALLINT'; + break; + case 'SMALLINT': + $attributes['TYPE'] = ($is_unsigned) ? 'INTEGER' : 'SMALLINT'; + break; + case 'MEDIUMINT': + $attributes['TYPE'] = 'INTEGER'; + break; + case 'INT': + $attributes['TYPE'] = ($is_unsigned) ? 'BIGINT' : 'INTEGER'; + break; + case 'BIGINT': + $attributes['TYPE'] = ($is_unsigned) ? 'NUMERIC' : 'BIGINT'; + break; + case 'DOUBLE': + $attributes['TYPE'] = 'DOUBLE PRECISION'; + break; + case 'DATETIME': + $attributes['TYPE'] = 'TIMESTAMP'; + break; + case 'LONGTEXT': + $attributes['TYPE'] = 'TEXT'; + break; + case 'BLOB': + $attributes['TYPE'] = 'BYTEA'; + break; + } + + // If this is an auto-incrementing primary key, use the serial data type instead + if (in_array($field, $primary_keys) && array_key_exists('AUTO_INCREMENT', $attributes) + && $attributes['AUTO_INCREMENT'] === TRUE) + { + $sql .= ' SERIAL'; + } + else + { + $sql .= ' '.$attributes['TYPE']; + } + + // Modified to prevent constraints with integer data types + if (array_key_exists('CONSTRAINT', $attributes) && strpos($attributes['TYPE'], 'INT') === false) + { + $sql .= '('.$attributes['CONSTRAINT'].')'; + } + + if (array_key_exists('DEFAULT', $attributes)) + { + $sql .= ' DEFAULT \''.$attributes['DEFAULT'].'\''; + } + + if (array_key_exists('NULL', $attributes) && $attributes['NULL'] === TRUE) + { + $sql .= ' NULL'; + } + else + { + $sql .= ' NOT NULL'; + } + + // Added new attribute to create unqite fields. Also works with MySQL + if (array_key_exists('UNIQUE', $attributes) && $attributes['UNIQUE'] === TRUE) + { + $sql .= ' UNIQUE'; + } + } + + // don't add a comma on the end of the last field + if (++$current_field_count < count($fields)) + { + $sql .= ','; + } + } + + if (count($primary_keys) > 0) + { + // Something seems to break when passing an array to _protect_identifiers() + foreach ($primary_keys as $index => $key) + { + $primary_keys[$index] = $this->db->_protect_identifiers($key); + } + + $sql .= ",\n\tPRIMARY KEY (" . implode(', ', $primary_keys) . ")"; + } + + $sql .= "\n);"; + + if (is_array($keys) && count($keys) > 0) + { + foreach ($keys as $key) + { + if (is_array($key)) + { + $key = $this->db->_protect_identifiers($key); + } + else + { + $key = array($this->db->_protect_identifiers($key)); + } + + foreach ($key as $field) + { + $sql .= "CREATE INDEX " . $table . "_" . str_replace(array('"', "'"), '', $field) . "_index ON $table ($field); "; + } + } + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Drop Table + * + * @access private + * @return bool + */ + function _drop_table($table) + { + return "DROP TABLE IF EXISTS ".$this->db->_escape_identifiers($table)." CASCADE"; + } + + // -------------------------------------------------------------------- + + /** + * Alter table query + * + * Generates a platform-specific query so that a table can be altered + * Called by add_column(), drop_column(), and column_alter(), + * + * @access private + * @param string the ALTER type (ADD, DROP, CHANGE) + * @param string the column name + * @param string the table name + * @param string the column definition + * @param string the default value + * @param boolean should 'NOT NULL' be added + * @param string the field after which we should add the new field + * @return object + */ + function _alter_table($alter_type, $table, $column_name, $column_definition = '', $default_value = '', $null = '', $after_field = '') + { + $sql = 'ALTER TABLE '.$this->db->_protect_identifiers($table)." $alter_type ".$this->db->_protect_identifiers($column_name); + + // DROP has everything it needs now. + if ($alter_type == 'DROP') + { + return $sql; + } + + $sql .= " $column_definition"; + + if ($default_value != '') + { + $sql .= " DEFAULT \"$default_value\""; + } + + if ($null === NULL) + { + $sql .= ' NULL'; + } + else + { + $sql .= ' NOT NULL'; + } + + if ($after_field != '') + { + $sql .= ' AFTER ' . $this->db->_protect_identifiers($after_field); + } + + return $sql; + + } + + // -------------------------------------------------------------------- + + /** + * Rename a table + * + * Generates a platform-specific query so that a table can be renamed + * + * @access private + * @param string the old table name + * @param string the new table name + * @return string + */ + function _rename_table($table_name, $new_table_name) + { + $sql = 'ALTER TABLE '.$this->db->_protect_identifiers($table_name)." RENAME TO ".$this->db->_protect_identifiers($new_table_name); + return $sql; + } + + +} + +/* End of file postgre_forge.php */ +/* Location: ./system/database/drivers/postgre/postgre_forge.php */ \ No newline at end of file diff --git a/system/database/drivers/postgre/postgre_result.php b/system/database/drivers/postgre/postgre_result.php new file mode 100644 index 0000000..e9a1d16 --- /dev/null +++ b/system/database/drivers/postgre/postgre_result.php @@ -0,0 +1,169 @@ +result_id); + } + + // -------------------------------------------------------------------- + + /** + * Number of fields in the result set + * + * @access public + * @return integer + */ + function num_fields() + { + return @pg_num_fields($this->result_id); + } + + // -------------------------------------------------------------------- + + /** + * Fetch Field Names + * + * Generates an array of column names + * + * @access public + * @return array + */ + function list_fields() + { + $field_names = array(); + for ($i = 0; $i < $this->num_fields(); $i++) + { + $field_names[] = pg_field_name($this->result_id, $i); + } + + return $field_names; + } + + // -------------------------------------------------------------------- + + /** + * Field data + * + * Generates an array of objects containing field meta-data + * + * @access public + * @return array + */ + function field_data() + { + $retval = array(); + for ($i = 0; $i < $this->num_fields(); $i++) + { + $F = new stdClass(); + $F->name = pg_field_name($this->result_id, $i); + $F->type = pg_field_type($this->result_id, $i); + $F->max_length = pg_field_size($this->result_id, $i); + $F->primary_key = 0; + $F->default = ''; + + $retval[] = $F; + } + + return $retval; + } + + // -------------------------------------------------------------------- + + /** + * Free the result + * + * @return null + */ + function free_result() + { + if (is_resource($this->result_id)) + { + pg_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 + * + * @access private + * @return array + */ + function _data_seek($n = 0) + { + return pg_result_seek($this->result_id, $n); + } + + // -------------------------------------------------------------------- + + /** + * Result - associative array + * + * Returns the result set as an array + * + * @access private + * @return array + */ + function _fetch_assoc() + { + return pg_fetch_assoc($this->result_id); + } + + // -------------------------------------------------------------------- + + /** + * Result - object + * + * Returns the result set as an object + * + * @access private + * @return object + */ + function _fetch_object() + { + return pg_fetch_object($this->result_id); + } + +} + + +/* End of file postgre_result.php */ +/* Location: ./system/database/drivers/postgre/postgre_result.php */ \ No newline at end of file diff --git a/system/database/drivers/postgre/postgre_utility.php b/system/database/drivers/postgre/postgre_utility.php new file mode 100644 index 0000000..741c52e --- /dev/null +++ b/system/database/drivers/postgre/postgre_utility.php @@ -0,0 +1,88 @@ +db->display_error('db_unsuported_feature'); + } +} + + +/* End of file postgre_utility.php */ +/* Location: ./system/database/drivers/postgre/postgre_utility.php */ \ No newline at end of file diff --git a/system/database/drivers/sqlite/index.html b/system/database/drivers/sqlite/index.html new file mode 100644 index 0000000..c942a79 --- /dev/null +++ b/system/database/drivers/sqlite/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/system/database/drivers/sqlite/sqlite_driver.php b/system/database/drivers/sqlite/sqlite_driver.php new file mode 100644 index 0000000..0cc898b --- /dev/null +++ b/system/database/drivers/sqlite/sqlite_driver.php @@ -0,0 +1,658 @@ +database, FILE_WRITE_MODE, $error)) + { + log_message('error', $error); + + if ($this->db_debug) + { + $this->display_error($error, '', TRUE); + } + + return FALSE; + } + + return $conn_id; + } + + // -------------------------------------------------------------------- + + /** + * Persistent database connection + * + * @access private called by the base class + * @return resource + */ + function db_pconnect() + { + if ( ! $conn_id = @sqlite_popen($this->database, FILE_WRITE_MODE, $error)) + { + log_message('error', $error); + + if ($this->db_debug) + { + $this->display_error($error, '', TRUE); + } + + return FALSE; + } + + return $conn_id; + } + + // -------------------------------------------------------------------- + + /** + * Reconnect + * + * Keep / reestablish the db connection if no queries have been + * sent for a length of time exceeding the server's idle timeout + * + * @access public + * @return void + */ + function reconnect() + { + // not implemented in SQLite + } + + // -------------------------------------------------------------------- + + /** + * Select the database + * + * @access private called by the base class + * @return resource + */ + function db_select() + { + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Set client character set + * + * @access public + * @param string + * @param string + * @return resource + */ + function db_set_charset($charset, $collation) + { + // @todo - add support if needed + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Version number query string + * + * @access public + * @return string + */ + function _version() + { + return sqlite_libversion(); + } + + // -------------------------------------------------------------------- + + /** + * Execute the query + * + * @access private called by the base class + * @param string an SQL query + * @return resource + */ + function _execute($sql) + { + $sql = $this->_prep_query($sql); + return @sqlite_query($this->conn_id, $sql); + } + + // -------------------------------------------------------------------- + + /** + * Prep the query + * + * If needed, each database adapter can prep the query string + * + * @access private called by execute() + * @param string an SQL query + * @return string + */ + function _prep_query($sql) + { + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Begin Transaction + * + * @access public + * @return bool + */ + function trans_begin($test_mode = FALSE) + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + 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) ? TRUE : FALSE; + + $this->simple_query('BEGIN TRANSACTION'); + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Commit Transaction + * + * @access public + * @return bool + */ + function trans_commit() + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + return TRUE; + } + + $this->simple_query('COMMIT'); + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Rollback Transaction + * + * @access public + * @return bool + */ + function trans_rollback() + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + return TRUE; + } + + $this->simple_query('ROLLBACK'); + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Escape String + * + * @access public + * @param string + * @param bool whether or not the string will be used in a LIKE condition + * @return string + */ + 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 = sqlite_escape_string($str); + + // escape LIKE condition wildcards + if ($like === TRUE) + { + $str = 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; + } + + // -------------------------------------------------------------------- + + /** + * Affected Rows + * + * @access public + * @return integer + */ + function affected_rows() + { + return sqlite_changes($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * Insert ID + * + * @access public + * @return integer + */ + function insert_id() + { + return @sqlite_last_insert_rowid($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * "Count All" query + * + * Generates a platform-specific query string that counts all records in + * the specified database + * + * @access public + * @param string + * @return string + */ + function count_all($table = '') + { + if ($table == '') + { + return 0; + } + + $query = $this->query($this->_count_string . $this->_protect_identifiers('numrows') . " FROM " . $this->_protect_identifiers($table, TRUE, NULL, FALSE)); + + if ($query->num_rows() == 0) + { + return 0; + } + + $row = $query->row(); + $this->_reset_select(); + return (int) $row->numrows; + } + + // -------------------------------------------------------------------- + + /** + * List table query + * + * Generates a platform-specific query string so that the table names can be fetched + * + * @access private + * @param boolean + * @return string + */ + function _list_tables($prefix_limit = FALSE) + { + $sql = "SELECT name from sqlite_master WHERE type='table'"; + + if ($prefix_limit !== FALSE AND $this->dbprefix != '') + { + $sql .= " AND '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 + * + * @access public + * @param string the table name + * @return string + */ + function _list_columns($table = '') + { + // Not supported + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Field data query + * + * Generates a platform-specific query so that the column data can be retrieved + * + * @access public + * @param string the table name + * @return object + */ + function _field_data($table) + { + return "SELECT * FROM ".$table." LIMIT 1"; + } + + // -------------------------------------------------------------------- + + /** + * The error message string + * + * @access private + * @return string + */ + function _error_message() + { + return sqlite_error_string(sqlite_last_error($this->conn_id)); + } + + // -------------------------------------------------------------------- + + /** + * The error message number + * + * @access private + * @return integer + */ + function _error_number() + { + return sqlite_last_error($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * Escape the SQL Identifiers + * + * This function escapes column and table names + * + * @access private + * @param string + * @return string + */ + function _escape_identifiers($item) + { + if ($this->_escape_char == '') + { + return $item; + } + + foreach ($this->_reserved_identifiers as $id) + { + if (strpos($item, '.'.$id) !== FALSE) + { + $str = $this->_escape_char. str_replace('.', $this->_escape_char.'.', $item); + + // remove duplicates if the user already included the escape + return preg_replace('/['.$this->_escape_char.']+/', $this->_escape_char, $str); + } + } + + if (strpos($item, '.') !== FALSE) + { + $str = $this->_escape_char.str_replace('.', $this->_escape_char.'.'.$this->_escape_char, $item).$this->_escape_char; + } + else + { + $str = $this->_escape_char.$item.$this->_escape_char; + } + + // remove duplicates if the user already included the escape + return preg_replace('/['.$this->_escape_char.']+/', $this->_escape_char, $str); + } + + // -------------------------------------------------------------------- + + /** + * From Tables + * + * This function implicitly groups FROM tables so there is no confusion + * about operator precedence in harmony with SQL standards + * + * @access public + * @param type + * @return type + */ + function _from_tables($tables) + { + if ( ! is_array($tables)) + { + $tables = array($tables); + } + + return '('.implode(', ', $tables).')'; + } + + // -------------------------------------------------------------------- + + /** + * Insert statement + * + * Generates a platform-specific insert string from the supplied data + * + * @access public + * @param string the table name + * @param array the insert keys + * @param array the insert values + * @return string + */ + function _insert($table, $keys, $values) + { + return "INSERT INTO ".$table." (".implode(', ', $keys).") VALUES (".implode(', ', $values).")"; + } + + // -------------------------------------------------------------------- + + /** + * Update statement + * + * Generates a platform-specific update string from the supplied data + * + * @access public + * @param string the table name + * @param array the update data + * @param array the where clause + * @param array the orderby clause + * @param array the limit clause + * @return string + */ + function _update($table, $values, $where, $orderby = array(), $limit = FALSE) + { + foreach ($values as $key => $val) + { + $valstr[] = $key." = ".$val; + } + + $limit = ( ! $limit) ? '' : ' LIMIT '.$limit; + + $orderby = (count($orderby) >= 1)?' ORDER BY '.implode(", ", $orderby):''; + + $sql = "UPDATE ".$table." SET ".implode(', ', $valstr); + + $sql .= ($where != '' AND count($where) >=1) ? " WHERE ".implode(" ", $where) : ''; + + $sql .= $orderby.$limit; + + return $sql; + } + + + // -------------------------------------------------------------------- + + /** + * Truncate statement + * + * Generates a platform-specific truncate string from the supplied data + * If the database does not support the truncate() command + * This function maps to "DELETE FROM table" + * + * @access public + * @param string the table name + * @return string + */ + function _truncate($table) + { + return $this->_delete($table); + } + + // -------------------------------------------------------------------- + + /** + * Delete statement + * + * Generates a platform-specific delete string from the supplied data + * + * @access public + * @param string the table name + * @param array the where clause + * @param string the limit clause + * @return string + */ + function _delete($table, $where = array(), $like = array(), $limit = FALSE) + { + $conditions = ''; + + if (count($where) > 0 OR count($like) > 0) + { + $conditions = "\nWHERE "; + $conditions .= implode("\n", $this->ar_where); + + if (count($where) > 0 && count($like) > 0) + { + $conditions .= " AND "; + } + $conditions .= implode("\n", $like); + } + + $limit = ( ! $limit) ? '' : ' LIMIT '.$limit; + + return "DELETE FROM ".$table.$conditions.$limit; + } + + // -------------------------------------------------------------------- + + /** + * Limit string + * + * Generates a platform-specific LIMIT clause + * + * @access public + * @param string the sql query string + * @param integer the number of rows to limit the query to + * @param integer the offset value + * @return string + */ + function _limit($sql, $limit, $offset) + { + if ($offset == 0) + { + $offset = ''; + } + else + { + $offset .= ", "; + } + + return $sql."LIMIT ".$offset.$limit; + } + + // -------------------------------------------------------------------- + + /** + * Close DB Connection + * + * @access public + * @param resource + * @return void + */ + function _close($conn_id) + { + @sqlite_close($conn_id); + } + + +} + + +/* End of file sqlite_driver.php */ +/* Location: ./system/database/drivers/sqlite/sqlite_driver.php */ \ No newline at end of file diff --git a/system/database/drivers/sqlite/sqlite_forge.php b/system/database/drivers/sqlite/sqlite_forge.php new file mode 100644 index 0000000..5690408 --- /dev/null +++ b/system/database/drivers/sqlite/sqlite_forge.php @@ -0,0 +1,265 @@ +db->database) OR ! @unlink($this->db->database)) + { + if ($this->db->db_debug) + { + return $this->db->display_error('db_unable_to_drop'); + } + return FALSE; + } + return TRUE; + } + // -------------------------------------------------------------------- + + /** + * Create Table + * + * @access private + * @param string the table name + * @param array the fields + * @param mixed primary key(s) + * @param mixed key(s) + * @param boolean should 'IF NOT EXISTS' be added to the SQL + * @return bool + */ + function _create_table($table, $fields, $primary_keys, $keys, $if_not_exists) + { + $sql = 'CREATE TABLE '; + + // IF NOT EXISTS added to SQLite in 3.3.0 + if ($if_not_exists === TRUE && version_compare($this->db->_version(), '3.3.0', '>=') === TRUE) + { + $sql .= 'IF NOT EXISTS '; + } + + $sql .= $this->db->_escape_identifiers($table)."("; + $current_field_count = 0; + + foreach ($fields as $field=>$attributes) + { + // Numeric field names aren't allowed in databases, so if the key is + // numeric, we know it was assigned by PHP and the developer manually + // entered the field information, so we'll simply add it to the list + if (is_numeric($field)) + { + $sql .= "\n\t$attributes"; + } + else + { + $attributes = array_change_key_case($attributes, CASE_UPPER); + + $sql .= "\n\t".$this->db->_protect_identifiers($field); + + $sql .= ' '.$attributes['TYPE']; + + if (array_key_exists('CONSTRAINT', $attributes)) + { + $sql .= '('.$attributes['CONSTRAINT'].')'; + } + + if (array_key_exists('UNSIGNED', $attributes) && $attributes['UNSIGNED'] === TRUE) + { + $sql .= ' UNSIGNED'; + } + + if (array_key_exists('DEFAULT', $attributes)) + { + $sql .= ' DEFAULT \''.$attributes['DEFAULT'].'\''; + } + + if (array_key_exists('NULL', $attributes) && $attributes['NULL'] === TRUE) + { + $sql .= ' NULL'; + } + else + { + $sql .= ' NOT NULL'; + } + + if (array_key_exists('AUTO_INCREMENT', $attributes) && $attributes['AUTO_INCREMENT'] === TRUE) + { + $sql .= ' AUTO_INCREMENT'; + } + } + + // don't add a comma on the end of the last field + if (++$current_field_count < count($fields)) + { + $sql .= ','; + } + } + + if (count($primary_keys) > 0) + { + $primary_keys = $this->db->_protect_identifiers($primary_keys); + $sql .= ",\n\tPRIMARY KEY (" . implode(', ', $primary_keys) . ")"; + } + + if (is_array($keys) && count($keys) > 0) + { + foreach ($keys as $key) + { + if (is_array($key)) + { + $key = $this->db->_protect_identifiers($key); + } + else + { + $key = array($this->db->_protect_identifiers($key)); + } + + $sql .= ",\n\tUNIQUE (" . implode(', ', $key) . ")"; + } + } + + $sql .= "\n)"; + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Drop Table + * + * Unsupported feature in SQLite + * + * @access private + * @return bool + */ + function _drop_table($table) + { + if ($this->db->db_debug) + { + return $this->db->display_error('db_unsuported_feature'); + } + return array(); + } + + // -------------------------------------------------------------------- + + /** + * Alter table query + * + * Generates a platform-specific query so that a table can be altered + * Called by add_column(), drop_column(), and column_alter(), + * + * @access private + * @param string the ALTER type (ADD, DROP, CHANGE) + * @param string the column name + * @param string the table name + * @param string the column definition + * @param string the default value + * @param boolean should 'NOT NULL' be added + * @param string the field after which we should add the new field + * @return object + */ + function _alter_table($alter_type, $table, $column_name, $column_definition = '', $default_value = '', $null = '', $after_field = '') + { + $sql = 'ALTER TABLE '.$this->db->_protect_identifiers($table)." $alter_type ".$this->db->_protect_identifiers($column_name); + + // DROP has everything it needs now. + if ($alter_type == 'DROP') + { + // SQLite does not support dropping columns + // http://www.sqlite.org/omitted.html + // http://www.sqlite.org/faq.html#q11 + return FALSE; + } + + $sql .= " $column_definition"; + + if ($default_value != '') + { + $sql .= " DEFAULT \"$default_value\""; + } + + if ($null === NULL) + { + $sql .= ' NULL'; + } + else + { + $sql .= ' NOT NULL'; + } + + if ($after_field != '') + { + $sql .= ' AFTER ' . $this->db->_protect_identifiers($after_field); + } + + return $sql; + + } + + // -------------------------------------------------------------------- + + /** + * Rename a table + * + * Generates a platform-specific query so that a table can be renamed + * + * @access private + * @param string the old table name + * @param string the new table name + * @return string + */ + function _rename_table($table_name, $new_table_name) + { + $sql = 'ALTER TABLE '.$this->db->_protect_identifiers($table_name)." RENAME TO ".$this->db->_protect_identifiers($new_table_name); + return $sql; + } +} + +/* End of file sqlite_forge.php */ +/* Location: ./system/database/drivers/sqlite/sqlite_forge.php */ \ No newline at end of file diff --git a/system/database/drivers/sqlite/sqlite_result.php b/system/database/drivers/sqlite/sqlite_result.php new file mode 100644 index 0000000..7bd30db --- /dev/null +++ b/system/database/drivers/sqlite/sqlite_result.php @@ -0,0 +1,179 @@ +result_id); + } + + // -------------------------------------------------------------------- + + /** + * Number of fields in the result set + * + * @access public + * @return integer + */ + function num_fields() + { + return @sqlite_num_fields($this->result_id); + } + + // -------------------------------------------------------------------- + + /** + * Fetch Field Names + * + * Generates an array of column names + * + * @access public + * @return array + */ + function list_fields() + { + $field_names = array(); + for ($i = 0; $i < $this->num_fields(); $i++) + { + $field_names[] = sqlite_field_name($this->result_id, $i); + } + + return $field_names; + } + + // -------------------------------------------------------------------- + + /** + * Field data + * + * Generates an array of objects containing field meta-data + * + * @access public + * @return array + */ + function field_data() + { + $retval = array(); + for ($i = 0; $i < $this->num_fields(); $i++) + { + $F = new stdClass(); + $F->name = sqlite_field_name($this->result_id, $i); + $F->type = 'varchar'; + $F->max_length = 0; + $F->primary_key = 0; + $F->default = ''; + + $retval[] = $F; + } + + return $retval; + } + + // -------------------------------------------------------------------- + + /** + * Free the result + * + * @return null + */ + function free_result() + { + // Not implemented in SQLite + } + + // -------------------------------------------------------------------- + + /** + * 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 + * + * @access private + * @return array + */ + function _data_seek($n = 0) + { + return sqlite_seek($this->result_id, $n); + } + + // -------------------------------------------------------------------- + + /** + * Result - associative array + * + * Returns the result set as an array + * + * @access private + * @return array + */ + function _fetch_assoc() + { + return sqlite_fetch_array($this->result_id); + } + + // -------------------------------------------------------------------- + + /** + * Result - object + * + * Returns the result set as an object + * + * @access private + * @return object + */ + function _fetch_object() + { + if (function_exists('sqlite_fetch_object')) + { + return sqlite_fetch_object($this->result_id); + } + else + { + $arr = sqlite_fetch_array($this->result_id, SQLITE_ASSOC); + if (is_array($arr)) + { + $obj = (object) $arr; + return $obj; + } else { + return NULL; + } + } + } + +} + + +/* End of file sqlite_result.php */ +/* Location: ./system/database/drivers/sqlite/sqlite_result.php */ \ No newline at end of file diff --git a/system/database/drivers/sqlite/sqlite_utility.php b/system/database/drivers/sqlite/sqlite_utility.php new file mode 100644 index 0000000..508023e --- /dev/null +++ b/system/database/drivers/sqlite/sqlite_utility.php @@ -0,0 +1,96 @@ +db_debug) + { + return $this->db->display_error('db_unsuported_feature'); + } + return array(); + } + + // -------------------------------------------------------------------- + + /** + * Optimize table query + * + * Is optimization even supported in SQLite? + * + * @access private + * @param string the table name + * @return object + */ + function _optimize_table($table) + { + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Repair table query + * + * Are table repairs even supported in SQLite? + * + * @access private + * @param string the table name + * @return object + */ + function _repair_table($table) + { + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * SQLite Export + * + * @access private + * @param array Preferences + * @return mixed + */ + function _backup($params = array()) + { + // Currently unsupported + return $this->db->display_error('db_unsuported_feature'); + } +} + +/* End of file sqlite_utility.php */ +/* Location: ./system/database/drivers/sqlite/sqlite_utility.php */ \ No newline at end of file diff --git a/system/database/drivers/sqlsrv/index.html b/system/database/drivers/sqlsrv/index.html new file mode 100644 index 0000000..c942a79 --- /dev/null +++ b/system/database/drivers/sqlsrv/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/system/database/drivers/sqlsrv/sqlsrv_driver.php b/system/database/drivers/sqlsrv/sqlsrv_driver.php new file mode 100644 index 0000000..400fd31 --- /dev/null +++ b/system/database/drivers/sqlsrv/sqlsrv_driver.php @@ -0,0 +1,599 @@ +char_set)) ? 'UTF-8' : $this->char_set; + + $connection = array( + 'UID' => empty($this->username) ? '' : $this->username, + 'PWD' => empty($this->password) ? '' : $this->password, + 'Database' => $this->database, + 'ConnectionPooling' => $pooling ? 1 : 0, + 'CharacterSet' => $character_set, + 'ReturnDatesAsStrings' => 1 + ); + + // If the username and password are both empty, assume this is a + // 'Windows Authentication Mode' connection. + if(empty($connection['UID']) && empty($connection['PWD'])) { + unset($connection['UID'], $connection['PWD']); + } + + return sqlsrv_connect($this->hostname, $connection); + } + + // -------------------------------------------------------------------- + + /** + * Persistent database connection + * + * @access private called by the base class + * @return resource + */ + function db_pconnect() + { + $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 + * + * @access public + * @return void + */ + function reconnect() + { + // not implemented in MSSQL + } + + // -------------------------------------------------------------------- + + /** + * Select the database + * + * @access private called by the base class + * @return resource + */ + function db_select() + { + return $this->_execute('USE ' . $this->database); + } + + // -------------------------------------------------------------------- + + /** + * Set client character set + * + * @access public + * @param string + * @param string + * @return resource + */ + function db_set_charset($charset, $collation) + { + // @todo - add support if needed + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Execute the query + * + * @access private called by the base class + * @param string an SQL query + * @return resource + */ + function _execute($sql) + { + $sql = $this->_prep_query($sql); + return sqlsrv_query($this->conn_id, $sql, null, array( + 'Scrollable' => SQLSRV_CURSOR_STATIC, + 'SendStreamParamsAtExec' => true + )); + } + + // -------------------------------------------------------------------- + + /** + * Prep the query + * + * If needed, each database adapter can prep the query string + * + * @access private called by execute() + * @param string an SQL query + * @return string + */ + function _prep_query($sql) + { + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Begin Transaction + * + * @access public + * @return bool + */ + function trans_begin($test_mode = FALSE) + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + 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) ? TRUE : FALSE; + + return sqlsrv_begin_transaction($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * Commit Transaction + * + * @access public + * @return bool + */ + function trans_commit() + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + return TRUE; + } + + return sqlsrv_commit($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * Rollback Transaction + * + * @access public + * @return bool + */ + function trans_rollback() + { + if ( ! $this->trans_enabled) + { + return TRUE; + } + + // When transactions are nested we only begin/commit/rollback the outermost ones + if ($this->_trans_depth > 0) + { + return TRUE; + } + + return sqlsrv_rollback($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * Escape String + * + * @access public + * @param string + * @param bool whether or not the string will be used in a LIKE condition + * @return string + */ + function escape_str($str, $like = FALSE) + { + // Escape single quotes + return str_replace("'", "''", $str); + } + + // -------------------------------------------------------------------- + + /** + * Affected Rows + * + * @access public + * @return integer + */ + function affected_rows() + { + return @sqlrv_rows_affected($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * Insert ID + * + * Returns the last id created in the Identity column. + * + * @access public + * @return integer + */ + function insert_id() + { + return $this->query('select @@IDENTITY as insert_id')->row('insert_id'); + } + + // -------------------------------------------------------------------- + + /** + * Parse major version + * + * Grabs the major version number from the + * database server version string passed in. + * + * @access private + * @param string $version + * @return int16 major version number + */ + function _parse_major_version($version) + { + preg_match('/([0-9]+)\.([0-9]+)\.([0-9]+)/', $version, $ver_info); + return $ver_info[1]; // return the major version b/c that's all we're interested in. + } + + // -------------------------------------------------------------------- + + /** + * Version number query string + * + * @access public + * @return string + */ + function _version() + { + $info = sqlsrv_server_info($this->conn_id); + return sprintf("select '%s' as ver", $info['SQLServerVersion']); + } + + // -------------------------------------------------------------------- + + /** + * "Count All" query + * + * Generates a platform-specific query string that counts all records in + * the specified database + * + * @access public + * @param string + * @return string + */ + function count_all($table = '') + { + if ($table == '') + return '0'; + + $query = $this->query("SELECT COUNT(*) AS numrows FROM " . $this->dbprefix . $table); + + if ($query->num_rows() == 0) + return '0'; + + $row = $query->row(); + $this->_reset_select(); + return $row->numrows; + } + + // -------------------------------------------------------------------- + + /** + * List table query + * + * Generates a platform-specific query string so that the table names can be fetched + * + * @access private + * @param boolean + * @return string + */ + function _list_tables($prefix_limit = FALSE) + { + return "SELECT name FROM sysobjects WHERE type = 'U' ORDER BY name"; + } + + // -------------------------------------------------------------------- + + /** + * List column query + * + * Generates a platform-specific query string so that the column names can be fetched + * + * @access private + * @param string the table name + * @return string + */ + function _list_columns($table = '') + { + return "SELECT * FROM INFORMATION_SCHEMA.Columns WHERE TABLE_NAME = '".$this->_escape_table($table)."'"; + } + + // -------------------------------------------------------------------- + + /** + * Field data query + * + * Generates a platform-specific query so that the column data can be retrieved + * + * @access public + * @param string the table name + * @return object + */ + function _field_data($table) + { + return "SELECT TOP 1 * FROM " . $this->_escape_table($table); + } + + // -------------------------------------------------------------------- + + /** + * The error message string + * + * @access private + * @return string + */ + function _error_message() + { + $error = array_shift(sqlsrv_errors()); + return !empty($error['message']) ? $error['message'] : null; + } + + // -------------------------------------------------------------------- + + /** + * The error message number + * + * @access private + * @return integer + */ + function _error_number() + { + $error = array_shift(sqlsrv_errors()); + return isset($error['SQLSTATE']) ? $error['SQLSTATE'] : null; + } + + // -------------------------------------------------------------------- + + /** + * Escape Table Name + * + * This function adds backticks if the table name has a period + * in it. Some DBs will get cranky unless periods are escaped + * + * @access private + * @param string the table name + * @return string + */ + function _escape_table($table) + { + return $table; + } + + + /** + * Escape the SQL Identifiers + * + * This function escapes column and table names + * + * @access private + * @param string + * @return string + */ + function _escape_identifiers($item) + { + return $item; + } + + // -------------------------------------------------------------------- + + /** + * From Tables + * + * This function implicitly groups FROM tables so there is no confusion + * about operator precedence in harmony with SQL standards + * + * @access public + * @param type + * @return type + */ + function _from_tables($tables) + { + if ( ! is_array($tables)) + { + $tables = array($tables); + } + + return implode(', ', $tables); + } + + // -------------------------------------------------------------------- + + /** + * Insert statement + * + * Generates a platform-specific insert string from the supplied data + * + * @access public + * @param string the table name + * @param array the insert keys + * @param array the insert values + * @return string + */ + function _insert($table, $keys, $values) + { + return "INSERT INTO ".$this->_escape_table($table)." (".implode(', ', $keys).") VALUES (".implode(', ', $values).")"; + } + + // -------------------------------------------------------------------- + + /** + * Update statement + * + * Generates a platform-specific update string from the supplied data + * + * @access public + * @param string the table name + * @param array the update data + * @param array the where clause + * @param array the orderby clause + * @param array the limit clause + * @return string + */ + function _update($table, $values, $where) + { + foreach($values as $key => $val) + { + $valstr[] = $key." = ".$val; + } + + return "UPDATE ".$this->_escape_table($table)." SET ".implode(', ', $valstr)." WHERE ".implode(" ", $where); + } + + // -------------------------------------------------------------------- + + /** + * Truncate statement + * + * Generates a platform-specific truncate string from the supplied data + * If the database does not support the truncate() command + * This function maps to "DELETE FROM table" + * + * @access public + * @param string the table name + * @return string + */ + function _truncate($table) + { + return "TRUNCATE ".$table; + } + + // -------------------------------------------------------------------- + + /** + * Delete statement + * + * Generates a platform-specific delete string from the supplied data + * + * @access public + * @param string the table name + * @param array the where clause + * @param string the limit clause + * @return string + */ + function _delete($table, $where) + { + return "DELETE FROM ".$this->_escape_table($table)." WHERE ".implode(" ", $where); + } + + // -------------------------------------------------------------------- + + /** + * Limit string + * + * Generates a platform-specific LIMIT clause + * + * @access public + * @param string the sql query string + * @param integer the number of rows to limit the query to + * @param integer the offset value + * @return string + */ + function _limit($sql, $limit, $offset) + { + $i = $limit + $offset; + + return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.$i.' ', $sql); + } + + // -------------------------------------------------------------------- + + /** + * Close DB Connection + * + * @access public + * @param resource + * @return void + */ + function _close($conn_id) + { + @sqlsrv_close($conn_id); + } + +} + + + +/* End of file mssql_driver.php */ +/* Location: ./system/database/drivers/mssql/mssql_driver.php */ \ No newline at end of file diff --git a/system/database/drivers/sqlsrv/sqlsrv_forge.php b/system/database/drivers/sqlsrv/sqlsrv_forge.php new file mode 100644 index 0000000..cc88ec5 --- /dev/null +++ b/system/database/drivers/sqlsrv/sqlsrv_forge.php @@ -0,0 +1,248 @@ +db->_escape_identifiers($table); + } + + // -------------------------------------------------------------------- + + /** + * Create Table + * + * @access private + * @param string the table name + * @param array the fields + * @param mixed primary key(s) + * @param mixed key(s) + * @param boolean should 'IF NOT EXISTS' be added to the SQL + * @return bool + */ + function _create_table($table, $fields, $primary_keys, $keys, $if_not_exists) + { + $sql = 'CREATE TABLE '; + + if ($if_not_exists === TRUE) + { + $sql .= 'IF NOT EXISTS '; + } + + $sql .= $this->db->_escape_identifiers($table)." ("; + $current_field_count = 0; + + foreach ($fields as $field=>$attributes) + { + // Numeric field names aren't allowed in databases, so if the key is + // numeric, we know it was assigned by PHP and the developer manually + // entered the field information, so we'll simply add it to the list + if (is_numeric($field)) + { + $sql .= "\n\t$attributes"; + } + else + { + $attributes = array_change_key_case($attributes, CASE_UPPER); + + $sql .= "\n\t".$this->db->_protect_identifiers($field); + + $sql .= ' '.$attributes['TYPE']; + + if (array_key_exists('CONSTRAINT', $attributes)) + { + $sql .= '('.$attributes['CONSTRAINT'].')'; + } + + if (array_key_exists('UNSIGNED', $attributes) && $attributes['UNSIGNED'] === TRUE) + { + $sql .= ' UNSIGNED'; + } + + if (array_key_exists('DEFAULT', $attributes)) + { + $sql .= ' DEFAULT \''.$attributes['DEFAULT'].'\''; + } + + if (array_key_exists('NULL', $attributes) && $attributes['NULL'] === TRUE) + { + $sql .= ' NULL'; + } + else + { + $sql .= ' NOT NULL'; + } + + if (array_key_exists('AUTO_INCREMENT', $attributes) && $attributes['AUTO_INCREMENT'] === TRUE) + { + $sql .= ' AUTO_INCREMENT'; + } + } + + // don't add a comma on the end of the last field + if (++$current_field_count < count($fields)) + { + $sql .= ','; + } + } + + if (count($primary_keys) > 0) + { + $primary_keys = $this->db->_protect_identifiers($primary_keys); + $sql .= ",\n\tPRIMARY KEY (" . implode(', ', $primary_keys) . ")"; + } + + if (is_array($keys) && count($keys) > 0) + { + foreach ($keys as $key) + { + if (is_array($key)) + { + $key = $this->db->_protect_identifiers($key); + } + else + { + $key = array($this->db->_protect_identifiers($key)); + } + + $sql .= ",\n\tFOREIGN KEY (" . implode(', ', $key) . ")"; + } + } + + $sql .= "\n)"; + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Alter table query + * + * Generates a platform-specific query so that a table can be altered + * Called by add_column(), drop_column(), and column_alter(), + * + * @access private + * @param string the ALTER type (ADD, DROP, CHANGE) + * @param string the column name + * @param string the table name + * @param string the column definition + * @param string the default value + * @param boolean should 'NOT NULL' be added + * @param string the field after which we should add the new field + * @return object + */ + function _alter_table($alter_type, $table, $column_name, $column_definition = '', $default_value = '', $null = '', $after_field = '') + { + $sql = 'ALTER TABLE '.$this->db->_protect_identifiers($table)." $alter_type ".$this->db->_protect_identifiers($column_name); + + // DROP has everything it needs now. + if ($alter_type == 'DROP') + { + return $sql; + } + + $sql .= " $column_definition"; + + if ($default_value != '') + { + $sql .= " DEFAULT \"$default_value\""; + } + + if ($null === NULL) + { + $sql .= ' NULL'; + } + else + { + $sql .= ' NOT NULL'; + } + + if ($after_field != '') + { + $sql .= ' AFTER ' . $this->db->_protect_identifiers($after_field); + } + + return $sql; + + } + + // -------------------------------------------------------------------- + + /** + * Rename a table + * + * Generates a platform-specific query so that a table can be renamed + * + * @access private + * @param string the old table name + * @param string the new table name + * @return string + */ + function _rename_table($table_name, $new_table_name) + { + // I think this syntax will work, but can find little documentation on renaming tables in MSSQL + $sql = 'ALTER TABLE '.$this->db->_protect_identifiers($table_name)." RENAME TO ".$this->db->_protect_identifiers($new_table_name); + return $sql; + } + +} + +/* End of file mssql_forge.php */ +/* Location: ./system/database/drivers/mssql/mssql_forge.php */ \ No newline at end of file diff --git a/system/database/drivers/sqlsrv/sqlsrv_result.php b/system/database/drivers/sqlsrv/sqlsrv_result.php new file mode 100644 index 0000000..bf0abd1 --- /dev/null +++ b/system/database/drivers/sqlsrv/sqlsrv_result.php @@ -0,0 +1,169 @@ +result_id); + } + + // -------------------------------------------------------------------- + + /** + * Number of fields in the result set + * + * @access public + * @return integer + */ + function num_fields() + { + return @sqlsrv_num_fields($this->result_id); + } + + // -------------------------------------------------------------------- + + /** + * Fetch Field Names + * + * Generates an array of column names + * + * @access public + * @return array + */ + function list_fields() + { + $field_names = array(); + foreach(sqlsrv_field_metadata($this->result_id) as $offset => $field) + { + $field_names[] = $field['Name']; + } + + return $field_names; + } + + // -------------------------------------------------------------------- + + /** + * Field data + * + * Generates an array of objects containing field meta-data + * + * @access public + * @return array + */ + function field_data() + { + $retval = array(); + foreach(sqlsrv_field_metadata($this->result_id) as $offset => $field) + { + $F = new stdClass(); + $F->name = $field['Name']; + $F->type = $field['Type']; + $F->max_length = $field['Size']; + $F->primary_key = 0; + $F->default = ''; + + $retval[] = $F; + } + + return $retval; + } + + // -------------------------------------------------------------------- + + /** + * Free the result + * + * @return null + */ + function free_result() + { + if (is_resource($this->result_id)) + { + sqlsrv_free_stmt($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 + * + * @access private + * @return array + */ + function _data_seek($n = 0) + { + // Not implemented + } + + // -------------------------------------------------------------------- + + /** + * Result - associative array + * + * Returns the result set as an array + * + * @access private + * @return array + */ + function _fetch_assoc() + { + return sqlsrv_fetch_array($this->result_id, SQLSRV_FETCH_ASSOC); + } + + // -------------------------------------------------------------------- + + /** + * Result - object + * + * Returns the result set as an object + * + * @access private + * @return object + */ + function _fetch_object() + { + return sqlsrv_fetch_object($this->result_id); + } + +} + + +/* End of file mssql_result.php */ +/* Location: ./system/database/drivers/mssql/mssql_result.php */ \ No newline at end of file diff --git a/system/database/drivers/sqlsrv/sqlsrv_utility.php b/system/database/drivers/sqlsrv/sqlsrv_utility.php new file mode 100644 index 0000000..13a1850 --- /dev/null +++ b/system/database/drivers/sqlsrv/sqlsrv_utility.php @@ -0,0 +1,88 @@ +db->display_error('db_unsuported_feature'); + } + +} + +/* End of file mssql_utility.php */ +/* Location: ./system/database/drivers/mssql/mssql_utility.php */ \ No newline at end of file diff --git a/system/database/index.html b/system/database/index.html new file mode 100644 index 0000000..c942a79 --- /dev/null +++ b/system/database/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/system/fonts/index.html b/system/fonts/index.html new file mode 100644 index 0000000..c942a79 --- /dev/null +++ b/system/fonts/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/system/fonts/texb.ttf b/system/fonts/texb.ttf new file mode 100644 index 0000000000000000000000000000000000000000..383c88b86b7c17e2e284732af48b2bfc359647ae GIT binary patch literal 143830 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 z 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 n`i '', 'img_path' => '', 'img_url' => '', 'img_width' => '150', 'img_height' => '30', 'font_path' => '', 'expiration' => 7200); + + foreach ($defaults as $key => $val) + { + if ( ! is_array($data)) + { + if ( ! isset($$key) OR $$key == '') + { + $$key = $val; + } + } + else + { + $$key = ( ! isset($data[$key])) ? $val : $data[$key]; + } + } + + if ($img_path == '' OR $img_url == '') + { + return FALSE; + } + + if ( ! @is_dir($img_path)) + { + return FALSE; + } + + if ( ! is_writable($img_path)) + { + return FALSE; + } + + if ( ! extension_loaded('gd')) + { + return FALSE; + } + + // ----------------------------------- + // Remove old images + // ----------------------------------- + + list($usec, $sec) = explode(" ", microtime()); + $now = ((float)$usec + (float)$sec); + + $current_dir = @opendir($img_path); + + while ($filename = @readdir($current_dir)) + { + if ($filename != "." and $filename != ".." and $filename != "index.html") + { + $name = str_replace(".jpg", "", $filename); + + if (($name + $expiration) < $now) + { + @unlink($img_path.$filename); + } + } + } + + @closedir($current_dir); + + // ----------------------------------- + // Do we have a "word" yet? + // ----------------------------------- + + if ($word == '') + { + $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + + $str = ''; + for ($i = 0; $i < 8; $i++) + { + $str .= substr($pool, mt_rand(0, strlen($pool) -1), 1); + } + + $word = $str; + } + + // ----------------------------------- + // Determine angle and position + // ----------------------------------- + + $length = strlen($word); + $angle = ($length >= 6) ? rand(-($length-6), ($length-6)) : 0; + $x_axis = rand(6, (360/$length)-16); + $y_axis = ($angle >= 0 ) ? rand($img_height, $img_width) : rand(6, $img_height); + + // ----------------------------------- + // Create image + // ----------------------------------- + + // PHP.net recommends imagecreatetruecolor(), but it isn't always available + if (function_exists('imagecreatetruecolor')) + { + $im = imagecreatetruecolor($img_width, $img_height); + } + else + { + $im = imagecreate($img_width, $img_height); + } + + // ----------------------------------- + // Assign colors + // ----------------------------------- + + $bg_color = imagecolorallocate ($im, 255, 255, 255); + $border_color = imagecolorallocate ($im, 153, 102, 102); + $text_color = imagecolorallocate ($im, 204, 153, 153); + $grid_color = imagecolorallocate($im, 255, 182, 182); + $shadow_color = imagecolorallocate($im, 255, 240, 240); + + // ----------------------------------- + // Create the rectangle + // ----------------------------------- + + ImageFilledRectangle($im, 0, 0, $img_width, $img_height, $bg_color); + + // ----------------------------------- + // Create the spiral pattern + // ----------------------------------- + + $theta = 1; + $thetac = 7; + $radius = 16; + $circles = 20; + $points = 32; + + for ($i = 0; $i < ($circles * $points) - 1; $i++) + { + $theta = $theta + $thetac; + $rad = $radius * ($i / $points ); + $x = ($rad * cos($theta)) + $x_axis; + $y = ($rad * sin($theta)) + $y_axis; + $theta = $theta + $thetac; + $rad1 = $radius * (($i + 1) / $points); + $x1 = ($rad1 * cos($theta)) + $x_axis; + $y1 = ($rad1 * sin($theta )) + $y_axis; + imageline($im, $x, $y, $x1, $y1, $grid_color); + $theta = $theta - $thetac; + } + + // ----------------------------------- + // Write the text + // ----------------------------------- + + $use_font = ($font_path != '' AND file_exists($font_path) AND function_exists('imagettftext')) ? TRUE : FALSE; + + if ($use_font == FALSE) + { + $font_size = 5; + $x = rand(0, $img_width/($length/3)); + $y = 0; + } + else + { + $font_size = 16; + $x = rand(0, $img_width/($length/1.5)); + $y = $font_size+2; + } + + for ($i = 0; $i < strlen($word); $i++) + { + if ($use_font == FALSE) + { + $y = rand(0 , $img_height/2); + imagestring($im, $font_size, $x, $y, substr($word, $i, 1), $text_color); + $x += ($font_size*2); + } + else + { + $y = rand($img_height/2, $img_height-3); + imagettftext($im, $font_size, $angle, $x, $y, $text_color, $font_path, substr($word, $i, 1)); + $x += $font_size; + } + } + + + // ----------------------------------- + // Create the border + // ----------------------------------- + + imagerectangle($im, 0, 0, $img_width-1, $img_height-1, $border_color); + + // ----------------------------------- + // Generate the image + // ----------------------------------- + + $img_name = $now.'.jpg'; + + ImageJPEG($im, $img_path.$img_name); + + $img = "\""; + + ImageDestroy($im); + + return array('word' => $word, 'time' => $now, 'image' => $img); + } +} + +// ------------------------------------------------------------------------ + +/* End of file captcha_helper.php */ +/* Location: ./system/heleprs/captcha_helper.php */ \ No newline at end of file diff --git a/system/helpers/cookie_helper.php b/system/helpers/cookie_helper.php new file mode 100644 index 0000000..7cee028 --- /dev/null +++ b/system/helpers/cookie_helper.php @@ -0,0 +1,103 @@ +input->set_cookie($name, $value, $expire, $domain, $path, $prefix, $secure); + } +} + +// -------------------------------------------------------------------- + +/** + * Fetch an item from the COOKIE array + * + * @access public + * @param string + * @param bool + * @return mixed + */ +if ( ! function_exists('get_cookie')) +{ + function get_cookie($index = '', $xss_clean = FALSE) + { + $CI =& get_instance(); + + $prefix = ''; + + if ( ! isset($_COOKIE[$index]) && config_item('cookie_prefix') != '') + { + $prefix = config_item('cookie_prefix'); + } + + return $CI->input->cookie($prefix.$index, $xss_clean); + } +} + +// -------------------------------------------------------------------- + +/** + * Delete a COOKIE + * + * @param mixed + * @param string the cookie domain. Usually: .yourdomain.com + * @param string the cookie path + * @param string the cookie prefix + * @return void + */ +if ( ! function_exists('delete_cookie')) +{ + function delete_cookie($name = '', $domain = '', $path = '/', $prefix = '') + { + set_cookie($name, '', '', $domain, $path, $prefix); + } +} + + +/* End of file cookie_helper.php */ +/* Location: ./system/helpers/cookie_helper.php */ \ No newline at end of file diff --git a/system/helpers/date_helper.php b/system/helpers/date_helper.php new file mode 100644 index 0000000..0aeb7fa --- /dev/null +++ b/system/helpers/date_helper.php @@ -0,0 +1,611 @@ +config->item('time_reference')) == 'gmt') + { + $now = time(); + $system_time = mktime(gmdate("H", $now), gmdate("i", $now), gmdate("s", $now), gmdate("m", $now), gmdate("d", $now), gmdate("Y", $now)); + + if (strlen($system_time) < 10) + { + $system_time = time(); + log_message('error', 'The Date class could not set a proper GMT timestamp so the local time() value was used.'); + } + + return $system_time; + } + else + { + return time(); + } + } +} + +// ------------------------------------------------------------------------ + +/** + * Convert MySQL Style Datecodes + * + * This function is identical to PHPs date() function, + * except that it allows date codes to be formatted using + * the MySQL style, where each code letter is preceded + * with a percent sign: %Y %m %d etc... + * + * The benefit of doing dates this way is that you don't + * have to worry about escaping your text letters that + * match the date codes. + * + * @access public + * @param string + * @param integer + * @return integer + */ +if ( ! function_exists('mdate')) +{ + function mdate($datestr = '', $time = '') + { + if ($datestr == '') + return ''; + + if ($time == '') + $time = now(); + + $datestr = str_replace('%\\', '', preg_replace("/([a-z]+?){1}/i", "\\\\\\1", $datestr)); + return date($datestr, $time); + } +} + +// ------------------------------------------------------------------------ + +/** + * Standard Date + * + * Returns a date formatted according to the submitted standard. + * + * @access public + * @param string the chosen format + * @param integer Unix timestamp + * @return string + */ +if ( ! function_exists('standard_date')) +{ + function standard_date($fmt = 'DATE_RFC822', $time = '') + { + $formats = array( + 'DATE_ATOM' => '%Y-%m-%dT%H:%i:%s%Q', + 'DATE_COOKIE' => '%l, %d-%M-%y %H:%i:%s UTC', + 'DATE_ISO8601' => '%Y-%m-%dT%H:%i:%s%Q', + 'DATE_RFC822' => '%D, %d %M %y %H:%i:%s %O', + 'DATE_RFC850' => '%l, %d-%M-%y %H:%i:%s UTC', + 'DATE_RFC1036' => '%D, %d %M %y %H:%i:%s %O', + 'DATE_RFC1123' => '%D, %d %M %Y %H:%i:%s %O', + 'DATE_RSS' => '%D, %d %M %Y %H:%i:%s %O', + 'DATE_W3C' => '%Y-%m-%dT%H:%i:%s%Q' + ); + + if ( ! isset($formats[$fmt])) + { + return FALSE; + } + + return mdate($formats[$fmt], $time); + } +} + +// ------------------------------------------------------------------------ + +/** + * Timespan + * + * Returns a span of seconds in this format: + * 10 days 14 hours 36 minutes 47 seconds + * + * @access public + * @param integer a number of seconds + * @param integer Unix timestamp + * @return integer + */ +if ( ! function_exists('timespan')) +{ + function timespan($seconds = 1, $time = '') + { + $CI =& get_instance(); + $CI->lang->load('date'); + + if ( ! is_numeric($seconds)) + { + $seconds = 1; + } + + if ( ! is_numeric($time)) + { + $time = time(); + } + + if ($time <= $seconds) + { + $seconds = 1; + } + else + { + $seconds = $time - $seconds; + } + + $str = ''; + $years = floor($seconds / 31536000); + + if ($years > 0) + { + $str .= $years.' '.$CI->lang->line((($years > 1) ? 'date_years' : 'date_year')).', '; + } + + $seconds -= $years * 31536000; + $months = floor($seconds / 2628000); + + if ($years > 0 OR $months > 0) + { + if ($months > 0) + { + $str .= $months.' '.$CI->lang->line((($months > 1) ? 'date_months' : 'date_month')).', '; + } + + $seconds -= $months * 2628000; + } + + $weeks = floor($seconds / 604800); + + if ($years > 0 OR $months > 0 OR $weeks > 0) + { + if ($weeks > 0) + { + $str .= $weeks.' '.$CI->lang->line((($weeks > 1) ? 'date_weeks' : 'date_week')).', '; + } + + $seconds -= $weeks * 604800; + } + + $days = floor($seconds / 86400); + + if ($months > 0 OR $weeks > 0 OR $days > 0) + { + if ($days > 0) + { + $str .= $days.' '.$CI->lang->line((($days > 1) ? 'date_days' : 'date_day')).', '; + } + + $seconds -= $days * 86400; + } + + $hours = floor($seconds / 3600); + + if ($days > 0 OR $hours > 0) + { + if ($hours > 0) + { + $str .= $hours.' '.$CI->lang->line((($hours > 1) ? 'date_hours' : 'date_hour')).', '; + } + + $seconds -= $hours * 3600; + } + + $minutes = floor($seconds / 60); + + if ($days > 0 OR $hours > 0 OR $minutes > 0) + { + if ($minutes > 0) + { + $str .= $minutes.' '.$CI->lang->line((($minutes > 1) ? 'date_minutes' : 'date_minute')).', '; + } + + $seconds -= $minutes * 60; + } + + if ($str == '') + { + $str .= $seconds.' '.$CI->lang->line((($seconds > 1) ? 'date_seconds' : 'date_second')).', '; + } + + return substr(trim($str), 0, -1); + } +} + +// ------------------------------------------------------------------------ + +/** + * Number of days in a month + * + * Takes a month/year as input and returns the number of days + * for the given month/year. Takes leap years into consideration. + * + * @access public + * @param integer a numeric month + * @param integer a numeric year + * @return integer + */ +if ( ! function_exists('days_in_month')) +{ + function days_in_month($month = 0, $year = '') + { + if ($month < 1 OR $month > 12) + { + return 0; + } + + if ( ! is_numeric($year) OR strlen($year) != 4) + { + $year = date('Y'); + } + + if ($month == 2) + { + if ($year % 400 == 0 OR ($year % 4 == 0 AND $year % 100 != 0)) + { + return 29; + } + } + + $days_in_month = array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); + return $days_in_month[$month - 1]; + } +} + +// ------------------------------------------------------------------------ + +/** + * Converts a local Unix timestamp to GMT + * + * @access public + * @param integer Unix timestamp + * @return integer + */ +if ( ! function_exists('local_to_gmt')) +{ + function local_to_gmt($time = '') + { + if ($time == '') + $time = time(); + + return mktime( gmdate("H", $time), gmdate("i", $time), gmdate("s", $time), gmdate("m", $time), gmdate("d", $time), gmdate("Y", $time)); + } +} + +// ------------------------------------------------------------------------ + +/** + * Converts GMT time to a localized value + * + * Takes a Unix timestamp (in GMT) as input, and returns + * at the local value based on the timezone and DST setting + * submitted + * + * @access public + * @param integer Unix timestamp + * @param string timezone + * @param bool whether DST is active + * @return integer + */ +if ( ! function_exists('gmt_to_local')) +{ + function gmt_to_local($time = '', $timezone = 'UTC', $dst = FALSE) + { + if ($time == '') + { + return now(); + } + + $time += timezones($timezone) * 3600; + + if ($dst == TRUE) + { + $time += 3600; + } + + return $time; + } +} + +// ------------------------------------------------------------------------ + +/** + * Converts a MySQL Timestamp to Unix + * + * @access public + * @param integer Unix timestamp + * @return integer + */ +if ( ! function_exists('mysql_to_unix')) +{ + function mysql_to_unix($time = '') + { + // We'll remove certain characters for backward compatibility + // since the formatting changed with MySQL 4.1 + // YYYY-MM-DD HH:MM:SS + + $time = str_replace('-', '', $time); + $time = str_replace(':', '', $time); + $time = str_replace(' ', '', $time); + + // YYYYMMDDHHMMSS + return mktime( + substr($time, 8, 2), + substr($time, 10, 2), + substr($time, 12, 2), + substr($time, 4, 2), + substr($time, 6, 2), + substr($time, 0, 4) + ); + } +} + +// ------------------------------------------------------------------------ + +/** + * Unix to "Human" + * + * Formats Unix timestamp to the following prototype: 2006-08-21 11:35 PM + * + * @access public + * @param integer Unix timestamp + * @param bool whether to show seconds + * @param string format: us or euro + * @return string + */ +if ( ! function_exists('unix_to_human')) +{ + function unix_to_human($time = '', $seconds = FALSE, $fmt = 'us') + { + $r = date('Y', $time).'-'.date('m', $time).'-'.date('d', $time).' '; + + if ($fmt == 'us') + { + $r .= date('h', $time).':'.date('i', $time); + } + else + { + $r .= date('H', $time).':'.date('i', $time); + } + + if ($seconds) + { + $r .= ':'.date('s', $time); + } + + if ($fmt == 'us') + { + $r .= ' '.date('A', $time); + } + + return $r; + } +} + +// ------------------------------------------------------------------------ + +/** + * Convert "human" date to GMT + * + * Reverses the above process + * + * @access public + * @param string format: us or euro + * @return integer + */ +if ( ! function_exists('human_to_unix')) +{ + function human_to_unix($datestr = '') + { + if ($datestr == '') + { + return FALSE; + } + + $datestr = trim($datestr); + $datestr = preg_replace("/\040+/", ' ', $datestr); + + if ( ! preg_match('/^[0-9]{2,4}\-[0-9]{1,2}\-[0-9]{1,2}\s[0-9]{1,2}:[0-9]{1,2}(?::[0-9]{1,2})?(?:\s[AP]M)?$/i', $datestr)) + { + return FALSE; + } + + $split = explode(' ', $datestr); + + $ex = explode("-", $split['0']); + + $year = (strlen($ex['0']) == 2) ? '20'.$ex['0'] : $ex['0']; + $month = (strlen($ex['1']) == 1) ? '0'.$ex['1'] : $ex['1']; + $day = (strlen($ex['2']) == 1) ? '0'.$ex['2'] : $ex['2']; + + $ex = explode(":", $split['1']); + + $hour = (strlen($ex['0']) == 1) ? '0'.$ex['0'] : $ex['0']; + $min = (strlen($ex['1']) == 1) ? '0'.$ex['1'] : $ex['1']; + + if (isset($ex['2']) && preg_match('/[0-9]{1,2}/', $ex['2'])) + { + $sec = (strlen($ex['2']) == 1) ? '0'.$ex['2'] : $ex['2']; + } + else + { + // Unless specified, seconds get set to zero. + $sec = '00'; + } + + if (isset($split['2'])) + { + $ampm = strtolower($split['2']); + + if (substr($ampm, 0, 1) == 'p' AND $hour < 12) + $hour = $hour + 12; + + if (substr($ampm, 0, 1) == 'a' AND $hour == 12) + $hour = '00'; + + if (strlen($hour) == 1) + $hour = '0'.$hour; + } + + return mktime($hour, $min, $sec, $month, $day, $year); + } +} + +// ------------------------------------------------------------------------ + +/** + * Timezone Menu + * + * Generates a drop-down menu of timezones. + * + * @access public + * @param string timezone + * @param string classname + * @param string menu name + * @return string + */ +if ( ! function_exists('timezone_menu')) +{ + function timezone_menu($default = 'UTC', $class = "", $name = 'timezones') + { + $CI =& get_instance(); + $CI->lang->load('date'); + + if ($default == 'GMT') + $default = 'UTC'; + + $menu = '"; + + return $menu; + } +} + +// ------------------------------------------------------------------------ + +/** + * Timezones + * + * Returns an array of timezones. This is a helper function + * for various other ones in this library + * + * @access public + * @param string timezone + * @return string + */ +if ( ! function_exists('timezones')) +{ + function timezones($tz = '') + { + // Note: Don't change the order of these even though + // some items appear to be in the wrong order + + $zones = array( + 'UM12' => -12, + 'UM11' => -11, + 'UM10' => -10, + 'UM95' => -9.5, + 'UM9' => -9, + 'UM8' => -8, + 'UM7' => -7, + 'UM6' => -6, + 'UM5' => -5, + 'UM45' => -4.5, + 'UM4' => -4, + 'UM35' => -3.5, + 'UM3' => -3, + 'UM2' => -2, + 'UM1' => -1, + 'UTC' => 0, + 'UP1' => +1, + 'UP2' => +2, + 'UP3' => +3, + 'UP35' => +3.5, + 'UP4' => +4, + 'UP45' => +4.5, + 'UP5' => +5, + 'UP55' => +5.5, + 'UP575' => +5.75, + 'UP6' => +6, + 'UP65' => +6.5, + 'UP7' => +7, + 'UP8' => +8, + 'UP875' => +8.75, + 'UP9' => +9, + 'UP95' => +9.5, + 'UP10' => +10, + 'UP105' => +10.5, + 'UP11' => +11, + 'UP115' => +11.5, + 'UP12' => +12, + 'UP1275' => +12.75, + 'UP13' => +13, + 'UP14' => +14 + ); + + if ($tz == '') + { + return $zones; + } + + if ($tz == 'GMT') + $tz = 'UTC'; + + return ( ! isset($zones[$tz])) ? 0 : $zones[$tz]; + } +} + + +/* End of file date_helper.php */ +/* Location: ./system/helpers/date_helper.php */ \ No newline at end of file diff --git a/system/helpers/directory_helper.php b/system/helpers/directory_helper.php new file mode 100644 index 0000000..38347fa --- /dev/null +++ b/system/helpers/directory_helper.php @@ -0,0 +1,80 @@ + 0) && @is_dir($source_dir.$file)) + { + $filedata[$file] = directory_map($source_dir.$file.DIRECTORY_SEPARATOR, $new_depth, $hidden); + } + else + { + $filedata[] = $file; + } + } + + closedir($fp); + return $filedata; + } + + return FALSE; + } +} + + +/* End of file directory_helper.php */ +/* Location: ./system/helpers/directory_helper.php */ \ No newline at end of file diff --git a/system/helpers/download_helper.php b/system/helpers/download_helper.php new file mode 100644 index 0000000..1145688 --- /dev/null +++ b/system/helpers/download_helper.php @@ -0,0 +1,107 @@ + 0) + { + $data =& fread($fp, filesize($file)); + } + + flock($fp, LOCK_UN); + fclose($fp); + + return $data; + } +} + +// ------------------------------------------------------------------------ + +/** + * Write File + * + * Writes data to the file specified in the path. + * Creates a new file if non-existent. + * + * @access public + * @param string path to file + * @param string file data + * @return bool + */ +if ( ! function_exists('write_file')) +{ + function write_file($path, $data, $mode = FOPEN_WRITE_CREATE_DESTRUCTIVE) + { + if ( ! $fp = @fopen($path, $mode)) + { + return FALSE; + } + + flock($fp, LOCK_EX); + fwrite($fp, $data); + flock($fp, LOCK_UN); + fclose($fp); + + return TRUE; + } +} + +// ------------------------------------------------------------------------ + +/** + * Delete Files + * + * Deletes all files contained in the supplied directory path. + * Files must be writable or owned by the system in order to be deleted. + * If the second parameter is set to TRUE, any directories contained + * within the supplied base directory will be nuked as well. + * + * @access public + * @param string path to file + * @param bool whether to delete any directories found in the path + * @return bool + */ +if ( ! function_exists('delete_files')) +{ + function delete_files($path, $del_dir = FALSE, $level = 0) + { + // Trim the trailing slash + $path = rtrim($path, DIRECTORY_SEPARATOR); + + if ( ! $current_dir = @opendir($path)) + { + return FALSE; + } + + while (FALSE !== ($filename = @readdir($current_dir))) + { + if ($filename != "." and $filename != "..") + { + if (is_dir($path.DIRECTORY_SEPARATOR.$filename)) + { + // Ignore empty folders + if (substr($filename, 0, 1) != '.') + { + delete_files($path.DIRECTORY_SEPARATOR.$filename, $del_dir, $level + 1); + } + } + else + { + unlink($path.DIRECTORY_SEPARATOR.$filename); + } + } + } + @closedir($current_dir); + + if ($del_dir == TRUE AND $level > 0) + { + return @rmdir($path); + } + + return TRUE; + } +} + +// ------------------------------------------------------------------------ + +/** + * Get Filenames + * + * Reads the specified directory and builds an array containing the filenames. + * Any sub-folders contained within the specified path are read as well. + * + * @access public + * @param string path to source + * @param bool whether to include the path as part of the filename + * @param bool internal variable to determine recursion status - do not use in calls + * @return array + */ +if ( ! function_exists('get_filenames')) +{ + function get_filenames($source_dir, $include_path = FALSE, $_recursion = FALSE) + { + static $_filedata = array(); + + if ($fp = @opendir($source_dir)) + { + // reset the array and make sure $source_dir has a trailing slash on the initial call + if ($_recursion === FALSE) + { + $_filedata = array(); + $source_dir = rtrim(realpath($source_dir), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR; + } + + while (FALSE !== ($file = readdir($fp))) + { + if (@is_dir($source_dir.$file) && strncmp($file, '.', 1) !== 0) + { + get_filenames($source_dir.$file.DIRECTORY_SEPARATOR, $include_path, TRUE); + } + elseif (strncmp($file, '.', 1) !== 0) + { + $_filedata[] = ($include_path == TRUE) ? $source_dir.$file : $file; + } + } + return $_filedata; + } + else + { + return FALSE; + } + } +} + +// -------------------------------------------------------------------- + +/** + * Get Directory File Information + * + * Reads the specified directory and builds an array containing the filenames, + * filesize, dates, and permissions + * + * Any sub-folders contained within the specified path are read as well. + * + * @access public + * @param string path to source + * @param bool Look only at the top level directory specified? + * @param bool internal variable to determine recursion status - do not use in calls + * @return array + */ +if ( ! function_exists('get_dir_file_info')) +{ + function get_dir_file_info($source_dir, $top_level_only = TRUE, $_recursion = FALSE) + { + static $_filedata = array(); + $relative_path = $source_dir; + + if ($fp = @opendir($source_dir)) + { + // reset the array and make sure $source_dir has a trailing slash on the initial call + if ($_recursion === FALSE) + { + $_filedata = array(); + $source_dir = rtrim(realpath($source_dir), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR; + } + + // foreach (scandir($source_dir, 1) as $file) // In addition to being PHP5+, scandir() is simply not as fast + while (FALSE !== ($file = readdir($fp))) + { + if (@is_dir($source_dir.$file) AND strncmp($file, '.', 1) !== 0 AND $top_level_only === FALSE) + { + get_dir_file_info($source_dir.$file.DIRECTORY_SEPARATOR, $top_level_only, TRUE); + } + elseif (strncmp($file, '.', 1) !== 0) + { + $_filedata[$file] = get_file_info($source_dir.$file); + $_filedata[$file]['relative_path'] = $relative_path; + } + } + + return $_filedata; + } + else + { + return FALSE; + } + } +} + +// -------------------------------------------------------------------- + +/** +* Get File Info +* +* Given a file and path, returns the name, path, size, date modified +* Second parameter allows you to explicitly declare what information you want returned +* Options are: name, server_path, size, date, readable, writable, executable, fileperms +* Returns FALSE if the file cannot be found. +* +* @access public +* @param string path to file +* @param mixed array or comma separated string of information returned +* @return array +*/ +if ( ! function_exists('get_file_info')) +{ + function get_file_info($file, $returned_values = array('name', 'server_path', 'size', 'date')) + { + + if ( ! file_exists($file)) + { + return FALSE; + } + + if (is_string($returned_values)) + { + $returned_values = explode(',', $returned_values); + } + + foreach ($returned_values as $key) + { + switch ($key) + { + case 'name': + $fileinfo['name'] = substr(strrchr($file, DIRECTORY_SEPARATOR), 1); + break; + case 'server_path': + $fileinfo['server_path'] = $file; + break; + case 'size': + $fileinfo['size'] = filesize($file); + break; + case 'date': + $fileinfo['date'] = filemtime($file); + break; + case 'readable': + $fileinfo['readable'] = is_readable($file); + break; + case 'writable': + // There are known problems using is_weritable on IIS. It may not be reliable - consider fileperms() + $fileinfo['writable'] = is_writable($file); + break; + case 'executable': + $fileinfo['executable'] = is_executable($file); + break; + case 'fileperms': + $fileinfo['fileperms'] = fileperms($file); + break; + } + } + + return $fileinfo; + } +} + +// -------------------------------------------------------------------- + +/** + * Get Mime by Extension + * + * Translates a file extension into a mime type based on config/mimes.php. + * Returns FALSE if it can't determine the type, or open the mime config file + * + * Note: this is NOT an accurate way of determining file mime types, and is here strictly as a convenience + * It should NOT be trusted, and should certainly NOT be used for security + * + * @access public + * @param string path to file + * @return mixed + */ +if ( ! function_exists('get_mime_by_extension')) +{ + function get_mime_by_extension($file) + { + $extension = strtolower(substr(strrchr($file, '.'), 1)); + + global $mimes; + + if ( ! is_array($mimes)) + { + if (defined('ENVIRONMENT') AND is_file(APPPATH.'config/'.ENVIRONMENT.'/mimes.php')) + { + include(APPPATH.'config/'.ENVIRONMENT.'/mimes.php'); + } + elseif (is_file(APPPATH.'config/mimes.php')) + { + include(APPPATH.'config/mimes.php'); + } + + if ( ! is_array($mimes)) + { + return FALSE; + } + } + + if (array_key_exists($extension, $mimes)) + { + if (is_array($mimes[$extension])) + { + // Multiple mime types, just give the first one + return current($mimes[$extension]); + } + else + { + return $mimes[$extension]; + } + } + else + { + return FALSE; + } + } +} + +// -------------------------------------------------------------------- + +/** + * Symbolic Permissions + * + * Takes a numeric value representing a file's permissions and returns + * standard symbolic notation representing that value + * + * @access public + * @param int + * @return string + */ +if ( ! function_exists('symbolic_permissions')) +{ + function symbolic_permissions($perms) + { + if (($perms & 0xC000) == 0xC000) + { + $symbolic = 's'; // Socket + } + elseif (($perms & 0xA000) == 0xA000) + { + $symbolic = 'l'; // Symbolic Link + } + elseif (($perms & 0x8000) == 0x8000) + { + $symbolic = '-'; // Regular + } + elseif (($perms & 0x6000) == 0x6000) + { + $symbolic = 'b'; // Block special + } + elseif (($perms & 0x4000) == 0x4000) + { + $symbolic = 'd'; // Directory + } + elseif (($perms & 0x2000) == 0x2000) + { + $symbolic = 'c'; // Character special + } + elseif (($perms & 0x1000) == 0x1000) + { + $symbolic = 'p'; // FIFO pipe + } + else + { + $symbolic = 'u'; // Unknown + } + + // Owner + $symbolic .= (($perms & 0x0100) ? 'r' : '-'); + $symbolic .= (($perms & 0x0080) ? 'w' : '-'); + $symbolic .= (($perms & 0x0040) ? (($perms & 0x0800) ? 's' : 'x' ) : (($perms & 0x0800) ? 'S' : '-')); + + // Group + $symbolic .= (($perms & 0x0020) ? 'r' : '-'); + $symbolic .= (($perms & 0x0010) ? 'w' : '-'); + $symbolic .= (($perms & 0x0008) ? (($perms & 0x0400) ? 's' : 'x' ) : (($perms & 0x0400) ? 'S' : '-')); + + // World + $symbolic .= (($perms & 0x0004) ? 'r' : '-'); + $symbolic .= (($perms & 0x0002) ? 'w' : '-'); + $symbolic .= (($perms & 0x0001) ? (($perms & 0x0200) ? 't' : 'x' ) : (($perms & 0x0200) ? 'T' : '-')); + + return $symbolic; + } +} + +// -------------------------------------------------------------------- + +/** + * Octal Permissions + * + * Takes a numeric value representing a file's permissions and returns + * a three character string representing the file's octal permissions + * + * @access public + * @param int + * @return string + */ +if ( ! function_exists('octal_permissions')) +{ + function octal_permissions($perms) + { + return substr(sprintf('%o', $perms), -3); + } +} + + +/* End of file file_helper.php */ +/* Location: ./system/helpers/file_helper.php */ \ No newline at end of file diff --git a/system/helpers/form_helper.php b/system/helpers/form_helper.php new file mode 100644 index 0000000..8733ae0 --- /dev/null +++ b/system/helpers/form_helper.php @@ -0,0 +1,1054 @@ +config->site_url($action); + } + + // If no action is provided then set to the current url + $action OR $action = $CI->config->site_url($CI->uri->uri_string()); + + $form = '
config->item('csrf_protection') === TRUE AND ! (strpos($action, $CI->config->base_url()) === FALSE OR strpos($form, 'method="get"'))) + { + $hidden[$CI->security->get_csrf_token_name()] = $CI->security->get_csrf_hash(); + } + + if (is_array($hidden) AND count($hidden) > 0) + { + $form .= sprintf("
%s
", form_hidden($hidden)); + } + + return $form; + } +} + +// ------------------------------------------------------------------------ + +/** + * Form Declaration - Multipart type + * + * Creates the opening portion of the form, but with "multipart/form-data". + * + * @access public + * @param string the URI segments of the form destination + * @param array a key/value pair of attributes + * @param array a key/value pair hidden data + * @return string + */ +if ( ! function_exists('form_open_multipart')) +{ + function form_open_multipart($action = '', $attributes = array(), $hidden = array()) + { + if (is_string($attributes)) + { + $attributes .= ' enctype="multipart/form-data"'; + } + else + { + $attributes['enctype'] = 'multipart/form-data'; + } + + return form_open($action, $attributes, $hidden); + } +} + +// ------------------------------------------------------------------------ + +/** + * Hidden Input Field + * + * Generates hidden fields. You can pass a simple key/value string or an associative + * array with multiple values. + * + * @access public + * @param mixed + * @param string + * @return string + */ +if ( ! function_exists('form_hidden')) +{ + function form_hidden($name, $value = '', $recursing = FALSE) + { + static $form; + + if ($recursing === FALSE) + { + $form = "\n"; + } + + if (is_array($name)) + { + foreach ($name as $key => $val) + { + form_hidden($key, $val, TRUE); + } + return $form; + } + + if ( ! is_array($value)) + { + $form .= ''."\n"; + } + else + { + foreach ($value as $k => $v) + { + $k = (is_int($k)) ? '' : $k; + form_hidden($name.'['.$k.']', $v, TRUE); + } + } + + return $form; + } +} + +// ------------------------------------------------------------------------ + +/** + * Text Input Field + * + * @access public + * @param mixed + * @param string + * @param string + * @return string + */ +if ( ! function_exists('form_input')) +{ + function form_input($data = '', $value = '', $extra = '') + { + $defaults = array('type' => 'text', 'name' => (( ! is_array($data)) ? $data : ''), 'value' => $value); + + return ""; + } +} + +// ------------------------------------------------------------------------ + +/** + * Password Field + * + * Identical to the input function but adds the "password" type + * + * @access public + * @param mixed + * @param string + * @param string + * @return string + */ +if ( ! function_exists('form_password')) +{ + function form_password($data = '', $value = '', $extra = '') + { + if ( ! is_array($data)) + { + $data = array('name' => $data); + } + + $data['type'] = 'password'; + return form_input($data, $value, $extra); + } +} + +// ------------------------------------------------------------------------ + +/** + * Upload Field + * + * Identical to the input function but adds the "file" type + * + * @access public + * @param mixed + * @param string + * @param string + * @return string + */ +if ( ! function_exists('form_upload')) +{ + function form_upload($data = '', $value = '', $extra = '') + { + if ( ! is_array($data)) + { + $data = array('name' => $data); + } + + $data['type'] = 'file'; + return form_input($data, $value, $extra); + } +} + +// ------------------------------------------------------------------------ + +/** + * Textarea field + * + * @access public + * @param mixed + * @param string + * @param string + * @return string + */ +if ( ! function_exists('form_textarea')) +{ + function form_textarea($data = '', $value = '', $extra = '') + { + $defaults = array('name' => (( ! is_array($data)) ? $data : ''), 'cols' => '40', 'rows' => '10'); + + if ( ! is_array($data) OR ! isset($data['value'])) + { + $val = $value; + } + else + { + $val = $data['value']; + unset($data['value']); // textareas don't use the value attribute + } + + $name = (is_array($data)) ? $data['name'] : $data; + return ""; + } +} + +// ------------------------------------------------------------------------ + +/** + * Multi-select menu + * + * @access public + * @param string + * @param array + * @param mixed + * @param string + * @return type + */ +if ( ! function_exists('form_multiselect')) +{ + function form_multiselect($name = '', $options = array(), $selected = array(), $extra = '') + { + if ( ! strpos($extra, 'multiple')) + { + $extra .= ' multiple="multiple"'; + } + + return form_dropdown($name, $options, $selected, $extra); + } +} + +// -------------------------------------------------------------------- + +/** + * Drop-down Menu + * + * @access public + * @param string + * @param array + * @param string + * @param string + * @return string + */ +if ( ! function_exists('form_dropdown')) +{ + function form_dropdown($name = '', $options = array(), $selected = array(), $extra = '') + { + if ( ! is_array($selected)) + { + $selected = array($selected); + } + + // If no selected state was submitted we will attempt to set it automatically + if (count($selected) === 0) + { + // If the form name appears in the $_POST array we have a winner! + if (isset($_POST[$name])) + { + $selected = array($_POST[$name]); + } + } + + if ($extra != '') $extra = ' '.$extra; + + $multiple = (count($selected) > 1 && strpos($extra, 'multiple') === FALSE) ? ' multiple="multiple"' : ''; + + $form = ''; + + return $form; + } +} + +// ------------------------------------------------------------------------ + +/** + * Checkbox Field + * + * @access public + * @param mixed + * @param string + * @param bool + * @param string + * @return string + */ +if ( ! function_exists('form_checkbox')) +{ + function form_checkbox($data = '', $value = '', $checked = FALSE, $extra = '') + { + $defaults = array('type' => 'checkbox', 'name' => (( ! is_array($data)) ? $data : ''), 'value' => $value); + + if (is_array($data) AND array_key_exists('checked', $data)) + { + $checked = $data['checked']; + + if ($checked == FALSE) + { + unset($data['checked']); + } + else + { + $data['checked'] = 'checked'; + } + } + + if ($checked == TRUE) + { + $defaults['checked'] = 'checked'; + } + else + { + unset($defaults['checked']); + } + + return ""; + } +} + +// ------------------------------------------------------------------------ + +/** + * Radio Button + * + * @access public + * @param mixed + * @param string + * @param bool + * @param string + * @return string + */ +if ( ! function_exists('form_radio')) +{ + function form_radio($data = '', $value = '', $checked = FALSE, $extra = '') + { + if ( ! is_array($data)) + { + $data = array('name' => $data); + } + + $data['type'] = 'radio'; + return form_checkbox($data, $value, $checked, $extra); + } +} + +// ------------------------------------------------------------------------ + +/** + * Submit Button + * + * @access public + * @param mixed + * @param string + * @param string + * @return string + */ +if ( ! function_exists('form_submit')) +{ + function form_submit($data = '', $value = '', $extra = '') + { + $defaults = array('type' => 'submit', 'name' => (( ! is_array($data)) ? $data : ''), 'value' => $value); + + return ""; + } +} + +// ------------------------------------------------------------------------ + +/** + * Reset Button + * + * @access public + * @param mixed + * @param string + * @param string + * @return string + */ +if ( ! function_exists('form_reset')) +{ + function form_reset($data = '', $value = '', $extra = '') + { + $defaults = array('type' => 'reset', 'name' => (( ! is_array($data)) ? $data : ''), 'value' => $value); + + return ""; + } +} + +// ------------------------------------------------------------------------ + +/** + * Form Button + * + * @access public + * @param mixed + * @param string + * @param string + * @return string + */ +if ( ! function_exists('form_button')) +{ + function form_button($data = '', $content = '', $extra = '') + { + $defaults = array('name' => (( ! is_array($data)) ? $data : ''), 'type' => 'button'); + + if ( is_array($data) AND isset($data['content'])) + { + $content = $data['content']; + unset($data['content']); // content is not an attribute + } + + return ""; + } +} + +// ------------------------------------------------------------------------ + +/** + * Form Label Tag + * + * @access public + * @param string The text to appear onscreen + * @param string The id the label applies to + * @param string Additional attributes + * @return string + */ +if ( ! function_exists('form_label')) +{ + function form_label($label_text = '', $id = '', $attributes = array()) + { + + $label = ' 0) + { + foreach ($attributes as $key => $val) + { + $label .= ' '.$key.'="'.$val.'"'; + } + } + + $label .= ">$label_text"; + + return $label; + } +} + +// ------------------------------------------------------------------------ +/** + * Fieldset Tag + * + * Used to produce
text. To close fieldset + * use form_fieldset_close() + * + * @access public + * @param string The legend text + * @param string Additional attributes + * @return string + */ +if ( ! function_exists('form_fieldset')) +{ + function form_fieldset($legend_text = '', $attributes = array()) + { + $fieldset = "".$extra; + } +} + +// ------------------------------------------------------------------------ + +/** + * Form Close Tag + * + * @access public + * @param string + * @return string + */ +if ( ! function_exists('form_close')) +{ + function form_close($extra = '') + { + return "".$extra; + } +} + +// ------------------------------------------------------------------------ + +/** + * Form Prep + * + * Formats text so that it can be safely placed in a form field in the event it has HTML tags. + * + * @access public + * @param string + * @return string + */ +if ( ! function_exists('form_prep')) +{ + function form_prep($str = '', $field_name = '') + { + static $prepped_fields = array(); + + // if the field name is an array we do this recursively + if (is_array($str)) + { + foreach ($str as $key => $val) + { + $str[$key] = form_prep($val); + } + + return $str; + } + + if ($str === '') + { + return ''; + } + + // we've already prepped a field with this name + // @todo need to figure out a way to namespace this so + // that we know the *exact* field and not just one with + // the same name + if (isset($prepped_fields[$field_name])) + { + return $str; + } + + $str = htmlspecialchars($str); + + // In case htmlspecialchars misses these. + $str = str_replace(array("'", '"'), array("'", """), $str); + + if ($field_name != '') + { + $prepped_fields[$field_name] = $field_name; + } + + return $str; + } +} + +// ------------------------------------------------------------------------ + +/** + * Form Value + * + * Grabs a value from the POST array for the specified field so you can + * re-populate an input field or textarea. If Form Validation + * is active it retrieves the info from the validation class + * + * @access public + * @param string + * @return mixed + */ +if ( ! function_exists('set_value')) +{ + function set_value($field = '', $default = '') + { + if (FALSE === ($OBJ =& _get_validation_object())) + { + if ( ! isset($_POST[$field])) + { + return $default; + } + + return form_prep($_POST[$field], $field); + } + + return form_prep($OBJ->set_value($field, $default), $field); + } +} + +// ------------------------------------------------------------------------ + +/** + * Set Select + * + * Let's you set the selected value of a