diff --git a/.gitignore b/.gitignore index 6432882b..97f74ab1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ -/.tmp-package -/composer -/doc -/easyappointments.zip -/node_modules -/src/application/logs/* -/src/config.php +.tmp-package +composer +doc +easyappointments.zip +node_modules +src/application/logs/* +!src/application/logs/index.html +src/config.php diff --git a/composer.json b/composer.json index dd400b0f..30c7f1e8 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ "vendor-dir": "composer" }, "require-dev": { - "apigen/apigen": "~4.1" + "codeigniter/framework": "^3.0" }, "require": { "gregwar/captcha": "^1.1", diff --git a/composer.lock b/composer.lock index 45acff92..5aba4f66 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "9376f6dce837c49ec0467c91adc7dd0e", - "content-hash": "f1caee945fa2c3ffed8c56511645dce9", + "hash": "c484aff175b14ee6c9810d6f4476c3c9", + "content-hash": "066f48bf5bdce6c78e3e440b892865f2", "packages": [ { "name": "gregwar/captcha", @@ -117,7 +117,40 @@ "time": "2015-11-01 10:15:28" } ], - "packages-dev": null, + "packages-dev": [ + { + "name": "codeigniter/framework", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/bcit-ci/CodeIgniter.git", + "reference": "8082544c5b4b33175790f505587e202e3ca9e488" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bcit-ci/CodeIgniter/zipball/8082544c5b4b33175790f505587e202e3ca9e488", + "reference": "8082544c5b4b33175790f505587e202e3ca9e488", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "mikey179/vfsstream": "1.1.*" + }, + "suggest": { + "paragonie/random_compat": "Provides better randomness in PHP 5.x" + }, + "type": "project", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The CodeIgniter framework", + "homepage": "https://codeigniter.com", + "time": "2016-03-21 16:26:30" + } + ], "aliases": [], "minimum-stability": "stable", "stability-flags": [], diff --git a/gulpfile.js b/gulpfile.js index 0fe961b2..f8836b40 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -17,7 +17,7 @@ gulp.task('composer', function() { '!./src/application/third_party/index.html', ]); - exec('composer install --no-dev --prefer-dist', function (err, stdout, stderr) { + exec('composer update && composer install --prefer-dist', function (err, stdout, stderr) { console.log(stdout); console.log(stderr); }); @@ -27,7 +27,8 @@ gulp.task('composer', function() { '!composer/**/demo{,/**}', '!composer/**/{demo,docs,examples,test,extras,language}{,/**}', '!composer/**/{composer.json,composer.lock,.gitignore}', - '!composer/**/{*.yml,*.md}' + '!composer/**/{*.yml,*.md}', + '!composer/codeigniter{,/**}' ]) .pipe(gulp.dest('./src/application/third_party/')); }); diff --git a/src/application/config/config.php b/src/application/config/config.php index 89dbcb7b..858f07e8 100644 --- a/src/application/config/config.php +++ b/src/application/config/config.php @@ -6,12 +6,11 @@ |-------------------------------------------------------------------------- | | Declare some of the global config values of Easy!Appointments. -| the global "config" variable. | */ -$config['ea_version'] = '1.2'; // This must be changed manually. -$config['ea_release_title'] = 'Dev'; // Leave empty for no title or add BETA, TEST etc ... -$config['ea_google_sync_feature'] = Config::GOOGLE_SYNC_FEATURE; +$config['version'] = '1.2'; // This must be changed manually. +$config['release_label'] = 'Dev'; // Leave empty for no title or add Alpha, Beta etc ... +$config['google_sync_feature'] = Config::GOOGLE_SYNC_FEATURE; /* |-------------------------------------------------------------------------- @@ -291,15 +290,13 @@ $config['encryption_key'] = Config::BASE_URL; | 'sess_time_to_update' = how many seconds between CI refreshing Session Information | */ -$config['sess_cookie_name'] = 'ci_session'; -$config['sess_expiration'] = 7200; -$config['sess_expire_on_close'] = FALSE; -$config['sess_encrypt_cookie'] = FALSE; -$config['sess_use_database'] = FALSE; -$config['sess_table_name'] = 'ci_sessions'; -$config['sess_match_ip'] = FALSE; -$config['sess_match_useragent'] = TRUE; -$config['sess_time_to_update'] = 300; +$config['sess_driver'] = 'files'; +$config['sess_cookie_name'] = 'ci_session'; +$config['sess_expiration'] = 7200; +$config['sess_save_path'] = NULL; +$config['sess_match_ip'] = FALSE; +$config['sess_time_to_update'] = 300; +$config['sess_regenerate_destroy'] = FALSE; /* |-------------------------------------------------------------------------- diff --git a/src/application/config/database.php b/src/application/config/database.php index 2bbb6bf7..f41f3fef 100644 --- a/src/application/config/database.php +++ b/src/application/config/database.php @@ -46,12 +46,12 @@ */ $active_group = 'default'; -$active_record = TRUE; +$query_builder = TRUE; $db['default']['hostname'] = Config::DB_HOST; -$db['default']['username'] = Config::DB_USERNAME; -$db['default']['password'] = Config::DB_PASSWORD; -$db['default']['database'] = Config::DB_NAME; +$db['default']['username'] = Config::DB_USERNAME; +$db['default']['password'] = Config::DB_PASSWORD; +$db['default']['database'] = Config::DB_NAME; $db['default']['dbdriver'] = 'mysqli'; $db['default']['dbprefix'] = ''; $db['default']['pconnect'] = TRUE; @@ -66,4 +66,4 @@ $db['default']['stricton'] = FALSE; /* End of file database.php */ -/* Location: ./application/config/database.php */ \ No newline at end of file +/* Location: ./application/config/database.php */ diff --git a/src/application/config/mimes.php b/src/application/config/mimes.php index 100f7d44..8bac8725 100644 --- a/src/application/config/mimes.php +++ b/src/application/config/mimes.php @@ -1,4 +1,6 @@ - '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 */ +return array( + 'hqx' => array('application/mac-binhex40', 'application/mac-binhex', 'application/x-binhex40', 'application/x-mac-binhex40'), + 'cpt' => 'application/mac-compactpro', + 'csv' => array('text/x-comma-separated-values', 'text/comma-separated-values', 'application/octet-stream', 'application/vnd.ms-excel', 'application/x-csv', 'text/x-csv', 'text/csv', 'application/csv', 'application/excel', 'application/vnd.msexcel', 'text/plain'), + 'bin' => array('application/macbinary', 'application/mac-binary', 'application/octet-stream', 'application/x-binary', 'application/x-macbinary'), + 'dms' => 'application/octet-stream', + 'lha' => 'application/octet-stream', + 'lzh' => 'application/octet-stream', + 'exe' => array('application/octet-stream', 'application/x-msdownload'), + 'class' => 'application/octet-stream', + 'psd' => array('application/x-photoshop', 'image/vnd.adobe.photoshop'), + 'so' => 'application/octet-stream', + 'sea' => 'application/octet-stream', + 'dll' => 'application/octet-stream', + 'oda' => 'application/oda', + 'pdf' => array('application/pdf', 'application/force-download', 'application/x-download', 'binary/octet-stream'), + 'ai' => array('application/pdf', 'application/postscript'), + 'eps' => 'application/postscript', + 'ps' => 'application/postscript', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'mif' => 'application/vnd.mif', + 'xls' => array('application/vnd.ms-excel', 'application/msexcel', 'application/x-msexcel', 'application/x-ms-excel', 'application/x-excel', 'application/x-dos_ms_excel', 'application/xls', 'application/x-xls', 'application/excel', 'application/download', 'application/vnd.ms-office', 'application/msword'), + 'ppt' => array('application/powerpoint', 'application/vnd.ms-powerpoint', 'application/vnd.ms-office', 'application/msword'), + 'pptx' => array('application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/x-zip', 'application/zip'), + 'wbxml' => 'application/wbxml', + 'wmlc' => 'application/wmlc', + 'dcr' => 'application/x-director', + 'dir' => 'application/x-director', + 'dxr' => 'application/x-director', + 'dvi' => 'application/x-dvi', + 'gtar' => 'application/x-gtar', + 'gz' => 'application/x-gzip', + 'gzip' => 'application/x-gzip', + 'php' => array('application/x-httpd-php', 'application/php', 'application/x-php', 'text/php', 'text/x-php', 'application/x-httpd-php-source'), + 'php4' => 'application/x-httpd-php', + 'php3' => 'application/x-httpd-php', + 'phtml' => 'application/x-httpd-php', + 'phps' => 'application/x-httpd-php-source', + 'js' => array('application/x-javascript', 'text/plain'), + 'swf' => 'application/x-shockwave-flash', + 'sit' => 'application/x-stuffit', + 'tar' => 'application/x-tar', + 'tgz' => array('application/x-tar', 'application/x-gzip-compressed'), + 'z' => 'application/x-compress', + 'xhtml' => 'application/xhtml+xml', + 'xht' => 'application/xhtml+xml', + 'zip' => array('application/x-zip', 'application/zip', 'application/x-zip-compressed', 'application/s-compressed', 'multipart/x-zip'), + 'rar' => array('application/x-rar', 'application/rar', 'application/x-rar-compressed'), + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mpga' => 'audio/mpeg', + 'mp2' => 'audio/mpeg', + 'mp3' => array('audio/mpeg', 'audio/mpg', 'audio/mpeg3', 'audio/mp3'), + 'aif' => array('audio/x-aiff', 'audio/aiff'), + 'aiff' => array('audio/x-aiff', 'audio/aiff'), + 'aifc' => 'audio/x-aiff', + 'ram' => 'audio/x-pn-realaudio', + 'rm' => 'audio/x-pn-realaudio', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'ra' => 'audio/x-realaudio', + 'rv' => 'video/vnd.rn-realvideo', + 'wav' => array('audio/x-wav', 'audio/wave', 'audio/wav'), + 'bmp' => array('image/bmp', 'image/x-bmp', 'image/x-bitmap', 'image/x-xbitmap', 'image/x-win-bitmap', 'image/x-windows-bmp', 'image/ms-bmp', 'image/x-ms-bmp', 'application/bmp', 'application/x-bmp', 'application/x-win-bitmap'), + 'gif' => 'image/gif', + 'jpeg' => array('image/jpeg', 'image/pjpeg'), + 'jpg' => array('image/jpeg', 'image/pjpeg'), + 'jpe' => array('image/jpeg', 'image/pjpeg'), + 'jp2' => array('image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'), + 'j2k' => array('image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'), + 'jpf' => array('image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'), + 'jpg2' => array('image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'), + 'jpx' => array('image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'), + 'jpm' => array('image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'), + 'mj2' => array('image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'), + 'mjp2' => array('image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'), + 'png' => array('image/png', 'image/x-png'), + 'tiff' => 'image/tiff', + 'tif' => 'image/tiff', + 'css' => array('text/css', 'text/plain'), + 'html' => array('text/html', 'text/plain'), + 'htm' => array('text/html', 'text/plain'), + 'shtml' => array('text/html', 'text/plain'), + 'txt' => 'text/plain', + 'text' => 'text/plain', + 'log' => array('text/plain', 'text/x-log'), + 'rtx' => 'text/richtext', + 'rtf' => 'text/rtf', + 'xml' => array('application/xml', 'text/xml', 'text/plain'), + 'xsl' => array('application/xml', 'text/xsl', 'text/xml'), + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpe' => 'video/mpeg', + 'qt' => 'video/quicktime', + 'mov' => 'video/quicktime', + 'avi' => array('video/x-msvideo', 'video/msvideo', 'video/avi', 'application/x-troff-msvideo'), + 'movie' => 'video/x-sgi-movie', + 'doc' => array('application/msword', 'application/vnd.ms-office'), + 'docx' => array('application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/zip', 'application/msword', 'application/x-zip'), + 'dot' => array('application/msword', 'application/vnd.ms-office'), + 'dotx' => array('application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/zip', 'application/msword'), + 'xlsx' => array('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/zip', 'application/vnd.ms-excel', 'application/msword', 'application/x-zip'), + 'word' => array('application/msword', 'application/octet-stream'), + 'xl' => 'application/excel', + 'eml' => 'message/rfc822', + 'json' => array('application/json', 'text/json'), + 'pem' => array('application/x-x509-user-cert', 'application/x-pem-file', 'application/octet-stream'), + 'p10' => array('application/x-pkcs10', 'application/pkcs10'), + 'p12' => 'application/x-pkcs12', + 'p7a' => 'application/x-pkcs7-signature', + 'p7c' => array('application/pkcs7-mime', 'application/x-pkcs7-mime'), + 'p7m' => array('application/pkcs7-mime', 'application/x-pkcs7-mime'), + 'p7r' => 'application/x-pkcs7-certreqresp', + 'p7s' => 'application/pkcs7-signature', + 'crt' => array('application/x-x509-ca-cert', 'application/x-x509-user-cert', 'application/pkix-cert'), + 'crl' => array('application/pkix-crl', 'application/pkcs-crl'), + 'der' => 'application/x-x509-ca-cert', + 'kdb' => 'application/octet-stream', + 'pgp' => 'application/pgp', + 'gpg' => 'application/gpg-keys', + 'sst' => 'application/octet-stream', + 'csr' => 'application/octet-stream', + 'rsa' => 'application/x-pkcs7', + 'cer' => array('application/pkix-cert', 'application/x-x509-ca-cert'), + '3g2' => 'video/3gpp2', + '3gp' => array('video/3gp', 'video/3gpp'), + 'mp4' => 'video/mp4', + 'm4a' => 'audio/x-m4a', + 'f4v' => array('video/mp4', 'video/x-f4v'), + 'flv' => 'video/x-flv', + 'webm' => 'video/webm', + 'aac' => 'audio/x-acc', + 'm4u' => 'application/vnd.mpegurl', + 'm3u' => 'text/plain', + 'xspf' => 'application/xspf+xml', + 'vlc' => 'application/videolan', + 'wmv' => array('video/x-ms-wmv', 'video/x-ms-asf'), + 'au' => 'audio/x-au', + 'ac3' => 'audio/ac3', + 'flac' => 'audio/x-flac', + 'ogg' => array('audio/ogg', 'video/ogg', 'application/ogg'), + 'kmz' => array('application/vnd.google-earth.kmz', 'application/zip', 'application/x-zip'), + 'kml' => array('application/vnd.google-earth.kml+xml', 'application/xml', 'text/xml'), + 'ics' => 'text/calendar', + 'ical' => 'text/calendar', + 'zsh' => 'text/x-scriptzsh', + '7zip' => array('application/x-compressed', 'application/x-zip-compressed', 'application/zip', 'multipart/x-zip'), + 'cdr' => array('application/cdr', 'application/coreldraw', 'application/x-cdr', 'application/x-coreldraw', 'image/cdr', 'image/x-cdr', 'zz-application/zz-winassoc-cdr'), + 'wma' => array('audio/x-ms-wma', 'video/x-ms-asf'), + 'jar' => array('application/java-archive', 'application/x-java-application', 'application/x-jar', 'application/x-compressed'), + 'svg' => array('image/svg+xml', 'application/xml', 'text/xml'), + 'vcf' => 'text/x-vcard', + 'srt' => array('text/srt', 'text/plain'), + 'vtt' => array('text/vtt', 'text/plain'), + 'ico' => array('image/x-icon', 'image/x-ico', 'image/vnd.microsoft.icon') +); diff --git a/src/application/controllers/appointments.php b/src/application/controllers/Appointments.php similarity index 100% rename from src/application/controllers/appointments.php rename to src/application/controllers/Appointments.php diff --git a/src/application/controllers/backend.php b/src/application/controllers/Backend.php similarity index 100% rename from src/application/controllers/backend.php rename to src/application/controllers/Backend.php diff --git a/src/application/controllers/backend_api.php b/src/application/controllers/Backend_api.php similarity index 100% rename from src/application/controllers/backend_api.php rename to src/application/controllers/Backend_api.php diff --git a/src/application/controllers/captcha.php b/src/application/controllers/Captcha.php similarity index 100% rename from src/application/controllers/captcha.php rename to src/application/controllers/Captcha.php diff --git a/src/application/controllers/errors.php b/src/application/controllers/Errors.php similarity index 100% rename from src/application/controllers/errors.php rename to src/application/controllers/Errors.php diff --git a/src/application/controllers/google.php b/src/application/controllers/Google.php similarity index 100% rename from src/application/controllers/google.php rename to src/application/controllers/Google.php diff --git a/src/application/controllers/installation.php b/src/application/controllers/Installation.php similarity index 100% rename from src/application/controllers/installation.php rename to src/application/controllers/Installation.php diff --git a/src/application/controllers/test.php b/src/application/controllers/Test.php similarity index 100% rename from src/application/controllers/test.php rename to src/application/controllers/Test.php diff --git a/src/application/controllers/user.php b/src/application/controllers/User.php similarity index 100% rename from src/application/controllers/user.php rename to src/application/controllers/User.php diff --git a/src/application/libraries/google_sync.php b/src/application/libraries/Google_sync.php similarity index 100% rename from src/application/libraries/google_sync.php rename to src/application/libraries/Google_sync.php diff --git a/src/application/libraries/notifications.php b/src/application/libraries/Notifications.php similarity index 100% rename from src/application/libraries/notifications.php rename to src/application/libraries/Notifications.php diff --git a/src/application/models/admins_model.php b/src/application/models/Admins_model.php similarity index 100% rename from src/application/models/admins_model.php rename to src/application/models/Admins_model.php diff --git a/src/application/models/appointments_model.php b/src/application/models/Appointments_model.php similarity index 100% rename from src/application/models/appointments_model.php rename to src/application/models/Appointments_model.php diff --git a/src/application/models/customers_model.php b/src/application/models/Customers_model.php similarity index 100% rename from src/application/models/customers_model.php rename to src/application/models/Customers_model.php diff --git a/src/application/models/providers_model.php b/src/application/models/Providers_model.php similarity index 100% rename from src/application/models/providers_model.php rename to src/application/models/Providers_model.php diff --git a/src/application/models/roles_model.php b/src/application/models/Roles_model.php similarity index 100% rename from src/application/models/roles_model.php rename to src/application/models/Roles_model.php diff --git a/src/application/models/secretaries_model.php b/src/application/models/Secretaries_model.php similarity index 100% rename from src/application/models/secretaries_model.php rename to src/application/models/Secretaries_model.php diff --git a/src/application/models/services_model.php b/src/application/models/Services_model.php similarity index 100% rename from src/application/models/services_model.php rename to src/application/models/Services_model.php diff --git a/src/application/models/settings_model.php b/src/application/models/Settings_model.php similarity index 100% rename from src/application/models/settings_model.php rename to src/application/models/Settings_model.php diff --git a/src/application/models/user_model.php b/src/application/models/User_model.php similarity index 100% rename from src/application/models/user_model.php rename to src/application/models/User_model.php diff --git a/src/application/third_party/autoload.php b/src/application/third_party/autoload.php index 6b607124..7836e9bb 100644 --- a/src/application/third_party/autoload.php +++ b/src/application/third_party/autoload.php @@ -4,4 +4,4 @@ require_once __DIR__ . '/composer' . '/autoload_real.php'; -return ComposerAutoloaderInitc1b442ad6654c006bdaf24db72724a82::getLoader(); +return ComposerAutoloaderInit643d16bb26f1ca0b505db35afcc43aff::getLoader(); diff --git a/src/application/third_party/composer/LICENSE b/src/application/third_party/composer/LICENSE index c8d57af8..1a281248 100644 --- a/src/application/third_party/composer/LICENSE +++ b/src/application/third_party/composer/LICENSE @@ -1,5 +1,5 @@ -Copyright (c) 2015 Nils Adermann, Jordi Boggiano +Copyright (c) 2016 Nils Adermann, Jordi Boggiano Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/application/third_party/composer/autoload_real.php b/src/application/third_party/composer/autoload_real.php index 613be8bb..f46684b6 100644 --- a/src/application/third_party/composer/autoload_real.php +++ b/src/application/third_party/composer/autoload_real.php @@ -2,7 +2,7 @@ // autoload_real.php @generated by Composer -class ComposerAutoloaderInitc1b442ad6654c006bdaf24db72724a82 +class ComposerAutoloaderInit643d16bb26f1ca0b505db35afcc43aff { private static $loader; @@ -19,9 +19,9 @@ class ComposerAutoloaderInitc1b442ad6654c006bdaf24db72724a82 return self::$loader; } - spl_autoload_register(array('ComposerAutoloaderInitc1b442ad6654c006bdaf24db72724a82', 'loadClassLoader'), true, true); + spl_autoload_register(array('ComposerAutoloaderInit643d16bb26f1ca0b505db35afcc43aff', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(); - spl_autoload_unregister(array('ComposerAutoloaderInitc1b442ad6654c006bdaf24db72724a82', 'loadClassLoader')); + spl_autoload_unregister(array('ComposerAutoloaderInit643d16bb26f1ca0b505db35afcc43aff', 'loadClassLoader')); $map = require __DIR__ . '/autoload_namespaces.php'; foreach ($map as $namespace => $path) { diff --git a/src/application/third_party/composer/installed.json b/src/application/third_party/composer/installed.json index f2978c25..372b3a0d 100644 --- a/src/application/third_party/composer/installed.json +++ b/src/application/third_party/composer/installed.json @@ -51,17 +51,17 @@ }, { "name": "phpmailer/phpmailer", - "version": "v5.2.13", - "version_normalized": "5.2.13.0", + "version": "v5.2.14", + "version_normalized": "5.2.14.0", "source": { "type": "git", "url": "https://github.com/PHPMailer/PHPMailer.git", - "reference": "45df3a88f7f46071e10d0b600f228d19f95911b3" + "reference": "e774bc9152de85547336e22b8926189e582ece95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/45df3a88f7f46071e10d0b600f228d19f95911b3", - "reference": "45df3a88f7f46071e10d0b600f228d19f95911b3", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/e774bc9152de85547336e22b8926189e582ece95", + "reference": "e774bc9152de85547336e22b8926189e582ece95", "shasum": "" }, "require": { @@ -72,9 +72,10 @@ "phpunit/phpunit": "4.7.*" }, "suggest": { - "league/oauth2-client": "Needed for Gmail's XOAUTH2 authentication system" + "league/oauth2-client": "Needed for XOAUTH2 authentication", + "league/oauth2-google": "Needed for Gmail XOAUTH2" }, - "time": "2015-09-14 09:18:12", + "time": "2015-11-01 10:15:28", "type": "library", "installation-source": "dist", "autoload": { @@ -110,5 +111,39 @@ } ], "description": "PHPMailer is a full-featured email creation and transfer class for PHP" + }, + { + "name": "codeigniter/framework", + "version": "3.0.6", + "version_normalized": "3.0.6.0", + "source": { + "type": "git", + "url": "https://github.com/bcit-ci/CodeIgniter.git", + "reference": "8082544c5b4b33175790f505587e202e3ca9e488" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bcit-ci/CodeIgniter/zipball/8082544c5b4b33175790f505587e202e3ca9e488", + "reference": "8082544c5b4b33175790f505587e202e3ca9e488", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "mikey179/vfsstream": "1.1.*" + }, + "suggest": { + "paragonie/random_compat": "Provides better randomness in PHP 5.x" + }, + "time": "2016-03-21 16:26:30", + "type": "project", + "installation-source": "dist", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The CodeIgniter framework", + "homepage": "https://codeigniter.com" } ] diff --git a/src/application/third_party/phpmailer/phpmailer/VERSION b/src/application/third_party/phpmailer/phpmailer/VERSION index 81badfbf..ba678f2d 100644 --- a/src/application/third_party/phpmailer/phpmailer/VERSION +++ b/src/application/third_party/phpmailer/phpmailer/VERSION @@ -1 +1 @@ -5.2.13 \ No newline at end of file +5.2.14 \ No newline at end of file diff --git a/src/application/third_party/phpmailer/phpmailer/class.phpmailer.php b/src/application/third_party/phpmailer/phpmailer/class.phpmailer.php index 0813cb08..e4dd00bf 100644 --- a/src/application/third_party/phpmailer/phpmailer/class.phpmailer.php +++ b/src/application/third_party/phpmailer/phpmailer/class.phpmailer.php @@ -29,66 +29,66 @@ class PHPMailer { /** * The PHPMailer Version number. - * @type string + * @var string */ - public $Version = '5.2.13'; + public $Version = '5.2.14'; /** * Email priority. * Options: null (default), 1 = High, 3 = Normal, 5 = low. * When null, the header is not set at all. - * @type integer + * @var integer */ public $Priority = null; /** * The character set of the message. - * @type string + * @var string */ public $CharSet = 'iso-8859-1'; /** * The MIME Content-type of the message. - * @type string + * @var string */ public $ContentType = 'text/plain'; /** * The message encoding. * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable". - * @type string + * @var string */ public $Encoding = '8bit'; /** * Holds the most recent mailer error message. - * @type string + * @var string */ public $ErrorInfo = ''; /** * The From email address for the message. - * @type string + * @var string */ public $From = 'root@localhost'; /** * The From name of the message. - * @type string + * @var string */ public $FromName = 'Root User'; /** * The Sender email (Return-Path) of the message. * If not empty, will be sent via -f to sendmail or as 'MAIL FROM' in smtp mode. - * @type string + * @var string */ public $Sender = ''; /** * The Return-Path of the message. * If empty, it will be set to either From or Sender. - * @type string + * @var string * @deprecated Email senders should never set a return-path header; * it's the receiver's job (RFC5321 section 4.4), so this no longer does anything. * @link https://tools.ietf.org/html/rfc5321#section-4.4 RFC5321 reference @@ -97,14 +97,14 @@ class PHPMailer /** * The Subject of the message. - * @type string + * @var string */ public $Subject = ''; /** * An HTML or plain text message body. * If HTML then call isHTML(true). - * @type string + * @var string */ public $Body = ''; @@ -113,7 +113,7 @@ class PHPMailer * This body can be read by mail clients that do not have HTML email * capability such as mutt & Eudora. * Clients that can read HTML will view the normal Body. - * @type string + * @var string */ public $AltBody = ''; @@ -123,27 +123,27 @@ class PHPMailer * To generate iCal events, use the bundled extras/EasyPeasyICS.php class or iCalcreator * @link http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/ * @link http://kigkonsult.se/iCalcreator/ - * @type string + * @var string */ public $Ical = ''; /** * The complete compiled MIME message body. * @access protected - * @type string + * @var string */ protected $MIMEBody = ''; /** * The complete compiled MIME message headers. - * @type string + * @var string * @access protected */ protected $MIMEHeader = ''; /** * Extra headers that createHeader() doesn't fold in. - * @type string + * @var string * @access protected */ protected $mailHeader = ''; @@ -151,64 +151,64 @@ class PHPMailer /** * Word-wrap the message body to this number of chars. * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance. - * @type integer + * @var integer */ public $WordWrap = 0; /** * Which method to use to send mail. * Options: "mail", "sendmail", or "smtp". - * @type string + * @var string */ public $Mailer = 'mail'; /** * The path to the sendmail program. - * @type string + * @var string */ public $Sendmail = '/usr/sbin/sendmail'; /** * Whether mail() uses a fully sendmail-compatible MTA. * One which supports sendmail's "-oi -f" options. - * @type boolean + * @var boolean */ public $UseSendmailOptions = true; /** * Path to PHPMailer plugins. * Useful if the SMTP class is not in the PHP include path. - * @type string + * @var string * @deprecated Should not be needed now there is an autoloader. */ public $PluginDir = ''; /** - * The email address that a reading confirmation should be sent to. - * @type string + * The email address that a reading confirmation should be sent to, also known as read receipt. + * @var string */ public $ConfirmReadingTo = ''; /** - * The hostname to use in Message-Id and Received headers - * and as default HELO string. - * If empty, the value returned - * by SERVER_NAME is used or 'localhost.localdomain'. - * @type string + * The hostname to use in the Message-ID header and as default HELO string. + * If empty, PHPMailer attempts to find one with, in order, + * $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value + * 'localhost.localdomain'. + * @var string */ public $Hostname = ''; /** - * An ID to be used in the Message-Id header. + * An ID to be used in the Message-ID header. * If empty, a unique id will be generated. - * @type string + * @var string */ public $MessageID = ''; /** * The message Date to be used in the Date header. * If empty, the current date will be added. - * @type string + * @var string */ public $MessageDate = ''; @@ -221,21 +221,22 @@ class PHPMailer * You can also specify encryption type, for example: * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465"). * Hosts will be tried in order. - * @type string + * @var string */ public $Host = 'localhost'; /** * The default SMTP server port. - * @type integer + * @var integer * @TODO Why is this needed when the SMTP class takes care of it? */ public $Port = 25; /** * The SMTP HELO of the message. - * Default is $Hostname. - * @type string + * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find + * one with the same method described above for $Hostname. + * @var string * @see PHPMailer::$Hostname */ public $Helo = ''; @@ -243,7 +244,7 @@ class PHPMailer /** * What kind of encryption to use on the SMTP connection. * Options: '', 'ssl' or 'tls' - * @type string + * @var string */ public $SMTPSecure = ''; @@ -251,14 +252,14 @@ class PHPMailer * Whether to enable TLS encryption automatically if a server supports it, * even if `SMTPSecure` is not set to 'tls'. * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid. - * @type boolean + * @var boolean */ public $SMTPAutoTLS = true; /** * Whether to use SMTP authentication. * Uses the Username and Password properties. - * @type boolean + * @var boolean * @see PHPMailer::$Username * @see PHPMailer::$Password */ @@ -266,47 +267,47 @@ class PHPMailer /** * Options array passed to stream_context_create when connecting via SMTP. - * @type array + * @var array */ public $SMTPOptions = array(); /** * SMTP username. - * @type string + * @var string */ public $Username = ''; /** * SMTP password. - * @type string + * @var string */ public $Password = ''; /** * SMTP auth type. * Options are LOGIN (default), PLAIN, NTLM, CRAM-MD5 - * @type string + * @var string */ public $AuthType = ''; /** * SMTP realm. * Used for NTLM auth - * @type string + * @var string */ public $Realm = ''; /** * SMTP workstation. * Used for NTLM auth - * @type string + * @var string */ public $Workstation = ''; /** * The SMTP server timeout in seconds. * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 - * @type integer + * @var integer */ public $Timeout = 300; @@ -319,7 +320,7 @@ class PHPMailer * * `2` Data and commands * * `3` As 2 plus connection status * * `4` Low-level data output - * @type integer + * @var integer * @see SMTP::$do_debug */ public $SMTPDebug = 0; @@ -335,7 +336,7 @@ class PHPMailer * * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; * - * @type string|callable + * @var string|callable * @see SMTP::$Debugoutput */ public $Debugoutput = 'echo'; @@ -344,20 +345,20 @@ class PHPMailer * Whether to keep SMTP connection open after each message. * If this is set to true then to close the connection * requires an explicit call to smtpClose(). - * @type boolean + * @var boolean */ public $SMTPKeepAlive = false; /** * Whether to split multiple to addresses into multiple messages * or send them all in one message. - * @type boolean + * @var boolean */ public $SingleTo = false; /** * Storage for addresses when SingleTo is enabled. - * @type array + * @var array * @TODO This should really not be public */ public $SingleToArray = array(); @@ -365,15 +366,15 @@ class PHPMailer /** * Whether to generate VERP addresses on send. * Only applicable when sending via SMTP. - * @link http://en.wikipedia.org/wiki/Variable_envelope_return_path + * @link https://en.wikipedia.org/wiki/Variable_envelope_return_path * @link http://www.postfix.org/VERP_README.html Postfix VERP info - * @type boolean + * @var boolean */ public $do_verp = false; /** * Whether to allow sending messages with an empty body. - * @type boolean + * @var boolean */ public $AllowEmpty = false; @@ -381,40 +382,40 @@ class PHPMailer * The default line ending. * @note The default remains "\n". We force CRLF where we know * it must be used via self::CRLF. - * @type string + * @var string */ public $LE = "\n"; /** * DKIM selector. - * @type string + * @var string */ public $DKIM_selector = ''; /** * DKIM Identity. * Usually the email address used as the source of the email - * @type string + * @var string */ public $DKIM_identity = ''; /** * DKIM passphrase. * Used if your key is encrypted. - * @type string + * @var string */ public $DKIM_passphrase = ''; /** * DKIM signing domain name. * @example 'example.com' - * @type string + * @var string */ public $DKIM_domain = ''; /** * DKIM private key file path. - * @type string + * @var string */ public $DKIM_private = ''; @@ -434,59 +435,48 @@ class PHPMailer * string $subject the subject * string $body the email body * string $from email address of sender - * @type string + * @var string */ public $action_function = ''; /** * What to put in the X-Mailer header. * Options: An empty string for PHPMailer default, whitespace for none, or a string to use - * @type string + * @var string */ public $XMailer = ''; - - /** - * Only For XOAUTH - Google - * Options: An empty string for PHPMailer default, Enter the email used to get access token - * @type string - */ -// public $UserEmail = ''; -// public $RefreshToken = ''; -// public $ClientId = ''; -// public $ClientSecret = ''; - - + /** * An instance of the SMTP sender class. - * @type SMTP + * @var SMTP * @access protected */ protected $smtp = null; /** - * The array of 'to' addresses. - * @type array + * The array of 'to' names and addresses. + * @var array * @access protected */ protected $to = array(); /** - * The array of 'cc' addresses. - * @type array + * The array of 'cc' names and addresses. + * @var array * @access protected */ protected $cc = array(); /** - * The array of 'bcc' addresses. - * @type array + * The array of 'bcc' names and addresses. + * @var array * @access protected */ protected $bcc = array(); /** * The array of reply-to names and addresses. - * @type array + * @var array * @access protected */ protected $ReplyTo = array(); @@ -494,77 +484,100 @@ class PHPMailer /** * An array of all kinds of addresses. * Includes all of $to, $cc, $bcc - * @type array + * @var array * @access protected + * @see PHPMailer::$to @see PHPMailer::$cc @see PHPMailer::$bcc */ protected $all_recipients = array(); + /** + * An array of names and addresses queued for validation. + * In send(), valid and non duplicate entries are moved to $all_recipients + * and one of $to, $cc, or $bcc. + * This array is used only for addresses with IDN. + * @var array + * @access protected + * @see PHPMailer::$to @see PHPMailer::$cc @see PHPMailer::$bcc + * @see PHPMailer::$all_recipients + */ + protected $RecipientsQueue = array(); + + /** + * An array of reply-to names and addresses queued for validation. + * In send(), valid and non duplicate entries are moved to $ReplyTo. + * This array is used only for addresses with IDN. + * @var array + * @access protected + * @see PHPMailer::$ReplyTo + */ + protected $ReplyToQueue = array(); + /** * The array of attachments. - * @type array + * @var array * @access protected */ protected $attachment = array(); /** * The array of custom headers. - * @type array + * @var array * @access protected */ protected $CustomHeader = array(); /** * The most recent Message-ID (including angular brackets). - * @type string + * @var string * @access protected */ protected $lastMessageID = ''; /** * The message's MIME type. - * @type string + * @var string * @access protected */ protected $message_type = ''; /** * The array of MIME boundary strings. - * @type array + * @var array * @access protected */ protected $boundary = array(); /** * The array of available languages. - * @type array + * @var array * @access protected */ protected $language = array(); /** * The number of errors encountered. - * @type integer + * @var integer * @access protected */ protected $error_count = 0; /** * The S/MIME certificate file path. - * @type string + * @var string * @access protected */ protected $sign_cert_file = ''; /** * The S/MIME key file path. - * @type string + * @var string * @access protected */ protected $sign_key_file = ''; /** * The optional S/MIME extra certificates ("CA Chain") file path. - * @type string + * @var string * @access protected */ protected $sign_extracerts_file = ''; @@ -572,21 +585,21 @@ class PHPMailer /** * The S/MIME password for the key. * Used only if the key is encrypted. - * @type string + * @var string * @access protected */ protected $sign_key_pass = ''; /** * Whether to throw exceptions for errors. - * @type boolean + * @var boolean * @access protected */ protected $exceptions = false; /** * Unique ID used for message ID and boundaries. - * @type string + * @var string * @access protected */ protected $uniqueid = ''; @@ -613,7 +626,7 @@ class PHPMailer /** * The maximum line length allowed by RFC 2822 section 2.1.1 - * @type integer + * @var integer */ const MAX_LINE_LENGTH = 998; @@ -775,55 +788,101 @@ class PHPMailer /** * Add a "To" address. - * @param string $address + * @param string $address The email address to send to * @param string $name - * @return boolean true on success, false if address already used + * @return boolean true on success, false if address already used or invalid in some way */ public function addAddress($address, $name = '') { - return $this->addAnAddress('to', $address, $name); + return $this->addOrEnqueueAnAddress('to', $address, $name); } /** * Add a "CC" address. * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer. - * @param string $address + * @param string $address The email address to send to * @param string $name - * @return boolean true on success, false if address already used + * @return boolean true on success, false if address already used or invalid in some way */ public function addCC($address, $name = '') { - return $this->addAnAddress('cc', $address, $name); + return $this->addOrEnqueueAnAddress('cc', $address, $name); } /** * Add a "BCC" address. * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer. - * @param string $address + * @param string $address The email address to send to * @param string $name - * @return boolean true on success, false if address already used + * @return boolean true on success, false if address already used or invalid in some way */ public function addBCC($address, $name = '') { - return $this->addAnAddress('bcc', $address, $name); + return $this->addOrEnqueueAnAddress('bcc', $address, $name); } /** - * Add a "Reply-to" address. - * @param string $address + * Add a "Reply-To" address. + * @param string $address The email address to reply to * @param string $name - * @return boolean + * @return boolean true on success, false if address already used or invalid in some way */ public function addReplyTo($address, $name = '') { - return $this->addAnAddress('Reply-To', $address, $name); + return $this->addOrEnqueueAnAddress('Reply-To', $address, $name); } /** - * Add an address to one of the recipient arrays. - * Addresses that have been added already return false, but do not throw exceptions - * @param string $kind One of 'to', 'cc', 'bcc', 'ReplyTo' - * @param string $address The email address to send to + * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer + * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still + * be modified after calling this function), addition of such addresses is delayed until send(). + * Addresses that have been added already return false, but do not throw exceptions. + * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' + * @param string $address The email address to send, resp. to reply to + * @param string $name + * @throws phpmailerException + * @return boolean true on success, false if address already used or invalid in some way + * @access protected + */ + protected function addOrEnqueueAnAddress($kind, $address, $name) + { + $address = trim($address); + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + if (($pos = strrpos($address, '@')) === false) { + // At-sign is misssing. + $error_message = $this->lang('invalid_address') . $address; + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new phpmailerException($error_message); + } + return false; + } + $params = array($kind, $address, $name); + // Enqueue addresses with IDN until we know the PHPMailer::$CharSet. + if ($this->has8bitChars(substr($address, ++$pos)) and $this->idnSupported()) { + if ($kind != 'Reply-To') { + if (!array_key_exists($address, $this->RecipientsQueue)) { + $this->RecipientsQueue[$address] = $params; + return true; + } + } else { + if (!array_key_exists($address, $this->ReplyToQueue)) { + $this->ReplyToQueue[$address] = $params; + return true; + } + } + return false; + } + // Immediately add standard addresses without IDN. + return call_user_func_array(array($this, 'addAnAddress'), $params); + } + + /** + * Add an address to one of the recipient arrays or to the ReplyTo array. + * Addresses that have been added already return false, but do not throw exceptions. + * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' + * @param string $address The email address to send, resp. to reply to * @param string $name * @throws phpmailerException * @return boolean true on success, false if address already used or invalid in some way @@ -831,26 +890,26 @@ class PHPMailer */ protected function addAnAddress($kind, $address, $name = '') { - if (!preg_match('/^(to|cc|bcc|Reply-To)$/', $kind)) { - $this->setError($this->lang('Invalid recipient array') . ': ' . $kind); - $this->edebug($this->lang('Invalid recipient array') . ': ' . $kind); + if (!in_array($kind, array('to', 'cc', 'bcc', 'Reply-To'))) { + $error_message = $this->lang('Invalid recipient kind: ') . $kind; + $this->setError($error_message); + $this->edebug($error_message); if ($this->exceptions) { - throw new phpmailerException('Invalid recipient array: ' . $kind); + throw new phpmailerException($error_message); } return false; } - $address = trim($address); - $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim if (!$this->validateAddress($address)) { - $this->setError($this->lang('invalid_address') . ': ' . $address); - $this->edebug($this->lang('invalid_address') . ': ' . $address); + $error_message = $this->lang('invalid_address') . $address; + $this->setError($error_message); + $this->edebug($error_message); if ($this->exceptions) { - throw new phpmailerException($this->lang('invalid_address') . ': ' . $address); + throw new phpmailerException($error_message); } return false; } if ($kind != 'Reply-To') { - if (!isset($this->all_recipients[strtolower($address)])) { + if (!array_key_exists(strtolower($address), $this->all_recipients)) { array_push($this->$kind, array($address, $name)); $this->all_recipients[strtolower($address)] = true; return true; @@ -931,11 +990,15 @@ class PHPMailer { $address = trim($address); $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim - if (!$this->validateAddress($address)) { - $this->setError($this->lang('invalid_address') . ': ' . $address); - $this->edebug($this->lang('invalid_address') . ': ' . $address); + // Don't validate now addresses with IDN. Will be done in send(). + if (($pos = strrpos($address, '@')) === false or + (!$this->has8bitChars(substr($address, ++$pos)) or !$this->idnSupported()) and + !$this->validateAddress($address)) { + $error_message = $this->lang('invalid_address') . $address; + $this->setError($error_message); + $this->edebug($error_message); if ($this->exceptions) { - throw new phpmailerException($this->lang('invalid_address') . ': ' . $address); + throw new phpmailerException($error_message); } return false; } @@ -965,10 +1028,10 @@ class PHPMailer * Check that a string looks like an email address. * @param string $address The email address to check * @param string $patternselect A selector for the validation pattern to use : - * * `auto` Pick strictest one automatically; + * * `auto` Pick best pattern automatically; * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0, PHP >= 5.3.2, 5.2.14; * * `pcre` Use old PCRE implementation; - * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL; same as pcre8 but does not allow 'dotless' domains; + * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL; * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements. * * `noregex` Don't use a regex: super fast, really dumb. * @return boolean @@ -977,6 +1040,10 @@ class PHPMailer */ public static function validateAddress($address, $patternselect = 'auto') { + //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321 + if (strpos($address, "\n") !== false or strpos($address, "\r") !== false) { + return false; + } if (!$patternselect or $patternselect == 'auto') { //Check this constant first so it works when extension_loaded() is disabled by safe mode //Constant was added in PHP 5.2.4 @@ -1056,6 +1123,48 @@ class PHPMailer } } + /** + * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the + * "intl" and "mbstring" PHP extensions. + * @return bool "true" if required functions for IDN support are present + */ + public function idnSupported() + { + // @TODO: Write our own "idn_to_ascii" function for PHP <= 5.2. + return function_exists('idn_to_ascii') and function_exists('mb_convert_encoding'); + } + + /** + * Converts IDN in given email address to its ASCII form, also known as punycode, if possible. + * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet. + * This function silently returns unmodified address if: + * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form) + * - Conversion to punycode is impossible (e.g. required PHP functions are not available) + * or fails for any reason (e.g. domain has characters not allowed in an IDN) + * @see PHPMailer::$CharSet + * @param string $address The email address to convert + * @return string The encoded address in ASCII form + */ + public function punyencodeAddress($address) + { + // Verify we have required functions, CharSet, and at-sign. + if ($this->idnSupported() and + !empty($this->CharSet) and + ($pos = strrpos($address, '@')) !== false) { + $domain = substr($address, ++$pos); + // Verify CharSet string is a valid one, and domain properly encoded in this CharSet. + if ($this->has8bitChars($domain) and @mb_check_encoding($domain, $this->CharSet)) { + $domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet); + if (($punycode = defined('INTL_IDNA_VARIANT_UTS46') ? + idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46) : + idn_to_ascii($domain)) !== false) { + return substr($address, 0, $pos) . $punycode; + } + } + } + return $address; + } + /** * Create a message and send it. * Uses the sending method specified by $Mailer. @@ -1087,17 +1196,41 @@ class PHPMailer public function preSend() { try { + $this->error_count = 0; // Reset errors $this->mailHeader = ''; + + // Dequeue recipient and Reply-To addresses with IDN + foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) { + $params[1] = $this->punyencodeAddress($params[1]); + call_user_func_array(array($this, 'addAnAddress'), $params); + } if ((count($this->to) + count($this->cc) + count($this->bcc)) < 1) { throw new phpmailerException($this->lang('provide_address'), self::STOP_CRITICAL); } + // Validate From, Sender, and ConfirmReadingTo addresses + foreach (array('From', 'Sender', 'ConfirmReadingTo') as $address_kind) { + $this->$address_kind = trim($this->$address_kind); + if (empty($this->$address_kind)) { + continue; + } + $this->$address_kind = $this->punyencodeAddress($this->$address_kind); + if (!$this->validateAddress($this->$address_kind)) { + $error_message = $this->lang('invalid_address') . $this->$address_kind; + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new phpmailerException($error_message); + } + return false; + } + } + // Set whether the message is multipart/alternative if (!empty($this->AltBody)) { $this->ContentType = 'multipart/alternative'; } - $this->error_count = 0; // Reset errors $this->setMessageType(); // Refuse to send an empty message unless we are specifically allowing it if (!$this->AllowEmpty and empty($this->Body)) { @@ -1238,7 +1371,15 @@ class PHPMailer fputs($mail, $header); fputs($mail, $body); $result = pclose($mail); - $this->doCallback(($result == 0), $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From); + $this->doCallback( + ($result == 0), + $this->to, + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From + ); if ($result != 0) { throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); } @@ -1384,7 +1525,7 @@ class PHPMailer if (is_null($this->smtp)) { $this->smtp = $this->getSMTPInstance(); } - + // Already connected? if ($this->smtp->connected()) { return true; @@ -1459,10 +1600,10 @@ class PHPMailer } if ($this->SMTPAuth) { if (!$this->smtp->authenticate( - $this->Username, - $this->Password, - $this->AuthType, - $this->Realm, + $this->Username, + $this->Password, + $this->AuthType, + $this->Realm, $this->Workstation ) ) { @@ -1524,7 +1665,7 @@ class PHPMailer 'file_open' => 'File Error: Could not open file: ', 'from_failed' => 'The following From address failed: ', 'instantiate' => 'Could not instantiate mail function.', - 'invalid_address' => 'Invalid address', + 'invalid_address' => 'Invalid address: ', 'mailer_not_supported' => ' mailer is not supported.', 'provide_address' => 'You must provide at least one recipient email address.', 'recipients_failed' => 'SMTP Error: The following recipients failed: ', @@ -1789,7 +1930,6 @@ class PHPMailer } $result .= $this->headerLine('Date', $this->MessageDate); - // To be created automatically by mail() if ($this->SingleTo) { if ($this->Mailer != 'mail') { @@ -1835,7 +1975,7 @@ class PHPMailer if ($this->MessageID != '') { $this->lastMessageID = $this->MessageID; } else { - $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->ServerHostname()); + $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname()); } $result .= $this->headerLine('Message-ID', $this->lastMessageID); if (!is_null($this->Priority)) { @@ -1844,7 +1984,7 @@ class PHPMailer if ($this->XMailer == '') { $result .= $this->headerLine( 'X-Mailer', - 'PHPMailer ' . $this->Version . ' (https://github.com/PHPMailer/PHPMailer/)' + 'PHPMailer ' . $this->Version . ' (https://github.com/PHPMailer/PHPMailer)' ); } else { $myXmailer = trim($this->XMailer); @@ -1854,7 +1994,7 @@ class PHPMailer } if ($this->ConfirmReadingTo != '') { - $result .= $this->headerLine('Disposition-Notification-To', '<' . trim($this->ConfirmReadingTo) . '>'); + $result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>'); } // Add custom headers @@ -2335,7 +2475,7 @@ class PHPMailer $type = $attachment[4]; $disposition = $attachment[6]; $cid = $attachment[7]; - if ($disposition == 'inline' && isset($cidUniq[$cid])) { + if ($disposition == 'inline' && array_key_exists($cid, $cidUniq)) { continue; } $cidUniq[$cid] = true; @@ -2426,7 +2566,6 @@ class PHPMailer * @param string $path The full path to the file * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' * @throws phpmailerException - * @see EncodeFile(encodeFile * @access protected * @return string */ @@ -2723,7 +2862,6 @@ class PHPMailer return str_replace(' ', '_', $encoded); } - /** * Add a string or binary attachment (non-filesystem). * This method can be used to attach ascii or binary data, @@ -2885,6 +3023,22 @@ class PHPMailer return !empty($this->AltBody); } + /** + * Clear queued addresses of given kind. + * @access protected + * @param string $kind 'to', 'cc', or 'bcc' + * @return void + */ + public function clearQueuedAddresses($kind) + { + $RecipientsQueue = $this->RecipientsQueue; + foreach ($RecipientsQueue as $address => $params) { + if ($params[0] == $kind) { + unset($this->RecipientsQueue[$address]); + } + } + } + /** * Clear all To recipients. * @return void @@ -2895,6 +3049,7 @@ class PHPMailer unset($this->all_recipients[strtolower($to[0])]); } $this->to = array(); + $this->clearQueuedAddresses('to'); } /** @@ -2907,6 +3062,7 @@ class PHPMailer unset($this->all_recipients[strtolower($cc[0])]); } $this->cc = array(); + $this->clearQueuedAddresses('cc'); } /** @@ -2919,6 +3075,7 @@ class PHPMailer unset($this->all_recipients[strtolower($bcc[0])]); } $this->bcc = array(); + $this->clearQueuedAddresses('bcc'); } /** @@ -2928,6 +3085,7 @@ class PHPMailer public function clearReplyTos() { $this->ReplyTo = array(); + $this->ReplyToQueue = array(); } /** @@ -2940,6 +3098,7 @@ class PHPMailer $this->cc = array(); $this->bcc = array(); $this->all_recipients = array(); + $this->RecipientsQueue = array(); } /** @@ -3096,8 +3255,7 @@ class PHPMailer } /** - * Returns all custom headers - * + * Returns all custom headers. * @return array */ public function getCustomHeaders() @@ -3114,13 +3272,13 @@ class PHPMailer * @param string $message HTML message string * @param string $basedir baseline directory for path * @param boolean|callable $advanced Whether to use the internal HTML to text converter - * or your own custom converter @see html2text() + * or your own custom converter @see PHPMailer::html2text() * @return string $message */ public function msgHTML($message, $basedir = '', $advanced = false) { preg_match_all('/(src|background)=["\'](.*)["\']/Ui', $message, $images); - if (isset($images[2])) { + if (array_key_exists(2, $images)) { foreach ($images[2] as $imgindex => $url) { // Convert data URIs into embedded images if (preg_match('#^data:(image[^;,]*)(;base64)?,#', $url, $match)) { @@ -3140,7 +3298,7 @@ class PHPMailer } } elseif (substr($url, 0, 4) !== 'cid:' && !preg_match('#^[A-z]+://#', $url)) { // Do not change urls for absolute images (thanks to corvuscorax) - // Do not change urls that are already inline images + // Do not change urls that are already inline images $filename = basename($url); $directory = dirname($url); if ($directory == '.') { @@ -3444,7 +3602,6 @@ class PHPMailer return preg_replace('/(\r\n|\r|\n)/ms', $breaktype, $text); } - /** * Set the public and private key files and password for S/MIME signing. * @access public @@ -3613,7 +3770,10 @@ class PHPMailer "\tbh=" . $DKIMb64 . ";\r\n" . "\tb="; $toSign = $this->DKIM_HeaderC( - $from_header . "\r\n" . $to_header . "\r\n" . $subject_header . "\r\n" . $dkimhdrs + $from_header . "\r\n" . + $to_header . "\r\n" . + $subject_header . "\r\n" . + $dkimhdrs ); $signed = $this->DKIM_Sign($toSign); return $dkimhdrs . $signed . "\r\n"; @@ -3633,6 +3793,7 @@ class PHPMailer /** * Allows for public read access to 'to' property. + * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. * @access public * @return array */ @@ -3643,6 +3804,7 @@ class PHPMailer /** * Allows for public read access to 'cc' property. + * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. * @access public * @return array */ @@ -3653,6 +3815,7 @@ class PHPMailer /** * Allows for public read access to 'bcc' property. + * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. * @access public * @return array */ @@ -3663,6 +3826,7 @@ class PHPMailer /** * Allows for public read access to 'ReplyTo' property. + * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. * @access public * @return array */ @@ -3673,6 +3837,7 @@ class PHPMailer /** * Allows for public read access to 'all_recipients' property. + * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. * @access public * @return array */ diff --git a/src/application/third_party/phpmailer/phpmailer/class.phpmaileroauth.php b/src/application/third_party/phpmailer/phpmailer/class.phpmaileroauth.php index 60b5b69b..f9cd7b42 100644 --- a/src/application/third_party/phpmailer/phpmailer/class.phpmaileroauth.php +++ b/src/application/third_party/phpmailer/phpmailer/class.phpmaileroauth.php @@ -27,38 +27,38 @@ class PHPMailerOAuth extends PHPMailer { /** * The OAuth user's email address - * @type string + * @var string */ public $oauthUserEmail = ''; /** * The OAuth refresh token - * @type string + * @var string */ public $oauthRefreshToken = ''; /** * The OAuth client ID - * @type string + * @var string */ public $oauthClientId = ''; /** * The OAuth client secret - * @type string + * @var string */ public $oauthClientSecret = ''; /** - * An instance of the OAuth class. - * @type OAuth + * An instance of the PHPMailerOAuthGoogle class. + * @var PHPMailerOAuthGoogle * @access protected */ protected $oauth = null; - + /** - * Get an OAuth instance to use. - * @return OAuth + * Get a PHPMailerOAuthGoogle instance to use. + * @return PHPMailerOAuthGoogle */ public function getOAUTHInstance() { @@ -79,19 +79,18 @@ class PHPMailerOAuth extends PHPMailer * @param array $options An array of options compatible with stream_context_create() * @uses SMTP * @access public - * @throws phpmailerException - * @return boolean + * @return bool */ public function smtpConnect($options = array()) { if (is_null($this->smtp)) { $this->smtp = $this->getSMTPInstance(); } - + if (is_null($this->oauth)) { $this->oauth = $this->getOAUTHInstance(); } - + // Already connected? if ($this->smtp->connected()) { return true; diff --git a/src/application/third_party/phpmailer/phpmailer/class.phpmaileroauthgoogle.php b/src/application/third_party/phpmailer/phpmailer/class.phpmaileroauthgoogle.php index a6e11af8..ab6292b8 100644 --- a/src/application/third_party/phpmailer/phpmailer/class.phpmaileroauthgoogle.php +++ b/src/application/third_party/phpmailer/phpmailer/class.phpmaileroauthgoogle.php @@ -30,7 +30,13 @@ class PHPMailerOAuthGoogle private $oauthRefreshToken = ''; private $oauthClientId = ''; private $oauthClientSecret = ''; - + + /** + * @param string $UserEmail + * @param string $ClientSecret + * @param string $ClientId + * @param string $RefreshToken + */ public function __construct( $UserEmail, $ClientSecret, @@ -49,19 +55,19 @@ class PHPMailerOAuthGoogle 'clientSecret' => $this->oauthClientSecret ]); } - + private function getGrant() { return new \League\OAuth2\Client\Grant\RefreshToken(); } - + private function getToken() { $provider = $this->getProvider(); $grant = $this->getGrant(); return $provider->getAccessToken($grant, ['refresh_token' => $this->oauthRefreshToken]); } - + public function getOauth64() { $token = $this->getToken(); diff --git a/src/application/third_party/phpmailer/phpmailer/class.pop3.php b/src/application/third_party/phpmailer/phpmailer/class.pop3.php index 63c81843..403b573e 100644 --- a/src/application/third_party/phpmailer/phpmailer/class.pop3.php +++ b/src/application/third_party/phpmailer/phpmailer/class.pop3.php @@ -31,28 +31,28 @@ class POP3 { /** * The POP3 PHPMailer Version number. - * @type string + * @var string * @access public */ - public $Version = '5.2.13'; + public $Version = '5.2.14'; /** * Default POP3 port number. - * @type integer + * @var integer * @access public */ public $POP3_PORT = 110; /** * Default timeout in seconds. - * @type integer + * @var integer * @access public */ public $POP3_TIMEOUT = 30; /** * POP3 Carriage Return + Line Feed. - * @type string + * @var string * @access public * @deprecated Use the constant instead */ @@ -61,66 +61,66 @@ class POP3 /** * Debug display level. * Options: 0 = no, 1+ = yes - * @type integer + * @var integer * @access public */ public $do_debug = 0; /** * POP3 mail server hostname. - * @type string + * @var string * @access public */ public $host; /** * POP3 port number. - * @type integer + * @var integer * @access public */ public $port; /** * POP3 Timeout Value in seconds. - * @type integer + * @var integer * @access public */ public $tval; /** * POP3 username - * @type string + * @var string * @access public */ public $username; /** * POP3 password. - * @type string + * @var string * @access public */ public $password; /** * Resource handle for the POP3 connection socket. - * @type resource - * @access private + * @var resource + * @access protected */ - private $pop_conn; + protected $pop_conn; /** * Are we connected? - * @type boolean - * @access private + * @var boolean + * @access protected */ - private $connected = false; + protected $connected = false; /** * Error container. - * @type array - * @access private + * @var array + * @access protected */ - private $errors = array(); + protected $errors = array(); /** * Line break constant @@ -310,9 +310,9 @@ class POP3 * $size is the maximum number of bytes to retrieve * @param integer $size * @return string - * @access private + * @access protected */ - private function getResponse($size = 128) + protected function getResponse($size = 128) { $response = fgets($this->pop_conn, $size); if ($this->do_debug >= 1) { @@ -325,9 +325,9 @@ class POP3 * Send raw data to the POP3 server. * @param string $string * @return integer - * @access private + * @access protected */ - private function sendString($string) + protected function sendString($string) { if ($this->pop_conn) { if ($this->do_debug >= 2) { //Show client messages when debug >= 2 @@ -343,9 +343,9 @@ class POP3 * Looks for for +OK or -ERR. * @param string $string * @return boolean - * @access private + * @access protected */ - private function checkResponse($string) + protected function checkResponse($string) { if (substr($string, 0, 3) !== '+OK') { $this->setError(array( @@ -363,8 +363,9 @@ class POP3 * Add an error to the internal error store. * Also display debug output if it's enabled. * @param $error + * @access protected */ - private function setError($error) + protected function setError($error) { $this->errors[] = $error; if ($this->do_debug >= 1) { @@ -376,15 +377,24 @@ class POP3 } } + /** + * Get an array of error messages, if any. + * @return array + */ + public function getErrors() + { + return $this->errors; + } + /** * POP3 connection error handler. * @param integer $errno * @param string $errstr * @param string $errfile * @param integer $errline - * @access private + * @access protected */ - private function catchWarning($errno, $errstr, $errfile, $errline) + protected function catchWarning($errno, $errstr, $errfile, $errline) { $this->setError(array( 'error' => "Connecting to the POP3 server raised a PHP warning: ", diff --git a/src/application/third_party/phpmailer/phpmailer/class.smtp.php b/src/application/third_party/phpmailer/phpmailer/class.smtp.php index 0cb215c4..2e32e2fc 100644 --- a/src/application/third_party/phpmailer/phpmailer/class.smtp.php +++ b/src/application/third_party/phpmailer/phpmailer/class.smtp.php @@ -28,25 +28,25 @@ class SMTP { /** * The PHPMailer SMTP version number. - * @type string + * @var string */ - const VERSION = '5.2.13'; + const VERSION = '5.2.14'; /** * SMTP line break constant. - * @type string + * @var string */ const CRLF = "\r\n"; /** * The SMTP port to use if one is not specified. - * @type integer + * @var integer */ const DEFAULT_SMTP_PORT = 25; /** * The maximum line length allowed by RFC 2822 section 2.1.1 - * @type integer + * @var integer */ const MAX_LINE_LENGTH = 998; @@ -77,15 +77,15 @@ class SMTP /** * The PHPMailer SMTP Version number. - * @type string + * @var string * @deprecated Use the `VERSION` constant instead * @see SMTP::VERSION */ - public $Version = '5.2.13'; + public $Version = '5.2.14'; /** * SMTP server port number. - * @type integer + * @var integer * @deprecated This is only ever used as a default value, so use the `DEFAULT_SMTP_PORT` constant instead * @see SMTP::DEFAULT_SMTP_PORT */ @@ -93,7 +93,7 @@ class SMTP /** * SMTP reply line ending. - * @type string + * @var string * @deprecated Use the `CRLF` constant instead * @see SMTP::CRLF */ @@ -107,7 +107,7 @@ class SMTP * * self::DEBUG_SERVER (`2`) Client commands and server responses * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages - * @type integer + * @var integer */ public $do_debug = self::DEBUG_OFF; @@ -122,7 +122,7 @@ class SMTP * * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; * - * @type string|callable + * @var string|callable */ public $Debugoutput = 'echo'; @@ -130,7 +130,7 @@ class SMTP * Whether to use VERP. * @link http://en.wikipedia.org/wiki/Variable_envelope_return_path * @link http://www.postfix.org/VERP_README.html Info on VERP - * @type boolean + * @var boolean */ public $do_verp = false; @@ -139,26 +139,26 @@ class SMTP * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure. * @link http://tools.ietf.org/html/rfc2821#section-4.5.3.2 - * @type integer + * @var integer */ public $Timeout = 300; /** * How long to wait for commands to complete, in seconds. * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 - * @type integer + * @var integer */ public $Timelimit = 300; /** * The socket for the server connection. - * @type resource + * @var resource */ protected $smtp_conn; /** * Error information, if any, for the last SMTP command. - * @type array + * @var array */ protected $error = array( 'error' => '', @@ -170,7 +170,7 @@ class SMTP /** * The reply the server sent to us for HELO. * If null, no HELO string has yet been received. - * @type string|null + * @var string|null */ protected $helo_rply = null; @@ -181,13 +181,13 @@ class SMTP * represents the server name. In case of HELO it is the only element of the array. * Other values can be boolean TRUE or an array containing extension options. * If null, no HELO/EHLO string has yet been received. - * @type array|null + * @var array|null */ protected $server_caps = null; /** * The most recent reply received from the server. - * @type string + * @var string */ protected $last_reply = ''; @@ -356,7 +356,7 @@ class SMTP * @param string $authtype The auth type (PLAIN, LOGIN, NTLM, CRAM-MD5, XOAUTH2) * @param string $realm The auth realm for NTLM * @param string $workstation The auth workstation for NTLM - * @param null|OAuth $OAuth An optional OAuth instance (@see PHPMailerOAuth) + * @param null|OAuth $OAuth An optional OAuth instance (@see PHPMailerOAuth) * @return bool True if successfully authenticated.* @access public */ public function authenticate( @@ -814,15 +814,15 @@ class SMTP * Sets the TO argument to $toaddr. * Returns true if the recipient was accepted false if it was rejected. * Implements from rfc 821: RCPT TO: - * @param string $toaddr The address the message is being sent to + * @param string $address The address the message is being sent to * @access public * @return boolean */ - public function recipient($toaddr) + public function recipient($address) { return $this->sendCommand( 'RCPT TO', - 'RCPT TO:<' . $toaddr . '>', + 'RCPT TO:<' . $address . '>', array(250, 251) ); } @@ -841,9 +841,9 @@ class SMTP /** * Send a command to an SMTP server and check its return code. - * @param string $command The command name - not sent to the server + * @param string $command The command name - not sent to the server * @param string $commandstring The actual command to send - * @param integer|array $expect One or more expected integer success codes + * @param integer|array $expect One or more expected integer success codes * @access protected * @return boolean True on success. */ @@ -853,6 +853,11 @@ class SMTP $this->setError("Called $command without being connected"); return false; } + //Reject line breaks in all commands + if (strpos($commandstring, "\n") !== false or strpos($commandstring, "\r") !== false) { + $this->setError("Command '$command' contained line breaks"); + return false; + } $this->client_send($commandstring . self::CRLF); $this->last_reply = $this->get_lines(); diff --git a/src/application/third_party/phpmailer/phpmailer/get_oauth_token.php b/src/application/third_party/phpmailer/phpmailer/get_oauth_token.php index 2c4b918d..46eb28a5 100644 --- a/src/application/third_party/phpmailer/phpmailer/get_oauth_token.php +++ b/src/application/third_party/phpmailer/phpmailer/get_oauth_token.php @@ -1,64 +1,60 @@ -//get_oauth_token.php - * e.g.: http://localhost/phpmail/get_oauth_token.php - * * Ensure dependencies are installed with 'composer install' - * * Set up an app in your Google developer console - * * Set the script address as the app's redirect URL - * If no refresh token is obtained when running this file, revoke access to your app - * using link: https://accounts.google.com/b/0/IssuedAuthSubTokens and run the script again. - * This script requires PHP 5.4 or later - */ - -require 'vendor/autoload.php'; - -session_start(); - -//If this automatic URL doesn't work, set it yourself manually -$redirectUri = isset($_SERVER['HTTPS']) ? 'https://' : 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF']; -//$redirectUri = 'http://localhost/phpmailer/get_oauth_token.php'; -$clientId = 'RANDOMCHARS-----duv1n2.apps.googleusercontent.com'; -$clientSecret = 'RANDOMCHARS-----lGyjPcRtvP'; - -//All details obtained by setting up app in Google developer console. -//Set Redirect URI in Developer Console as [https/http]:////get_oauth_token.php -$provider = new League\OAuth2\Client\Provider\Google ( - [ - 'clientId' => $clientId, - 'clientSecret' => $clientSecret, - 'redirectUri' => $redirectUri, - 'scopes' => ['https://mail.google.com/'], - 'accessType' => 'offline' - ] -); - -if (!isset($_GET['code'])) { - // If we don't have an authorization code then get one - $authUrl = $provider->getAuthorizationUrl(); - $_SESSION['oauth2state'] = $provider->state; - header('Location: ' . $authUrl); - exit; -// Check given state against previously stored one to mitigate CSRF attack -} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) { - unset($_SESSION['oauth2state']); - exit('Invalid state'); -} else { - $provider->accessType = 'offline'; - // Try to get an access token (using the authorization code grant) - $token = $provider->getAccessToken( - 'authorization_code', - [ - 'code' => $_GET['code'] - ] - ); - // Use this to interact with an API on the users behalf - // echo $token->accessToken.'
'; - - // Use this to get a new access token if the old one expires - echo 'Refresh Token: ' . $token->refreshToken; - - // Unix timestamp of when the token will expire, and need refreshing - // echo $token->expires; -} +//get_oauth_token.php + * e.g.: http://localhost/phpmail/get_oauth_token.php + * * Ensure dependencies are installed with 'composer install' + * * Set up an app in your Google developer console + * * Set the script address as the app's redirect URL + * If no refresh token is obtained when running this file, revoke access to your app + * using link: https://accounts.google.com/b/0/IssuedAuthSubTokens and run the script again. + * This script requires PHP 5.4 or later + * PHP Version 5.4 + */ + +require 'vendor/autoload.php'; + +session_start(); + +//If this automatic URL doesn't work, set it yourself manually +$redirectUri = isset($_SERVER['HTTPS']) ? 'https://' : 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF']; +//$redirectUri = 'http://localhost/phpmailer/get_oauth_token.php'; + +//These details obtained are by setting up app in Google developer console. +$clientId = 'RANDOMCHARS-----duv1n2.apps.googleusercontent.com'; +$clientSecret = 'RANDOMCHARS-----lGyjPcRtvP'; + +//Set Redirect URI in Developer Console as [https/http]:////get_oauth_token.php +$provider = new League\OAuth2\Client\Provider\Google( + array( + 'clientId' => $clientId, + 'clientSecret' => $clientSecret, + 'redirectUri' => $redirectUri, + 'scopes' => array('https://mail.google.com/'), + 'accessType' => 'offline' + ) +); + +if (!isset($_GET['code'])) { + // If we don't have an authorization code then get one + $authUrl = $provider->getAuthorizationUrl(); + $_SESSION['oauth2state'] = $provider->getState(); + header('Location: ' . $authUrl); + exit; +// Check given state against previously stored one to mitigate CSRF attack +} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) { + unset($_SESSION['oauth2state']); + exit('Invalid state'); +} else { + // Try to get an access token (using the authorization code grant) + $token = $provider->getAccessToken( + 'authorization_code', + array( + 'code' => $_GET['code'] + ) + ); + + // Use this to get a new access token if the old one expires + echo 'Refresh Token: ' . $token->getRefreshToken(); +} diff --git a/src/application/views/appointments/book.php b/src/application/views/appointments/book.php index 4a6dd1d7..fba29d92 100644 --- a/src/application/views/appointments/book.php +++ b/src/application/views/appointments/book.php @@ -33,30 +33,6 @@ type="text/css" href=""> - - - - - - - - - "> - - - - @@ -431,9 +379,66 @@ + + + + + + + + + + + + + + + + diff --git a/src/application/views/appointments/book_success.php b/src/application/views/appointments/book_success.php index 9614837e..dc990e62 100755 --- a/src/application/views/appointments/book_success.php +++ b/src/application/views/appointments/book_success.php @@ -51,7 +51,7 @@ '; - if ($this->config->item('ea_google_sync_feature')) { + if ($this->config->item('google_sync_feature')) { echo ' ' + - '' + - '' + - '' + - '' + - ''; - $('.breaks').append(tr); - }); - } else { - $('#' + index).prop('checked', false); - $('#' + index + '-start').prop('disabled', true); - $('#' + index + '-end').prop('disabled', true); - } - }); - - // Make break cells editable. - WorkingPlan.prototype.editableBreakDay($('.breaks .break-day')); - WorkingPlan.prototype.editableBreakTime($('.breaks').find('.break-start, .break-end')); -}; - -/** - * This method makes editable the break day cells. - * - * @param {object} $selector The jquery selector ready for use. - */ -WorkingPlan.prototype.editableBreakDay = function($selector) { - var weekDays = {}; - weekDays[EALang['monday']] = EALang['monday']; //'Monday'; - weekDays[EALang['tuesday']] = EALang['tuesday']; //'Tuesday'; - weekDays[EALang['wednesday']] = EALang['wednesday']; //'Wednesday'; - weekDays[EALang['thursday']] = EALang['thursday']; //'Thursday'; - weekDays[EALang['friday']] = EALang['friday']; //'Friday'; - weekDays[EALang['saturday']] = EALang['saturday']; //'Saturday'; - weekDays[EALang['sunday']] = EALang['sunday']; //'Sunday'; - - $selector.editable(function(value, settings) { - return value; - }, { - 'type': 'select', - 'data': weekDays, - 'event': 'edit', - 'height': '30px', - 'submit': '', - 'cancel': '', - 'onblur': 'ignore', - 'onreset': function(settings, td) { - if (!WorkingPlan.prototype.enableCancel) return false; // disable ESC button - }, - 'onsubmit': function(settings, td) { - if (!WorkingPlan.prototype.enableSubmit) return false; // disable Enter button - } - }); -}; - -/** - * This method makes editable the break time cells. - * - * @param {object} $selector The jquery selector ready for use. - */ -WorkingPlan.prototype.editableBreakTime = function($selector) { - $selector.editable(function(value, settings) { - // Do not return the value because the user needs to press the "Save" button. - return value; - }, { - 'event': 'edit', - 'height': '25px', - 'submit': '', - 'cancel': '', - 'onblur': 'ignore', - 'onreset': function(settings, td) { - if (!WorkingPlan.prototype.enableCancel) return false; // disable ESC button - }, - 'onsubmit': function(settings, td) { - if (!WorkingPlan.prototype.enableSubmit) return false; // disable Enter button - } - }); -}; - -/** - * Binds the event handlers for the working plan dom elements. - */ -WorkingPlan.prototype.bindEventHandlers = function() { - /** - * Event: Day Checkbox "Click" - * - * Enable or disable the time selection for each day. - */ - $('.working-plan input[type="checkbox"]').click(function() { - var id = $(this).attr('id'); - - if ($(this).prop('checked') == true) { - $('#' + id + '-start').prop('disabled', false).val('09:00'); - $('#' + id + '-end').prop('disabled', false).val('18:00'); - } else { - $('#' + id + '-start').prop('disabled', true).val(''); - $('#' + id + '-end').prop('disabled', true).val(''); - } - }); + /** + * This flag determines whether the jeditables are allowed to submit. It is + * true only whenever the user presses the save button. + * + * @type {bool} + */ + this.enableSubmit = false; + }; /** - * Event: Add Break Button "Click" + * Setup the dom elements of a given working plan. * - * A new row is added on the table and the user can enter the new break - * data. After that he can either press the save or cancel button. + * @param {object} workingPlan Contains the working hours and breaks for each day of the week. */ - $('.add-break').click(function() { - var tr = - '' + - '' + EALang['monday'] + '' + - '09:00' + - '10:00' + - '' + - '' + - '' + - '' + - '' + - '' + - ''; - $('.breaks').prepend(tr); + WorkingPlan.prototype.setup = function(workingPlan) { + $.each(workingPlan, function(index, workingDay) { + if (workingDay != null) { + $('#' + index).prop('checked', true); + $('#' + index + '-start').val(workingDay.start); + $('#' + index + '-end').val(workingDay.end); - // Bind editable and event handlers. - tr = $('.breaks tr').get()[1]; - WorkingPlan.prototype.editableBreakDay($(tr).find('.break-day')); - WorkingPlan.prototype.editableBreakTime($(tr).find('.break-start, .break-end')); - $(tr).find('.edit-break').trigger('click'); - $('.add-break').prop('disabled', true); - }); + // Add the day's breaks on the breaks table. + $.each(workingDay.breaks, function(i, brk) { + var day = this.convertValueToDay(index); + + var tr = + '' + + '' + GeneralFunctions.ucaseFirstLetter(day) + '' + + '' + brk.start + '' + + '' + brk.end + '' + + '' + + '' + + '' + + '' + + '' + + '' + + ''; + $('.breaks').append(tr); + }.bind(this)); + } else { + $('#' + index).prop('checked', false); + $('#' + index + '-start').prop('disabled', true); + $('#' + index + '-end').prop('disabled', true); + } + }.bind(this)); + + // Make break cells editable. + this.editableBreakDay($('.breaks .break-day')); + this.editableBreakTime($('.breaks').find('.break-start, .break-end')); + }; /** - * Event: Edit Break Button "Click" + * This method makes editable the break day cells. * - * Enables the row editing for the "Breaks" table rows. + * @param {object} $selector The jquery selector ready for use. */ - $(document).on('click', '.edit-break', function() { - // Reset previous editable tds - var $previousEdt = $(this).closest('table').find('.editable').get(); - $.each($previousEdt, function(index, edt) { - if (edt.reset !== undefined) edt.reset(); - }); + WorkingPlan.prototype.editableBreakDay = function($selector) { + var weekDays = {}; + weekDays[EALang['monday']] = EALang['monday']; //'Monday'; + weekDays[EALang['tuesday']] = EALang['tuesday']; //'Tuesday'; + weekDays[EALang['wednesday']] = EALang['wednesday']; //'Wednesday'; + weekDays[EALang['thursday']] = EALang['thursday']; //'Thursday'; + weekDays[EALang['friday']] = EALang['friday']; //'Friday'; + weekDays[EALang['saturday']] = EALang['saturday']; //'Saturday'; + weekDays[EALang['sunday']] = EALang['sunday']; //'Sunday'; - // Make all cells in current row editable. - $(this).parent().parent().children().trigger('edit'); - $(this).parent().parent().find('.break-start input, .break-end input').timepicker({ - currentText: EALang['now'], - closeText: EALang['close'], - timeOnlyTitle: EALang['select_time'], - timeText: EALang['time'], - hourText: EALang['hour'], - minuteText: EALang['minutes'] - }); - $(this).parent().parent().find('.break-day select').focus(); - - // Show save - cancel buttons. - $(this).closest('table').find('.edit-break, .delete-break').addClass('hidden'); - $(this).parent().find('.save-break, .cancel-break').removeClass('hidden'); - - $('.add-break').prop('disabled', true); - }); - - /** - * Event: Delete Break Button "Click" - * - * Removes the current line from the "Breaks" table. - */ - $(document).on('click', '.delete-break', function() { - $(this).parent().parent().remove(); - }); - - /** - * Event: Cancel Break Button "Click" - * - * Bring the ".breaks" table back to its initial state. - */ - $(document).on('click', '.cancel-break', function() { - WorkingPlan.prototype.enableCancel = true; - $(this).parent().parent().find('.cancel-editable').trigger('click'); - WorkingPlan.prototype.enableCancel = false; - - $(this).closest('table').find('.edit-break, .delete-break').removeClass('hidden'); - $(this).parent().find('.save-break, .cancel-break').addClass('hidden'); - $('.add-break').prop('disabled', false); - }); - - /** - * Event: Save Break Button "Click" - * - * Save the editable values and restore the table to its initial state. - */ - $(document).on('click', '.save-break', function() { - // Break's start time must always be prior to break's end. - var start = Date.parse($(this).parent().parent().find('.break-start input').val()); - var end = Date.parse($(this).parent().parent().find('.break-end input').val()); - if (start > end) { - $(this).parent().parent().find('.break-end input').val(start.addHours(1).toString('HH:mm')); - } - - WorkingPlan.prototype.enableSubmit = true; - $(this).parent().parent().find('.editable .submit-editable').trigger('click'); - WorkingPlan.prototype.enableSubmit = false; - - $(this).parent().find('.save-break, .cancel-break').addClass('hidden'); - $(this).closest('table').find('.edit-break, .delete-break').removeClass('hidden'); - $('.add-break').prop('disabled', false); - }); -}; - -/** - * Get the working plan settings. - * - * @returns {object} Returns the working plan settings object. - */ -WorkingPlan.prototype.get = function() { - var workingPlan = {}; - $('.working-plan input[type="checkbox"]').each(function() { - var id = $(this).attr('id'); - if ($(this).prop('checked') == true) { - workingPlan[id] = {}; - workingPlan[id].start = $('#' + id + '-start').val(); - workingPlan[id].end = $('#' + id + '-end').val(); - workingPlan[id].breaks = []; - - $('.breaks tr').each(function(index, tr) { - var day = WorkingPlan.prototype.convertDayToValue( - $(tr).find('.break-day').text()); - if (day == id) { - var start = $(tr).find('.break-start').text(); - var end = $(tr).find('.break-end').text(); - - workingPlan[id].breaks.push({ - 'start': start, - 'end': end - }); + $selector.editable(function(value, settings) { + return value; + }, { + type: 'select', + data: weekDays, + event: 'edit', + height: '30px', + submit: '', + cancel: '', + onblur: 'ignore', + onreset: function(settings, td) { + if (!this.enableCancel) { + return false; // disable ESC button } - }); - - } else { - workingPlan[id] = null; - } - }); - - return workingPlan; -}; - -/** - * Enables or disabled the timepicker functionality from the working plan input - * text fields. - * - * @param {bool} disabled (OPTIONAL = false) If true then the timepickers will be - * disabled. - */ -WorkingPlan.prototype.timepickers = function(disabled) { - if (disabled == undefined) disabled == false; - - if (disabled == false) { - // Set timepickers where needed. - $('.working-plan input[type="text"]').timepicker({ - 'timeFormat': 'HH:mm', - - currentText: EALang['now'], - closeText: EALang['close'], - timeOnlyTitle: EALang['select_time'], - timeText: EALang['time'], - hourText: EALang['hour'], - minuteText: EALang['minutes'], - - 'onSelect': function(datetime, inst) { - // Start time must be earlier than end time. - var start = Date.parse($(this).parent().parent().find('.work-start').val()); - var end = Date.parse($(this).parent().parent().find('.work-end').val()); - - if (start > end) { - $(this).parent().parent().find('.work-end').val(start.addHours(1).toString('HH:mm')); + }.bind(this), + onsubmit: function(settings, td) { + if (!this.enableSubmit) { + return false; // disable Enter button } + }.bind(this) + }); + }; + + /** + * This method makes editable the break time cells. + * + * @param {object} $selector The jquery selector ready for use. + */ + WorkingPlan.prototype.editableBreakTime = function($selector) { + $selector.editable(function(value, settings) { + // Do not return the value because the user needs to press the "Save" button. + return value; + }, { + event: 'edit', + height: '25px', + submit: '', + cancel: '', + onblur: 'ignore', + onreset: function(settings, td) { + if (!this.enableCancel) { + return false; // disable ESC button + } + }.bind(this), + onsubmit: function(settings, td) { + if (!this.enableSubmit) { + return false; // disable Enter button + } + }.bind(this) + }); + }; + + /** + * Binds the event handlers for the working plan dom elements. + */ + WorkingPlan.prototype.bindEventHandlers = function() { + /** + * Event: Day Checkbox "Click" + * + * Enable or disable the time selection for each day. + */ + $('.working-plan input[type="checkbox"]').click(function() { + var id = $(this).attr('id'); + + if ($(this).prop('checked') == true) { + $('#' + id + '-start').prop('disabled', false).val('09:00'); + $('#' + id + '-end').prop('disabled', false).val('18:00'); + } else { + $('#' + id + '-start').prop('disabled', true).val(''); + $('#' + id + '-end').prop('disabled', true).val(''); } }); - } else { - $('.working-plan input').timepicker('destroy'); - } -}; -/** - * Reset the current plan back to the company's default working plan. - */ -WorkingPlan.prototype.reset = function() { + /** + * Event: Add Break Button "Click" + * + * A new row is added on the table and the user can enter the new break + * data. After that he can either press the save or cancel button. + */ + $('.add-break').click(function() { + var tr = + '' + + '' + EALang['monday'] + '' + + '09:00' + + '10:00' + + '' + + '' + + '' + + '' + + '' + + '' + + ''; + $('.breaks').prepend(tr); -}; + // Bind editable and event handlers. + tr = $('.breaks tr')[1]; + this.editableBreakDay($(tr).find('.break-day')); + this.editableBreakTime($(tr).find('.break-start, .break-end')); + $(tr).find('.edit-break').trigger('click'); + $('.add-break').prop('disabled', true); + }.bind(this)); -/** - * This is necessary for translated days. - * - * @param {string} value Day value could be like "monday", "tuesday" etc. - */ -WorkingPlan.prototype.convertValueToDay = function(value) { - switch (value) { - case 'monday': - return EALang['monday']; - break; - case 'tuesday': - return EALang['tuesday']; - break; - case 'wednesday': - return EALang['wednesday']; - break; - case 'thursday': - return EALang['thursday']; - break; - case 'friday': - return EALang['friday']; - break; - case 'saturday': - return EALang['saturday']; - break; - case 'sunday': - return EALang['sunday']; - break; - } -}; + /** + * Event: Edit Break Button "Click" + * + * Enables the row editing for the "Breaks" table rows. + */ + $(document).on('click', '.edit-break', function() { + // Reset previous editable tds + var $previousEdt = $(this).closest('table').find('.editable').get(); + $.each($previousEdt, function(index, edt) { + if (edt.reset !== undefined) { + edt.reset(); + } + }); -/** - * This is necessary for translated days. - * - * @param {string} value Day value could be like "Monday", "Tuesday" etc. - */ -WorkingPlan.prototype.convertDayToValue = function(day) { - switch (day) { - case EALang['monday']: - return 'monday'; - break; - case EALang['tuesday']: - return 'tuesday'; - break; - case EALang['wednesday']: - return 'wednesday'; - break; - case EALang['thursday']: - return 'thursday'; - break; - case EALang['friday']: - return 'friday'; - break; - case EALang['saturday']: - return 'saturday'; - break; - case EALang['sunday']: - return 'sunday'; - break; - } -}; + // Make all cells in current row editable. + $(this).parent().parent().children().trigger('edit'); + $(this).parent().parent().find('.break-start input, .break-end input').timepicker({ + currentText: EALang['now'], + closeText: EALang['close'], + timeOnlyTitle: EALang['select_time'], + timeText: EALang['time'], + hourText: EALang['hour'], + minuteText: EALang['minutes'] + }); + $(this).parent().parent().find('.break-day select').focus(); + + // Show save - cancel buttons. + $(this).closest('table').find('.edit-break, .delete-break').addClass('hidden'); + $(this).parent().find('.save-break, .cancel-break').removeClass('hidden'); + + $('.add-break').prop('disabled', true); + }); + + /** + * Event: Delete Break Button "Click" + * + * Removes the current line from the "Breaks" table. + */ + $(document).on('click', '.delete-break', function() { + $(this).parent().parent().remove(); + }); + + /** + * Event: Cancel Break Button "Click" + * + * Bring the ".breaks" table back to its initial state. + */ + $(document).on('click', '.cancel-break', function(e) { + var element = e.target; + this.enableCancel = true; + $(element).parent().parent().find('.cancel-editable').trigger('click'); + this.enableCancel = false; + + $(element).closest('table').find('.edit-break, .delete-break').removeClass('hidden'); + $(element).parent().find('.save-break, .cancel-break').addClass('hidden'); + $('.add-break').prop('disabled', false); + }.bind(this)); + + /** + * Event: Save Break Button "Click" + * + * Save the editable values and restore the table to its initial state. + */ + $(document).on('click', '.save-break', function(e) { + // Break's start time must always be prior to break's end. + var element = e.target, + start = Date.parse($(element).parent().parent().find('.break-start input').val()), + end = Date.parse($(element).parent().parent().find('.break-end input').val()); + if (start > end) { + $(element).parent().parent().find('.break-end input').val(start.addHours(1).toString('HH:mm')); + } + + this.enableSubmit = true; + $(element).parent().parent().find('.editable .submit-editable').trigger('click'); + this.enableSubmit = false; + + $(element).parent().find('.save-break, .cancel-break').addClass('hidden'); + $(element).closest('table').find('.edit-break, .delete-break').removeClass('hidden'); + $('.add-break').prop('disabled', false); + }.bind(this)); + }; + + /** + * Get the working plan settings. + * + * @returns {object} Returns the working plan settings object. + */ + WorkingPlan.prototype.get = function() { + var workingPlan = {}; + $('.working-plan input[type="checkbox"]').each(function(index, checkbox) { + var id = $(checkbox).attr('id'); + if ($(checkbox).prop('checked') == true) { + workingPlan[id] = {}; + workingPlan[id].start = $('#' + id + '-start').val(); + workingPlan[id].end = $('#' + id + '-end').val(); + workingPlan[id].breaks = []; + + $('.breaks tr').each(function(index, tr) { + var day = this.convertDayToValue($(tr).find('.break-day').text()); + if (day == id) { + var start = $(tr).find('.break-start').text(), + end = $(tr).find('.break-end').text(); + + workingPlan[id].breaks.push({ + 'start': start, + 'end': end + }); + } + }.bind(this)); + } else { + workingPlan[id] = null; + } + }.bind(this)); + + return workingPlan; + }; + + /** + * Enables or disabled the timepicker functionality from the working plan input + * text fields. + * + * @param {bool} disabled (OPTIONAL = false) If true then the timepickers will be + * disabled. + */ + WorkingPlan.prototype.timepickers = function(disabled) { + disabled = disabled || false; + + if (disabled == false) { + // Set timepickers where needed. + $('.working-plan input[type="text"]').timepicker({ + timeFormat: 'HH:mm', + + currentText: EALang['now'], + closeText: EALang['close'], + timeOnlyTitle: EALang['select_time'], + timeText: EALang['time'], + hourText: EALang['hour'], + minuteText: EALang['minutes'], + + onSelect: function(datetime, inst) { + // Start time must be earlier than end time. + var start = Date.parse($(this).parent().parent().find('.work-start').val()), + end = Date.parse($(this).parent().parent().find('.work-end').val()); + + if (start > end) { + $(this).parent().parent().find('.work-end').val(start.addHours(1).toString('HH:mm')); + } + } + }); + } else { + $('.working-plan input').timepicker('destroy'); + } + }; + + /** + * Reset the current plan back to the company's default working plan. + */ + WorkingPlan.prototype.reset = function() { + + }; + + /** + * This is necessary for translated days. + * + * @param {string} value Day value could be like "monday", "tuesday" etc. + */ + WorkingPlan.prototype.convertValueToDay = function(value) { + switch (value) { + case 'monday': + return EALang['monday']; + break; + case 'tuesday': + return EALang['tuesday']; + break; + case 'wednesday': + return EALang['wednesday']; + break; + case 'thursday': + return EALang['thursday']; + break; + case 'friday': + return EALang['friday']; + break; + case 'saturday': + return EALang['saturday']; + break; + case 'sunday': + return EALang['sunday']; + break; + } + }; + + /** + * This is necessary for translated days. + * + * @param {string} value Day value could be like "Monday", "Tuesday" etc. + */ + WorkingPlan.prototype.convertDayToValue = function(day) { + switch (day) { + case EALang['monday']: + return 'monday'; + break; + case EALang['tuesday']: + return 'tuesday'; + break; + case EALang['wednesday']: + return 'wednesday'; + break; + case EALang['thursday']: + return 'thursday'; + break; + case EALang['friday']: + return 'friday'; + break; + case EALang['saturday']: + return 'saturday'; + break; + case EALang['sunday']: + return 'sunday'; + break; + } + }; + + window.WorkingPlan = WorkingPlan; + +})(); diff --git a/src/index.php b/src/index.php index 6d79f162..fc186491 100644 --- a/src/index.php +++ b/src/index.php @@ -1,31 +1,66 @@ =')) + { + error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT & ~E_USER_NOTICE & ~E_USER_DEPRECATED); + } + else + { + error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT & ~E_USER_NOTICE); + } + break; - default: - exit('The application environment is not set correctly.'); - } + default: + header('HTTP/1.1 503 Service Unavailable.', TRUE, 503); + echo 'The application environment is not set correctly.'; + exit(1); // EXIT_ERROR } /* *--------------------------------------------------------------- - * SYSTEM FOLDER NAME + * SYSTEM DIRECTORY NAME *--------------------------------------------------------------- * - * This variable must contain the name of your "system" folder. - * Include the path if the folder is not in the same directory - * as this file. - * + * This variable must contain the name of your "system" directory. + * Set the path if it is not in the same directory as this file. */ $system_path = 'system'; /* *--------------------------------------------------------------- - * APPLICATION FOLDER NAME + * APPLICATION DIRECTORY NAME *--------------------------------------------------------------- * * If you want this front controller to use a different "application" - * folder then the default one you can set its name here. The folder - * can also be renamed or relocated anywhere on your server. If - * you do, use a full server path. For more info please see the user guide: - * http://codeigniter.com/user_guide/general/managing_apps.html + * directory than the default one you can set its name here. The directory + * can also be renamed or relocated anywhere on your server. If you do, + * use an absolute (full) server path. + * For more info please see the user guide: + * + * https://codeigniter.com/user_guide/general/managing_apps.html * * NO TRAILING SLASH! - * */ $application_folder = 'application'; +/* + *--------------------------------------------------------------- + * VIEW DIRECTORY NAME + *--------------------------------------------------------------- + * + * If you want to move the view directory out of the application + * directory, set the path to it here. The directory can be renamed + * and relocated anywhere on your server. If blank, it will default + * to the standard location inside your application directory. + * If you do move this, use an absolute (full) server path. + * + * NO TRAILING SLASH! + */ + $view_folder = ''; + + /* * -------------------------------------------------------------------- * DEFAULT CONTROLLER @@ -108,24 +165,23 @@ if (defined('ENVIRONMENT')) * * Normally you will set your default controller in the routes.php file. * You can, however, force a custom routing by hard-coding a - * specific controller class/function here. For most applications, you + * specific controller class/function here. For most applications, you * WILL NOT set your routing here, but it's an option for those * special instances where you might want to override the standard * routing in a specific front controller that shares a common CI installation. * - * IMPORTANT: If you set the routing here, NO OTHER controller will be + * IMPORTANT: If you set the routing here, NO OTHER controller will be * callable. In essence, this preference limits your application to ONE - * specific controller. Leave the function name blank if you need + * specific controller. Leave the function name blank if you need * to call functions dynamically via the URI. * * Un-comment the $routing array below to use this feature - * */ - // The directory name, relative to the "controllers" folder. Leave blank - // if your controller is not in a sub-folder within the "controllers" folder + // The directory name, relative to the "controllers" directory. Leave blank + // if your controller is not in a sub-directory within the "controllers" one // $routing['directory'] = ''; - // The controller class file name. Example: Mycontroller + // The controller class file name. Example: mycontroller // $routing['controller'] = ''; // The controller function you wish to be called. @@ -145,7 +201,6 @@ if (defined('ENVIRONMENT')) * config values. * * Un-comment the $assign_to_config array below to use this feature - * */ // $assign_to_config['name_of_config_item'] = 'value of config item'; @@ -167,18 +222,26 @@ if (defined('ENVIRONMENT')) chdir(dirname(__FILE__)); } - if (realpath($system_path) !== FALSE) + if (($_temp = realpath($system_path)) !== FALSE) { - $system_path = realpath($system_path).'/'; + $system_path = $_temp.DIRECTORY_SEPARATOR; + } + else + { + // Ensure there's a trailing slash + $system_path = strtr( + rtrim($system_path, '/\\'), + '/\\', + DIRECTORY_SEPARATOR.DIRECTORY_SEPARATOR + ).DIRECTORY_SEPARATOR; } - - // ensure there's a trailing slash - $system_path = rtrim($system_path, '/').'/'; // Is the system path correct? if ( ! is_dir($system_path)) { - exit("Your system folder path does not appear to be set correctly. Please open the following file and correct this: ".pathinfo(__FILE__, PATHINFO_BASENAME)); + header('HTTP/1.1 503 Service Unavailable.', TRUE, 503); + echo 'Your system folder path does not appear to be set correctly. Please open the following file and correct this: '.pathinfo(__FILE__, PATHINFO_BASENAME); + exit(3); // EXIT_CONFIG } /* @@ -189,44 +252,90 @@ if (defined('ENVIRONMENT')) // The name of THIS file define('SELF', pathinfo(__FILE__, PATHINFO_BASENAME)); - // The PHP file extension - // this global constant is deprecated. - define('EXT', '.php'); + // Path to the system directory + define('BASEPATH', $system_path); - // Path to the system folder - define('BASEPATH', str_replace("\\", "/", $system_path)); + // Path to the front controller (this file) directory + define('FCPATH', dirname(__FILE__).DIRECTORY_SEPARATOR); - // Path to the front controller (this file) - define('FCPATH', str_replace(SELF, '', __FILE__)); + // Name of the "system" directory + define('SYSDIR', basename(BASEPATH)); - // Name of the "system folder" - define('SYSDIR', trim(strrchr(trim(BASEPATH, '/'), '/'), '/')); - - - // The path to the "application" folder + // The path to the "application" directory if (is_dir($application_folder)) { - define('APPPATH', $application_folder.'/'); + if (($_temp = realpath($application_folder)) !== FALSE) + { + $application_folder = $_temp; + } + else + { + $application_folder = strtr( + rtrim($application_folder, '/\\'), + '/\\', + DIRECTORY_SEPARATOR.DIRECTORY_SEPARATOR + ); + } + } + elseif (is_dir(BASEPATH.$application_folder.DIRECTORY_SEPARATOR)) + { + $application_folder = BASEPATH.strtr( + trim($application_folder, '/\\'), + '/\\', + DIRECTORY_SEPARATOR.DIRECTORY_SEPARATOR + ); } else { - if ( ! is_dir(BASEPATH.$application_folder.'/')) - { - exit("Your application folder path does not appear to be set correctly. Please open the following file and correct this: ".SELF); - } - - define('APPPATH', BASEPATH.$application_folder.'/'); + header('HTTP/1.1 503 Service Unavailable.', TRUE, 503); + echo 'Your application folder path does not appear to be set correctly. Please open the following file and correct this: '.SELF; + exit(3); // EXIT_CONFIG } + define('APPPATH', $application_folder.DIRECTORY_SEPARATOR); + + // The path to the "views" directory + if ( ! isset($view_folder[0]) && is_dir(APPPATH.'views'.DIRECTORY_SEPARATOR)) + { + $view_folder = APPPATH.'views'; + } + elseif (is_dir($view_folder)) + { + if (($_temp = realpath($view_folder)) !== FALSE) + { + $view_folder = $_temp; + } + else + { + $view_folder = strtr( + rtrim($view_folder, '/\\'), + '/\\', + DIRECTORY_SEPARATOR.DIRECTORY_SEPARATOR + ); + } + } + elseif (is_dir(APPPATH.$view_folder.DIRECTORY_SEPARATOR)) + { + $view_folder = APPPATH.strtr( + trim($view_folder, '/\\'), + '/\\', + DIRECTORY_SEPARATOR.DIRECTORY_SEPARATOR + ); + } + else + { + header('HTTP/1.1 503 Service Unavailable.', TRUE, 503); + echo 'Your view folder path does not appear to be set correctly. Please open the following file and correct this: '.SELF; + exit(3); // EXIT_CONFIG + } + + define('VIEWPATH', $view_folder.DIRECTORY_SEPARATOR); + /* * -------------------------------------------------------------------- * LOAD THE BOOTSTRAP FILE * -------------------------------------------------------------------- * * And away we go... - * */ require_once BASEPATH.'core/CodeIgniter.php'; - -/* End of file index.php */ -/* Location: ./index.php */ diff --git a/src/system/.htaccess b/src/system/.htaccess index 14249c50..97c65d2d 100644 --- a/src/system/.htaccess +++ b/src/system/.htaccess @@ -1 +1,6 @@ -Deny from all \ No newline at end of file + + Require all denied + + + Deny from all + \ No newline at end of file diff --git a/src/system/core/Benchmark.php b/src/system/core/Benchmark.php index daffa292..b1d74f78 100644 --- a/src/system/core/Benchmark.php +++ b/src/system/core/Benchmark.php @@ -1,62 +1,82 @@ -marker[$name] = microtime(); + $this->marker[$name] = microtime(TRUE); } // -------------------------------------------------------------------- /** + * Elapsed time + * * Calculates the time difference between two marked points. * * If the first parameter is empty this function instead returns the @@ -64,15 +84,17 @@ class CI_Benchmark { * 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 + * @param string $point1 A particular marked point + * @param string $point2 A particular marked point + * @param int $decimals Number of decimal places + * + * @return string Calculated elapsed time on success, + * an '{elapsed_string}' if $point1 is empty + * or an empty string if $point1 is not found. */ - function elapsed_time($point1 = '', $point2 = '', $decimals = 4) + public function elapsed_time($point1 = '', $point2 = '', $decimals = 4) { - if ($point1 == '') + if ($point1 === '') { return '{elapsed_time}'; } @@ -84,13 +106,10 @@ class CI_Benchmark { if ( ! isset($this->marker[$point2])) { - $this->marker[$point2] = microtime(); + $this->marker[$point2] = microtime(TRUE); } - list($sm, $ss) = explode(' ', $this->marker[$point1]); - list($em, $es) = explode(' ', $this->marker[$point2]); - - return number_format(($em + $es) - ($sm + $ss), $decimals); + return number_format($this->marker[$point2] - $this->marker[$point1], $decimals); } // -------------------------------------------------------------------- @@ -98,22 +117,17 @@ class CI_Benchmark { /** * Memory Usage * - * This function returns the {memory_usage} pseudo-variable. + * Simply returns the {memory_usage} marker. + * * This permits it to be put it anywhere in a template * without the memory being calculated until the end. * The output class will swap the real value for this variable. * - * @access public - * @return string + * @return string '{memory_usage}' */ - function memory_usage() + public 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/src/system/core/CodeIgniter.php b/src/system/core/CodeIgniter.php index 9f2e7510..3eb3e057 100644 --- a/src/system/core/CodeIgniter.php +++ b/src/system/core/CodeIgniter.php @@ -1,20 +1,41 @@ - '_ENV', 'G' => '_GET', 'P' => '_POST', 'C' => '_COOKIE', 'S' => '_SERVER') as $key => $superglobal) + { + if (strpos($_registered, $key) === FALSE) + { + continue; + } + + foreach (array_keys($$superglobal) as $var) + { + if (isset($GLOBALS[$var]) && ! in_array($var, $_protected, TRUE)) + { + $GLOBALS[$var] = NULL; + } + } + } } +} + /* * ------------------------------------------------------ * Define a custom error handler so we can log PHP errors * ------------------------------------------------------ */ - set_error_handler('_exception_handler'); - - if ( ! is_php('5.3')) - { - @set_magic_quotes_runtime(0); // Kill magic quotes - } + set_error_handler('_error_handler'); + set_exception_handler('_exception_handler'); + register_shutdown_function('_shutdown_handler'); /* * ------------------------------------------------------ @@ -86,26 +145,39 @@ * The subclass prefix allows CI to know if a core class is * being extended via a library in the local application * "libraries" folder. Since CI allows config items to be - * overriden via data set in the main index. php file, + * overridden via data set in the main index.php file, * before proceeding we need to know if a subclass_prefix - * override exists. If so, we will set this value now, + * override exists. If so, we will set this value now, * before any classes are loaded * Note: Since the config file data is cached it doesn't * hurt to load it here. */ - if (isset($assign_to_config['subclass_prefix']) AND $assign_to_config['subclass_prefix'] != '') + if ( ! empty($assign_to_config['subclass_prefix'])) { get_config(array('subclass_prefix' => $assign_to_config['subclass_prefix'])); } /* * ------------------------------------------------------ - * Set a liberal script execution time limit + * Should we use a Composer autoloader? * ------------------------------------------------------ */ - if (function_exists("set_time_limit") == TRUE AND @ini_get("safe_mode") == 0) + if ($composer_autoload = config_item('composer_autoload')) { - @set_time_limit(300); + if ($composer_autoload === TRUE) + { + file_exists(APPPATH.'vendor/autoload.php') + ? require_once(APPPATH.'vendor/autoload.php') + : log_message('error', '$config[\'composer_autoload\'] is set to TRUE but '.APPPATH.'vendor/autoload.php was not found.'); + } + elseif (file_exists($composer_autoload)) + { + require_once($composer_autoload); + } + else + { + log_message('error', 'Could not find the specified $config[\'composer_autoload\'] path: '.$composer_autoload); + } } /* @@ -129,33 +201,96 @@ * Is there a "pre_system" hook? * ------------------------------------------------------ */ - $EXT->_call_hook('pre_system'); + $EXT->call_hook('pre_system'); /* * ------------------------------------------------------ * Instantiate the config class * ------------------------------------------------------ + * + * Note: It is important that Config is loaded first as + * most other classes depend on it either directly or by + * depending on another class that uses it. + * */ $CFG =& load_class('Config', 'core'); // Do we have any manually set config items in the index.php file? - if (isset($assign_to_config)) + if (isset($assign_to_config) && is_array($assign_to_config)) { - $CFG->_assign_to_config($assign_to_config); + foreach ($assign_to_config as $key => $value) + { + $CFG->set_item($key, $value); + } } +/* + * ------------------------------------------------------ + * Important charset-related stuff + * ------------------------------------------------------ + * + * Configure mbstring and/or iconv if they are enabled + * and set MB_ENABLED and ICONV_ENABLED constants, so + * that we don't repeatedly do extension_loaded() or + * function_exists() calls. + * + * Note: UTF-8 class depends on this. It used to be done + * in it's constructor, but it's _not_ class-specific. + * + */ + $charset = strtoupper(config_item('charset')); + ini_set('default_charset', $charset); + + if (extension_loaded('mbstring')) + { + define('MB_ENABLED', TRUE); + // mbstring.internal_encoding is deprecated starting with PHP 5.6 + // and it's usage triggers E_DEPRECATED messages. + @ini_set('mbstring.internal_encoding', $charset); + // This is required for mb_convert_encoding() to strip invalid characters. + // That's utilized by CI_Utf8, but it's also done for consistency with iconv. + mb_substitute_character('none'); + } + else + { + define('MB_ENABLED', FALSE); + } + + // There's an ICONV_IMPL constant, but the PHP manual says that using + // iconv's predefined constants is "strongly discouraged". + if (extension_loaded('iconv')) + { + define('ICONV_ENABLED', TRUE); + // iconv.internal_encoding is deprecated starting with PHP 5.6 + // and it's usage triggers E_DEPRECATED messages. + @ini_set('iconv.internal_encoding', $charset); + } + else + { + define('ICONV_ENABLED', FALSE); + } + + if (is_php('5.6')) + { + ini_set('php.internal_encoding', $charset); + } + +/* + * ------------------------------------------------------ + * Load compatibility features + * ------------------------------------------------------ + */ + + require_once(BASEPATH.'core/compat/mbstring.php'); + require_once(BASEPATH.'core/compat/hash.php'); + require_once(BASEPATH.'core/compat/password.php'); + require_once(BASEPATH.'core/compat/standard.php'); + /* * ------------------------------------------------------ * Instantiate the UTF-8 class * ------------------------------------------------------ - * - * 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'); /* @@ -170,14 +305,7 @@ * 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); - } + $RTR =& load_class('Router', 'core', isset($routing) ? $routing : NULL); /* * ------------------------------------------------------ @@ -188,15 +316,12 @@ /* * ------------------------------------------------------ - * Is there a valid cache file? If so, we're done... + * Is there a valid cache file? If so, we're done... * ------------------------------------------------------ */ - if ($EXT->_call_hook('cache_override') === FALSE) + if ($EXT->call_hook('cache_override') === FALSE && $OUT->_display_cache($CFG, $URI) === TRUE) { - if ($OUT->_display_cache($CFG, $URI) == TRUE) - { - exit; - } + exit; } /* @@ -227,76 +352,142 @@ * */ // Load the base controller class - require BASEPATH.'core/Controller.php'; + require_once BASEPATH.'core/Controller.php'; + /** + * Reference to the CI_Controller method. + * + * Returns current CI instance object + * + * @return CI_Controller + */ function &get_instance() { return CI_Controller::get_instance(); } - if (file_exists(APPPATH.'core/'.$CFG->config['subclass_prefix'].'Controller.php')) { - require APPPATH.'core/'.$CFG->config['subclass_prefix'].'Controller.php'; + require_once 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 + * Sanity checks * ------------------------------------------------------ * - * 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 + * The Router class has already validated the request, + * leaving us with 3 options here: + * + * 1) an empty class name, if we reached the default + * controller, but it didn't exist; + * 2) a query string which doesn't go through a + * file_exists() check + * 3) a regular request for a non-existing page + * + * We handle all of these as a 404 error. + * + * Furthermore, none of the methods in the app controller + * or the loader class can be called via the URI, nor can + * controller methods that begin with an underscore. */ - $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'))) - ) + $e404 = FALSE; + $class = ucfirst($RTR->class); + $method = $RTR->method; + + if (empty($class) OR ! file_exists(APPPATH.'controllers/'.$RTR->directory.$class.'.php')) + { + $e404 = TRUE; + } + else + { + require_once(APPPATH.'controllers/'.$RTR->directory.$class.'.php'); + + if ( ! class_exists($class, FALSE) OR $method[0] === '_' OR method_exists('CI_Controller', $method)) + { + $e404 = TRUE; + } + elseif (method_exists($class, '_remap')) + { + $params = array($method, array_slice($URI->rsegments, 2)); + $method = '_remap'; + } + // WARNING: It appears that there are issues with is_callable() even in PHP 5.2! + // Furthermore, there are bug reports and feature/change requests related to it + // that make it unreliable to use in this context. Please, DO NOT change this + // work-around until a better alternative is available. + elseif ( ! in_array(strtolower($method), array_map('strtolower', get_class_methods($class)), TRUE)) + { + $e404 = TRUE; + } + } + + if ($e404) { 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 (sscanf($RTR->routes['404_override'], '%[^/]/%s', $error_class, $error_method) !== 2) { - if ( ! file_exists(APPPATH.'controllers/'.$class.'.php')) - { - show_404("{$class}/{$method}"); - } - - include_once(APPPATH.'controllers/'.$class.'.php'); + $error_method = 'index'; } + + $error_class = ucfirst($error_class); + + if ( ! class_exists($error_class, FALSE)) + { + if (file_exists(APPPATH.'controllers/'.$RTR->directory.$error_class.'.php')) + { + require_once(APPPATH.'controllers/'.$RTR->directory.$error_class.'.php'); + $e404 = ! class_exists($error_class, FALSE); + } + // Were we in a directory? If so, check for a global override + elseif ( ! empty($RTR->directory) && file_exists(APPPATH.'controllers/'.$error_class.'.php')) + { + require_once(APPPATH.'controllers/'.$error_class.'.php'); + if (($e404 = ! class_exists($error_class, FALSE)) === FALSE) + { + $RTR->directory = ''; + } + } + } + else + { + $e404 = FALSE; + } + } + + // Did we reset the $e404 flag? If so, set the rsegments, starting from index 1 + if ( ! $e404) + { + $class = $error_class; + $method = $error_method; + + $URI->rsegments = array( + 1 => $class, + 2 => $method + ); } else { - show_404("{$class}/{$method}"); + show_404($RTR->directory.$class.'/'.$method); } } + if ($method !== '_remap') + { + $params = array_slice($URI->rsegments, 2); + } + /* * ------------------------------------------------------ * Is there a "pre_controller" hook? * ------------------------------------------------------ */ - $EXT->_call_hook('pre_controller'); + $EXT->call_hook('pre_controller'); /* * ------------------------------------------------------ @@ -313,53 +504,14 @@ * Is there a "post_controller_constructor" hook? * ------------------------------------------------------ */ - $EXT->_call_hook('post_controller_constructor'); + $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)); - } - + call_user_func_array(array(&$CI, $method), $params); // Mark a benchmark end point $BM->mark('controller_execution_time_( '.$class.' / '.$method.' )_end'); @@ -369,14 +521,14 @@ * Is there a "post_controller" hook? * ------------------------------------------------------ */ - $EXT->_call_hook('post_controller'); + $EXT->call_hook('post_controller'); /* * ------------------------------------------------------ * Send the final rendered output to the browser * ------------------------------------------------------ */ - if ($EXT->_call_hook('display_override') === FALSE) + if ($EXT->call_hook('display_override') === FALSE) { $OUT->_display(); } @@ -386,18 +538,4 @@ * 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 + $EXT->call_hook('post_system'); diff --git a/src/system/core/Common.php b/src/system/core/Common.php index 5e56aa09..b87ce4d6 100644 --- a/src/system/core/Common.php +++ b/src/system/core/Common.php @@ -1,20 +1,41 @@ -='); } return $_is_php[$version]; @@ -55,43 +75,44 @@ if ( ! function_exists('is_php')) // ------------------------------------------------------------------------ -/** - * 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')) { + /** + * Tests for file writability + * + * is_writable() returns TRUE on Windows servers when you really can't write to + * the file, based on the read-only attribute. is_writable() is also unreliable + * on Unix servers if safe_mode is on. + * + * @link https://bugs.php.net/bug.php?id=54709 + * @param string + * @return bool + */ function is_really_writable($file) { // If we're on a Unix server with safe_mode off we call is_writable - if (DIRECTORY_SEPARATOR == '/' AND @ini_get("safe_mode") == FALSE) + if (DIRECTORY_SEPARATOR === '/' && (is_php('5.4') OR ! ini_get('safe_mode'))) { return is_writable($file); } - // For windows servers and safe_mode "on" installations we'll actually - // write a file then read it. Bah... + /* 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) + $file = rtrim($file, '/').'/'.md5(mt_rand()); + if (($fp = @fopen($file, 'ab')) === FALSE) { return FALSE; } fclose($fp); - @chmod($file, DIR_WRITE_MODE); + @chmod($file, 0777); @unlink($file); return TRUE; } - elseif ( ! is_file($file) OR ($fp = @fopen($file, FOPEN_WRITE_CREATE)) === FALSE) + elseif ( ! is_file($file) OR ($fp = @fopen($file, 'ab')) === FALSE) { return FALSE; } @@ -103,26 +124,25 @@ if ( ! function_exists('is_really_writable')) // ------------------------------------------------------------------------ -/** -* 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_') + /** + * Class registry + * + * This function acts as a singleton. If the requested class does not + * exist it is instantiated and set to a static variable. If it has + * previously been instantiated the variable is returned. + * + * @param string the class name being requested + * @param string the directory where the class should be found + * @param string an optional argument to pass to the class constructor + * @return object + */ + function &load_class($class, $directory = 'libraries', $param = NULL) { static $_classes = array(); - // Does the class exist? If so, we're done... + // Does the class exist? If so, we're done... if (isset($_classes[$class])) { return $_classes[$class]; @@ -136,60 +156,64 @@ if ( ! function_exists('load_class')) { if (file_exists($path.$directory.'/'.$class.'.php')) { - $name = $prefix.$class; + $name = 'CI_'.$class; - if (class_exists($name) === FALSE) + if (class_exists($name, FALSE) === FALSE) { - require($path.$directory.'/'.$class.'.php'); + require_once($path.$directory.'/'.$class.'.php'); } break; } } - // Is the request a class extension? If so we load it too + // 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) + if (class_exists($name, FALSE) === FALSE) { - require(APPPATH.$directory.'/'.config_item('subclass_prefix').$class.'.php'); + require_once(APPPATH.$directory.'/'.$name.'.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'); + // Note: We use exit() rather than show_error() in order to avoid a + // self-referencing loop with the Exceptions class + set_status_header(503); + echo 'Unable to locate the specified class: '.$class.'.php'; + exit(5); // EXIT_UNK_CLASS } // Keep track of what we just loaded is_loaded($class); - $_classes[$class] = new $name(); + $_classes[$class] = isset($param) + ? new $name($param) + : 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')) { + /** + * Keeps track of which libraries have been loaded. This function is + * called by the load_class() function above + * + * @param string + * @return array + */ function &is_loaded($class = '') { static $_is_loaded = array(); - if ($class != '') + if ($class !== '') { $_is_loaded[strtolower($class)] = $class; } @@ -200,326 +224,502 @@ if ( ! function_exists('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()) + /** + * Loads the main config.php file + * + * This function lets us grab the config file even if the Config class + * hasn't been instantiated yet + * + * @param array + * @return array + */ + function &get_config(Array $replace = array()) + { + static $config; + + if (empty($config)) + { + $file_path = APPPATH.'config/config.php'; + $found = FALSE; + if (file_exists($file_path)) + { + $found = TRUE; + require($file_path); + } + + // Is the config file in the environment folder? + if (file_exists($file_path = APPPATH.'config/'.ENVIRONMENT.'/config.php')) + { + require($file_path); + } + elseif ( ! $found) + { + set_status_header(503); + echo 'The configuration file does not exist.'; + exit(3); // EXIT_CONFIG + } + + // Does the $config array exist in the file? + if ( ! isset($config) OR ! is_array($config)) + { + set_status_header(503); + echo 'Your config file does not appear to be formatted correctly.'; + exit(3); // EXIT_CONFIG + } + } + + // Are any values being dynamically added or replaced? + foreach ($replace as $key => $val) + { + $config[$key] = $val; + } + + return $config; + } +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('config_item')) +{ + /** + * Returns the specified config item + * + * @param string + * @return mixed + */ + function config_item($item) { static $_config; - if (isset($_config)) + if (empty($_config)) { - return $_config[0]; + // references cannot be directly assigned to static variables, so we use an array + $_config[0] =& get_config(); } - // 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; - } - } - } - - $_config[0] =& $config; - return $_config[0]; + return isset($_config[0][$item]) ? $_config[0][$item] : NULL; } } // ------------------------------------------------------------------------ -/** -* Returns the specified config item -* -* @access public -* @return mixed -*/ -if ( ! function_exists('config_item')) +if ( ! function_exists('get_mimes')) { - function config_item($item) + /** + * Returns the MIME types array from config/mimes.php + * + * @return array + */ + function &get_mimes() { - static $_config_item = array(); + static $_mimes; - if ( ! isset($_config_item[$item])) + if (empty($_mimes)) { - $config =& get_config(); - - if ( ! isset($config[$item])) + if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/mimes.php')) { - return FALSE; + $_mimes = include(APPPATH.'config/'.ENVIRONMENT.'/mimes.php'); + } + elseif (file_exists(APPPATH.'config/mimes.php')) + { + $_mimes = include(APPPATH.'config/mimes.php'); + } + else + { + $_mimes = array(); } - $_config_item[$item] = $config[$item]; } - return $_config_item[$item]; + return $_mimes; + } +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('is_https')) +{ + /** + * Is HTTPS? + * + * Determines if the application is accessed via an encrypted + * (HTTPS) connection. + * + * @return bool + */ + function is_https() + { + if ( ! empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off') + { + return TRUE; + } + elseif (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') + { + return TRUE; + } + elseif ( ! empty($_SERVER['HTTP_FRONT_END_HTTPS']) && strtolower($_SERVER['HTTP_FRONT_END_HTTPS']) !== 'off') + { + return TRUE; + } + + return FALSE; + } +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('is_cli')) +{ + + /** + * Is CLI? + * + * Test to see if a request was made from the command line. + * + * @return bool + */ + function is_cli() + { + return (PHP_SAPI === 'cli' OR defined('STDIN')); } } // ------------------------------------------------------------------------ -/** -* 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')) { + /** + * Error Handler + * + * This function lets us invoke the exception class and + * display errors using the standard error template located + * in application/views/errors/error_general.php + * This function will send the error page directly to the + * browser and exit. + * + * @param string + * @param int + * @param string + * @return void + */ function show_error($message, $status_code = 500, $heading = 'An Error Was Encountered') { + $status_code = abs($status_code); + if ($status_code < 100) + { + $exit_status = $status_code + 9; // 9 is EXIT__AUTO_MIN + if ($exit_status > 125) // 125 is EXIT__AUTO_MAX + { + $exit_status = 1; // EXIT_ERROR + } + + $status_code = 500; + } + else + { + $exit_status = 1; // EXIT_ERROR + } + $_error =& load_class('Exceptions', 'core'); echo $_error->show_error($heading, $message, 'error_general', $status_code); - exit; + exit($exit_status); } } // ------------------------------------------------------------------------ -/** -* 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')) { + /** + * 404 Page Handler + * + * This function is similar to the show_error() function above + * However, instead of the standard error template it displays + * 404 errors. + * + * @param string + * @param bool + * @return void + */ function show_404($page = '', $log_error = TRUE) { $_error =& load_class('Exceptions', 'core'); $_error->show_404($page, $log_error); - exit; + exit(4); // EXIT_UNKNOWN_FILE } } // ------------------------------------------------------------------------ -/** -* 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) + /** + * Error Logging Interface + * + * We use this as a simple mechanism to access the logging + * class and send messages to be logged. + * + * @param string the error level: 'error', 'debug' or 'info' + * @param string the error message + * @return void + */ + function log_message($level, $message) { static $_log; - if (config_item('log_threshold') == 0) + if ($_log === NULL) + { + // references cannot be directly assigned to static variables, so we use an array + $_log[0] =& load_class('Log', 'core'); + } + + $_log[0]->write_log($level, $message); + } +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('set_status_header')) +{ + /** + * Set HTTP Status Header + * + * @param int the status code + * @param string + * @return void + */ + function set_status_header($code = 200, $text = '') + { + if (is_cli()) { return; } - $_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)) + if (empty($code) OR ! is_numeric($code)) { show_error('Status codes must be numeric', 500); } - if (isset($stati[$code]) AND $text == '') + if (empty($text)) { - $text = $stati[$code]; + is_int($code) OR $code = (int) $code; + $stati = array( + 100 => 'Continue', + 101 => 'Switching Protocols', + + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 307 => 'Temporary Redirect', + + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 422 => 'Unprocessable Entity', + + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported' + ); + + if (isset($stati[$code])) + { + $text = $stati[$code]; + } + else + { + show_error('No status text available. Please check your status code number or supply your own message text.', 500); + } } - if ($text == '') + if (strpos(PHP_SAPI, 'cgi') === 0) { - 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); + header('Status: '.$code.' '.$text, TRUE); } else { - header("HTTP/1.1 {$code} {$text}", TRUE, $code); + $server_protocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1'; + header($server_protocol.' '.$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')) +if ( ! function_exists('_error_handler')) { - function _exception_handler($severity, $message, $filepath, $line) + /** + * Error Handler + * + * This is the custom error handler that is declared at the (relative) + * top of CodeIgniter.php. The main reason we use this is to permit + * PHP errors to be logged in our own log files since the user may + * not have access to server logs. Since this function effectively + * intercepts PHP errors, however, we also need to display errors + * based on the current error_reporting level. + * We do that with the use of a PHP error template. + * + * @param int $severity + * @param string $message + * @param string $filepath + * @param int $line + * @return void + */ + function _error_handler($severity, $message, $filepath, $line) { - // 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. - if ($severity == E_STRICT) + $is_error = (((E_ERROR | E_COMPILE_ERROR | E_CORE_ERROR | E_USER_ERROR) & $severity) === $severity); + + // When an error occurred, set the status header to '500 Internal Server Error' + // to indicate to the client something went wrong. + // This can't be done within the $_error->show_php_error method because + // it is only called when the display_errors flag is set (which isn't usually + // the case in a production environment) or when errors are ignored because + // they are above the error_reporting threshold. + if ($is_error) + { + set_status_header(500); + } + + // Should we ignore the error? We'll get the current error_reporting + // level and add its bits with the severity bits to find out. + if (($severity & error_reporting()) !== $severity) { return; } $_error =& load_class('Exceptions', 'core'); + $_error->log_exception($severity, $message, $filepath, $line); - // Should we display the error? We'll get the current error_reporting - // level and add its bits with the severity bits to find out. - if (($severity & error_reporting()) == $severity) + // Should we display the error? + if (str_ireplace(array('off', 'none', 'no', 'false', 'null'), '', ini_get('display_errors'))) { $_error->show_php_error($severity, $message, $filepath, $line); } - // Should we log the error? No? We're done... - if (config_item('log_threshold') == 0) + // If the error is fatal, the execution of the script should be stopped because + // errors can't be recovered from. Halting the script conforms with PHP's + // default error handling. See http://www.php.net/manual/en/errorfunc.constants.php + if ($is_error) { - return; + exit(1); // EXIT_ERROR + } + } +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('_exception_handler')) +{ + /** + * Exception Handler + * + * Sends uncaught exceptions to the logger and displays them + * only if display_errors is On so that they don't show up in + * production environments. + * + * @param Exception $exception + * @return void + */ + function _exception_handler($exception) + { + $_error =& load_class('Exceptions', 'core'); + $_error->log_exception('error', 'Exception: '.$exception->getMessage(), $exception->getFile(), $exception->getLine()); + + // Should we display the error? + if (str_ireplace(array('off', 'none', 'no', 'false', 'null'), '', ini_get('display_errors'))) + { + $_error->show_exception($exception); } - $_error->log_exception($severity, $message, $filepath, $line); + exit(1); // EXIT_ERROR + } +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('_shutdown_handler')) +{ + /** + * Shutdown Handler + * + * This is the shutdown handler that is declared at the top + * of CodeIgniter.php. The main reason we use this is to simulate + * a complete custom exception handler. + * + * E_STRICT is purposively neglected because such events may have + * been caught. Duplication or none? None is preferred for now. + * + * @link http://insomanic.me.uk/post/229851073/php-trick-catching-fatal-errors-e-error-with-a + * @return void + */ + function _shutdown_handler() + { + $last_error = error_get_last(); + if (isset($last_error) && + ($last_error['type'] & (E_ERROR | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING))) + { + _error_handler($last_error['type'], $last_error['message'], $last_error['file'], $last_error['line']); + } } } // -------------------------------------------------------------------- -/** - * 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')) { + /** + * Remove Invisible Characters + * + * This prevents sandwiching null characters + * between ascii characters, like Java\0script. + * + * @param string + * @param bool + * @return string + */ function remove_invisible_characters($str, $url_encoded = TRUE) { $non_displayables = array(); - - // every control character except newline (dec 10) - // carriage return (dec 13), and horizontal tab (dec 09) - + + // 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 @@ -534,27 +734,118 @@ if ( ! function_exists('remove_invisible_characters')) // ------------------------------------------------------------------------ -/** -* Returns HTML escaped variable -* -* @access public -* @param mixed -* @return mixed -*/ if ( ! function_exists('html_escape')) { - function html_escape($var) + /** + * Returns HTML escaped variable. + * + * @param mixed $var The input string or array of strings to be escaped. + * @param bool $double_encode $double_encode set to FALSE prevents escaping twice. + * @return mixed The escaped string or array of strings as a result. + */ + function html_escape($var, $double_encode = TRUE) { + if (empty($var)) + { + return $var; + } + if (is_array($var)) { - return array_map('html_escape', $var); - } - else - { - return htmlspecialchars($var, ENT_QUOTES, config_item('charset')); + foreach (array_keys($var) as $key) + { + $var[$key] = html_escape($var[$key], $double_encode); + } + + return $var; } + + return htmlspecialchars($var, ENT_QUOTES, config_item('charset'), $double_encode); } } -/* End of file Common.php */ -/* Location: ./system/core/Common.php */ \ No newline at end of file +// ------------------------------------------------------------------------ + +if ( ! function_exists('_stringify_attributes')) +{ + /** + * Stringify attributes for use in HTML tags. + * + * Helper function used to convert a string, array, or object + * of attributes to a string. + * + * @param mixed string, array, object + * @param bool + * @return string + */ + function _stringify_attributes($attributes, $js = FALSE) + { + $atts = NULL; + + if (empty($attributes)) + { + return $atts; + } + + if (is_string($attributes)) + { + return ' '.$attributes; + } + + $attributes = (array) $attributes; + + foreach ($attributes as $key => $val) + { + $atts .= ($js) ? $key.'='.$val.',' : ' '.$key.'="'.$val.'"'; + } + + return rtrim($atts, ','); + } +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('function_usable')) +{ + /** + * Function usable + * + * Executes a function_exists() check, and if the Suhosin PHP + * extension is loaded - checks whether the function that is + * checked might be disabled in there as well. + * + * This is useful as function_exists() will return FALSE for + * functions disabled via the *disable_functions* php.ini + * setting, but not for *suhosin.executor.func.blacklist* and + * *suhosin.executor.disable_eval*. These settings will just + * terminate script execution if a disabled function is executed. + * + * The above described behavior turned out to be a bug in Suhosin, + * but even though a fix was commited for 0.9.34 on 2012-02-12, + * that version is yet to be released. This function will therefore + * be just temporary, but would probably be kept for a few years. + * + * @link http://www.hardened-php.net/suhosin/ + * @param string $function_name Function to check for + * @return bool TRUE if the function exists and is safe to call, + * FALSE otherwise. + */ + function function_usable($function_name) + { + static $_suhosin_func_blacklist; + + if (function_exists($function_name)) + { + if ( ! isset($_suhosin_func_blacklist)) + { + $_suhosin_func_blacklist = extension_loaded('suhosin') + ? explode(',', trim(ini_get('suhosin.executor.func.blacklist'))) + : array(); + } + + return ! in_array($function_name, $_suhosin_func_blacklist, TRUE); + } + + return FALSE; + } +} diff --git a/src/system/core/Config.php b/src/system/core/Config.php index 45641faa..ca6fb379 100644 --- a/src/system/core/Config.php +++ b/src/system/core/Config.php @@ -1,23 +1,44 @@ -config =& get_config(); - log_message('debug', "Config Class Initialized"); // Set the base_url automatically if none was provided - if ($this->config['base_url'] == '') + if (empty($this->config['base_url'])) { - // The regular expression is only a basic validation for a valid "Host" header. - // It's not exhaustive, only checks for valid characters. - if (isset($_SERVER['HTTP_HOST']) && preg_match('/^((\[[0-9a-f:]+\])|(\d{1,3}(\.\d{1,3}){3})|[a-z0-9\-\.]+)(:\d+)?$/i', $_SERVER['HTTP_HOST'])) + if (isset($_SERVER['SERVER_ADDR'])) { - $base_url = (empty($_SERVER['HTTPS']) OR strtolower($_SERVER['HTTPS']) === 'off') ? 'http' : 'https'; - $base_url .= '://'. $_SERVER['HTTP_HOST']; - $base_url .= substr($_SERVER['SCRIPT_NAME'], 0, strpos($_SERVER['SCRIPT_NAME'], basename($_SERVER['SCRIPT_FILENAME']))); - } + if (strpos($_SERVER['SERVER_ADDR'], ':') !== FALSE) + { + $server_addr = '['.$_SERVER['SERVER_ADDR'].']'; + } + else + { + $server_addr = $_SERVER['SERVER_ADDR']; + } + $base_url = (is_https() ? 'https' : 'http').'://'.$server_addr + .substr($_SERVER['SCRIPT_NAME'], 0, strpos($_SERVER['SCRIPT_NAME'], basename($_SERVER['SCRIPT_FILENAME']))); + } else { $base_url = 'http://localhost/'; @@ -83,6 +109,8 @@ class CI_Config { $this->set_item('base_url', $base_url); } + + log_message('info', 'Config Class Initialized'); } // -------------------------------------------------------------------- @@ -90,91 +118,71 @@ class CI_Config { /** * 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 + * @param string $file Configuration file name + * @param bool $use_sections Whether configuration values should be loaded into their own section + * @param bool $fail_gracefully Whether to just return FALSE or display an error message + * @return bool TRUE if the file was loaded correctly or FALSE on failure */ - function load($file = '', $use_sections = FALSE, $fail_gracefully = FALSE) + public function load($file = '', $use_sections = FALSE, $fail_gracefully = FALSE) { - $file = ($file == '') ? 'config' : str_replace('.php', '', $file); - $found = FALSE; + $file = ($file === '') ? 'config' : str_replace('.php', '', $file); $loaded = FALSE; - $check_locations = defined('ENVIRONMENT') - ? array(ENVIRONMENT.'/'.$file, $file) - : array($file); - foreach ($this->_config_paths as $path) { - foreach ($check_locations as $location) + foreach (array($file, ENVIRONMENT.DIRECTORY_SEPARATOR.$file) as $location) { $file_path = $path.'config/'.$location.'.php'; - if (in_array($file_path, $this->is_loaded, TRUE)) { - $loaded = TRUE; - continue 2; + return TRUE; } - if (file_exists($file_path)) + if ( ! file_exists($file_path)) { - $found = TRUE; - break; + continue; } - } - if ($found === FALSE) - { - continue; - } + include($file_path); - include($file_path); - - if ( ! isset($config) OR ! is_array($config)) - { - if ($fail_gracefully === TRUE) + if ( ! isset($config) OR ! is_array($config)) { - return FALSE; + if ($fail_gracefully === TRUE) + { + return FALSE; + } + + show_error('Your '.$file_path.' file does not appear to contain a valid configuration array.'); } - show_error('Your '.$file_path.' file does not appear to contain a valid configuration array.'); - } - if ($use_sections === TRUE) - { - if (isset($this->config[$file])) + if ($use_sections === TRUE) { - $this->config[$file] = array_merge($this->config[$file], $config); + $this->config[$file] = isset($this->config[$file]) + ? array_merge($this->config[$file], $config) + : $config; } else { - $this->config[$file] = $config; + $this->config = array_merge($this->config, $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; + $this->is_loaded[] = $file_path; + $config = NULL; + $loaded = TRUE; + log_message('debug', 'Config file loaded: '.$file_path); + } } - if ($loaded === FALSE) + if ($loaded === TRUE) { - if ($fail_gracefully === TRUE) - { - return FALSE; - } - show_error('The configuration file '.$file.'.php does not exist.'); + return TRUE; + } + elseif ($fail_gracefully === TRUE) + { + return FALSE; } - return TRUE; + show_error('The configuration file '.$file.'.php does not exist.'); } // -------------------------------------------------------------------- @@ -182,59 +190,35 @@ class CI_Config { /** * Fetch a config file item * - * - * @access public - * @param string the config item name - * @param string the index name - * @param bool - * @return string + * @param string $item Config item name + * @param string $index Index name + * @return string|null The configuration item or NULL if the item doesn't exist */ - function item($item, $index = '') + public 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 isset($this->config[$item]) ? $this->config[$item] : NULL; } - return $pref; + return isset($this->config[$index], $this->config[$index][$item]) ? $this->config[$index][$item] : NULL; } // -------------------------------------------------------------------- /** - * Fetch a config file item - adds slash after item (if item is not empty) + * Fetch a config file item with slash appended (if not empty) * - * @access public - * @param string the config item name - * @param bool - * @return string + * @param string $item Config item name + * @return string|null The configuration item or NULL if the item doesn't exist */ - function slash_item($item) + public function slash_item($item) { if ( ! isset($this->config[$item])) { - return FALSE; + return NULL; } - if( trim($this->config[$item]) == '') + elseif (trim($this->config[$item]) === '') { return ''; } @@ -246,80 +230,125 @@ class CI_Config { /** * Site URL + * * Returns base_url . index_page [. uri_string] * - * @access public - * @param string the URI string + * @uses CI_Config::_uri_string() + * + * @param string|string[] $uri URI string or an array of segments + * @param string $protocol * @return string */ - function site_url($uri = '') + public function site_url($uri = '', $protocol = NULL) { - if ($uri == '') + $base_url = $this->slash_item('base_url'); + + if (isset($protocol)) { - return $this->slash_item('base_url').$this->item('index_page'); + // For protocol-relative links + if ($protocol === '') + { + $base_url = substr($base_url, strpos($base_url, '//')); + } + else + { + $base_url = $protocol.substr($base_url, strpos($base_url, '://')); + } } - if ($this->item('enable_query_strings') == FALSE) + if (empty($uri)) { - $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; + return $base_url.$this->item('index_page'); } - else + + $uri = $this->_uri_string($uri); + + if ($this->item('enable_query_strings') === FALSE) { - return $this->slash_item('base_url').$this->item('index_page').'?'.$this->_uri_string($uri); + $suffix = isset($this->config['url_suffix']) ? $this->config['url_suffix'] : ''; + + if ($suffix !== '') + { + if (($offset = strpos($uri, '?')) !== FALSE) + { + $uri = substr($uri, 0, $offset).$suffix.substr($uri, $offset); + } + else + { + $uri .= $suffix; + } + } + + return $base_url.$this->slash_item('index_page').$uri; } + elseif (strpos($uri, '?') === FALSE) + { + $uri = '?'.$uri; + } + + return $base_url.$this->item('index_page').$uri; } // ------------------------------------------------------------- /** * Base URL + * * Returns base_url [. uri_string] * - * @access public - * @param string $uri - * @return string + * @uses CI_Config::_uri_string() + * + * @param string|string[] $uri URI string or an array of segments + * @param string $protocol + * @return string */ - function base_url($uri = '') + public function base_url($uri = '', $protocol = NULL) { - return $this->slash_item('base_url').ltrim($this->_uri_string($uri), '/'); + $base_url = $this->slash_item('base_url'); + + if (isset($protocol)) + { + // For protocol-relative links + if ($protocol === '') + { + $base_url = substr($base_url, strpos($base_url, '//')); + } + else + { + $base_url = $protocol.substr($base_url, strpos($base_url, '://')); + } + } + + return $base_url.ltrim($this->_uri_string($uri), '/'); } // ------------------------------------------------------------- /** - * Build URI string for use in Config::site_url() and Config::base_url() + * Build URI string * - * @access protected - * @param $uri - * @return string + * @used-by CI_Config::site_url() + * @used-by CI_Config::base_url() + * + * @param string|string[] $uri URI string or an array of segments + * @return string */ protected function _uri_string($uri) { - if ($this->item('enable_query_strings') == FALSE) + if ($this->item('enable_query_strings') === FALSE) { if (is_array($uri)) { $uri = implode('/', $uri); } - $uri = trim($uri, '/'); + return trim($uri, '/'); } - else + elseif (is_array($uri)) { - if (is_array($uri)) - { - $i = 0; - $str = ''; - foreach ($uri as $key => $val) - { - $prefix = ($i == 0) ? '' : '&'; - $str .= $prefix.$key.'='.$val; - $i++; - } - $uri = $str; - } + return http_build_query($uri); } - return $uri; + + return $uri; } // -------------------------------------------------------------------- @@ -327,12 +356,12 @@ class CI_Config { /** * System URL * - * @access public + * @deprecated 3.0.0 Encourages insecure practices * @return string */ - function system_url() + public function system_url() { - $x = explode("/", preg_replace("|/*(.+?)/*$|", "\\1", BASEPATH)); + $x = explode('/', preg_replace('|/*(.+?)/*$|', '\\1', BASEPATH)); return $this->slash_item('base_url').end($x).'/'; } @@ -341,42 +370,13 @@ class CI_Config { /** * Set a config file item * - * @access public - * @param string the config item key - * @param string the config item value + * @param string $item Config item key + * @param string $value Config item value * @return void */ - function set_item($item, $value) + public function set_item($item, $value) { $this->config[$item] = $value; } - // -------------------------------------------------------------------- - - /** - * Assign to Config - * - * This function is called by the front controller (CodeIgniter.php) - * after the Config class is instantiated. It permits config items - * to be assigned or overriden by variables contained in the index.php file - * - * @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/src/system/core/Controller.php b/src/system/core/Controller.php index e05362ca..83b3df26 100644 --- a/src/system/core/Controller.php +++ b/src/system/core/Controller.php @@ -1,23 +1,44 @@ -load =& load_class('Loader', 'core'); - $this->load->initialize(); - - log_message('debug', "Controller Class Initialized"); + log_message('info', 'Controller Class Initialized'); } + // -------------------------------------------------------------------- + + /** + * Get the CI singleton + * + * @static + * @return object + */ 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/src/system/core/Exceptions.php b/src/system/core/Exceptions.php index 4ca2fafb..a1c6a197 100644 --- a/src/system/core/Exceptions.php +++ b/src/system/core/Exceptions.php @@ -1,20 +1,41 @@ - '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' - ); - + public $levels = array( + E_ERROR => 'Error', + E_WARNING => 'Warning', + E_PARSE => 'Parsing Error', + E_NOTICE => 'Notice', + E_CORE_ERROR => 'Core Error', + E_CORE_WARNING => 'Core Warning', + E_COMPILE_ERROR => 'Compile Error', + E_COMPILE_WARNING => 'Compile Warning', + E_USER_ERROR => 'User Error', + E_USER_WARNING => 'User Warning', + E_USER_NOTICE => 'User Notice', + E_STRICT => 'Runtime Notice' + ); /** - * Constructor + * Class constructor + * + * @return void */ public function __construct() { $this->ob_level = ob_get_level(); - // Note: Do not log messages from this constructor. + // Note: Do not log messages from this constructor. } // -------------------------------------------------------------------- @@ -76,45 +91,52 @@ class CI_Exceptions { /** * Exception Logger * - * This function logs PHP generated error messages + * 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 + * @param int $severity Log level + * @param string $message Error message + * @param string $filepath File path + * @param int $line Line number + * @return void */ - function log_exception($severity, $message, $filepath, $line) + public 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); + $severity = isset($this->levels[$severity]) ? $this->levels[$severity] : $severity; + log_message('error', 'Severity: '.$severity.' --> '.$message.' '.$filepath.' '.$line); } // -------------------------------------------------------------------- /** - * 404 Page Not Found Handler + * 404 Error Handler * - * @access private - * @param string the page - * @param bool log error yes/no - * @return string + * @uses CI_Exceptions::show_error() + * + * @param string $page Page URI + * @param bool $log_error Whether to log the error + * @return void */ - function show_404($page = '', $log_error = TRUE) + public function show_404($page = '', $log_error = TRUE) { - $heading = "404 Page Not Found"; - $message = "The page you requested was not found."; + if (is_cli()) + { + $heading = 'Not Found'; + $message = 'The controller/method pair you requested was not found.'; + } + else + { + $heading = '404 Page Not Found'; + $message = 'The page you requested was not found.'; + } // By default we log this, but allow a dev to skip it if ($log_error) { - log_message('error', '404 Page Not Found --> '.$page); + log_message('error', $heading.': '.$page); } echo $this->show_error($heading, $message, 'error_404', 404); - exit; + exit(4); // EXIT_UNKNOWN_FILE } // -------------------------------------------------------------------- @@ -122,29 +144,42 @@ class CI_Exceptions { /** * General Error Page * - * This function takes an error message as input - * (either as a string or an array) and displays - * it using the specified template. + * Takes an error message as input (either as a string or an array) + * and displays it using the specified template. * - * @access private - * @param string the heading - * @param string the message - * @param string the template name - * @param int the status code - * @return string + * @param string $heading Page heading + * @param string|string[] $message Error message + * @param string $template Template name + * @param int $status_code (default: 500) + * + * @return string Error page output */ - function show_error($heading, $message, $template = 'error_general', $status_code = 500) + public function show_error($heading, $message, $template = 'error_general', $status_code = 500) { - set_status_header($status_code); + $templates_path = config_item('error_views_path'); + if (empty($templates_path)) + { + $templates_path = VIEWPATH.'errors'.DIRECTORY_SEPARATOR; + } - $message = '

'.implode('

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

'; + if (is_cli()) + { + $message = "\t".(is_array($message) ? implode("\n\t", $message) : $message); + $template = 'cli'.DIRECTORY_SEPARATOR.$template; + } + else + { + set_status_header($status_code); + $message = '

'.(is_array($message) ? implode('

', $message) : $message).'

'; + $template = 'html'.DIRECTORY_SEPARATOR.$template; + } if (ob_get_level() > $this->ob_level + 1) { ob_end_flush(); } ob_start(); - include(APPPATH.'errors/'.$template.'.php'); + include($templates_path.$template.'.php'); $buffer = ob_get_contents(); ob_end_clean(); return $buffer; @@ -152,27 +187,78 @@ class CI_Exceptions { // -------------------------------------------------------------------- + public function show_exception($exception) + { + $templates_path = config_item('error_views_path'); + if (empty($templates_path)) + { + $templates_path = VIEWPATH.'errors'.DIRECTORY_SEPARATOR; + } + + $message = $exception->getMessage(); + if (empty($message)) + { + $message = '(null)'; + } + + if (is_cli()) + { + $templates_path .= 'cli'.DIRECTORY_SEPARATOR; + } + else + { + set_status_header(500); + $templates_path .= 'html'.DIRECTORY_SEPARATOR; + } + + if (ob_get_level() > $this->ob_level + 1) + { + ob_end_flush(); + } + + ob_start(); + include($templates_path.'error_exception.php'); + $buffer = ob_get_contents(); + ob_end_clean(); + echo $buffer; + } + + // -------------------------------------------------------------------- + /** * Native PHP error handler * - * @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 + * @param int $severity Error level + * @param string $message Error message + * @param string $filepath File path + * @param int $line Line number + * @return string Error page output */ - function show_php_error($severity, $message, $filepath, $line) + public 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, '/')) + $templates_path = config_item('error_views_path'); + if (empty($templates_path)) { - $x = explode('/', $filepath); - $filepath = $x[count($x)-2].'/'.end($x); + $templates_path = VIEWPATH.'errors'.DIRECTORY_SEPARATOR; + } + + $severity = isset($this->levels[$severity]) ? $this->levels[$severity] : $severity; + + // For safety reasons we don't show the full file path in non-CLI requests + if ( ! is_cli()) + { + $filepath = str_replace('\\', '/', $filepath); + if (FALSE !== strpos($filepath, '/')) + { + $x = explode('/', $filepath); + $filepath = $x[count($x)-2].'/'.end($x); + } + + $template = 'html'.DIRECTORY_SEPARATOR.'error_php'; + } + else + { + $template = 'cli'.DIRECTORY_SEPARATOR.'error_php'; } if (ob_get_level() > $this->ob_level + 1) @@ -180,15 +266,10 @@ class CI_Exceptions { ob_end_flush(); } ob_start(); - include(APPPATH.'errors/error_php.php'); + include($templates_path.$template.'.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/src/system/core/Hooks.php b/src/system/core/Hooks.php index 4dbabb87..856795cb 100644 --- a/src/system/core/Hooks.php +++ b/src/system/core/Hooks.php @@ -1,23 +1,44 @@ -_initialize(); - log_message('debug', "Hooks Class Initialized"); - } - - // -------------------------------------------------------------------- - - /** - * Initialize the Hooks Preferences - * - * @access private * @return void */ - function _initialize() + public function __construct() { $CFG =& load_class('Config', 'core'); + log_message('info', 'Hooks Class Initialized'); // If hooks are not enabled in the config file // there is nothing else to do - - if ($CFG->item('enable_hooks') == FALSE) + 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')) + if (file_exists(APPPATH.'config/hooks.php')) { include(APPPATH.'config/hooks.php'); } + if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/hooks.php')) + { + include(APPPATH.'config/'.ENVIRONMENT.'/hooks.php'); + } + // If there are no hooks, we're done. if ( ! isset($hook) OR ! is_array($hook)) { return; @@ -105,20 +123,21 @@ class CI_Hooks { /** * Call Hook * - * Calls a particular hook + * Calls a particular hook. Called by CodeIgniter.php. * - * @access private - * @param string the hook name - * @return mixed + * @uses CI_Hooks::_run_hook() + * + * @param string $which Hook name + * @return bool TRUE on success or FALSE on failure */ - function _call_hook($which = '') + public 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])) + if (is_array($this->hooks[$which]) && ! isset($this->hooks[$which]['function'])) { foreach ($this->hooks[$which] as $val) { @@ -140,13 +159,21 @@ class CI_Hooks { * * Runs a particular hook * - * @access private - * @param array the hook details - * @return bool + * @param array $data Hook details + * @return bool TRUE on success or FALSE on failure */ - function _run_hook($data) + protected function _run_hook($data) { - if ( ! is_array($data)) + // Closures/lambda functions and array($object, 'method') callables + if (is_callable($data)) + { + is_array($data) + ? $data[0]->{$data[1]}() + : $data(); + + return TRUE; + } + elseif ( ! is_array($data)) { return FALSE; } @@ -157,8 +184,7 @@ class CI_Hooks { // If the script being called happens to have the same // hook call within it a loop can happen - - if ($this->in_progress == TRUE) + if ($this->_in_progress === TRUE) { return; } @@ -167,7 +193,7 @@ class CI_Hooks { // Set file path // ----------------------------------- - if ( ! isset($data['filepath']) OR ! isset($data['filename'])) + if ( ! isset($data['filepath'], $data['filename'])) { return FALSE; } @@ -179,71 +205,62 @@ class CI_Hooks { return FALSE; } - // ----------------------------------- - // Set class/function name - // ----------------------------------- + // Determine and class and/or function names + $class = empty($data['class']) ? FALSE : $data['class']; + $function = empty($data['function']) ? FALSE : $data['function']; + $params = isset($data['params']) ? $data['params'] : ''; - $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) + if (empty($function)) { return FALSE; } - // ----------------------------------- - // Set the in_progress flag - // ----------------------------------- + // Set the _in_progress flag + $this->_in_progress = TRUE; - $this->in_progress = TRUE; - - // ----------------------------------- // Call the requested class and/or function - // ----------------------------------- - if ($class !== FALSE) { - if ( ! class_exists($class)) + // The object is stored? + if (isset($this->_objects[$class])) { - require($filepath); + if (method_exists($this->_objects[$class], $function)) + { + $this->_objects[$class]->$function($params); + } + else + { + return $this->_in_progress = FALSE; + } } + else + { + class_exists($class, FALSE) OR require_once($filepath); - $HOOK = new $class; - $HOOK->$function($params); + if ( ! class_exists($class, FALSE) OR ! method_exists($class, $function)) + { + return $this->_in_progress = FALSE; + } + + // Store the object and execute the method + $this->_objects[$class] = new $class(); + $this->_objects[$class]->$function($params); + } } else { + function_exists($function) OR require_once($filepath); + if ( ! function_exists($function)) { - require($filepath); + return $this->_in_progress = FALSE; } $function($params); } - $this->in_progress = FALSE; + $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/src/system/core/Input.php b/src/system/core/Input.php index 40305745..a7c9ecd0 100644 --- a/src/system/core/Input.php +++ b/src/system/core/Input.php @@ -1,20 +1,41 @@ -_allow_get_array = (config_item('allow_get_array') === TRUE); + $this->_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); + $this->_standardize_newlines = (bool) config_item('standardize_newlines'); - global $SEC; - $this->security =& $SEC; + $this->security =& load_class('Security', 'core'); // Do we need the UTF-8 class? if (UTF8_ENABLED === TRUE) { - global $UNI; - $this->uni =& $UNI; + $this->uni =& load_class('Utf8', 'core'); } // Sanitize global arrays $this->_sanitize_globals(); + + // CSRF Protection check + if ($this->_enable_csrf === TRUE && ! is_cli()) + { + $this->security->csrf_verify(); + } + + log_message('info', 'Input Class Initialized'); } // -------------------------------------------------------------------- @@ -109,147 +167,204 @@ class CI_Input { /** * Fetch from array * - * This is a helper function to retrieve values from global arrays + * Internal method used to retrieve values from global arrays. * - * @access private - * @param array - * @param string - * @param bool - * @return string + * @param array &$array $_GET, $_POST, $_COOKIE, $_SERVER, etc. + * @param mixed $index Index for item to be fetched from $array + * @param bool $xss_clean Whether to apply XSS filtering + * @return mixed */ - function _fetch_from_array(&$array, $index = '', $xss_clean = FALSE) + protected function _fetch_from_array(&$array, $index = NULL, $xss_clean = NULL) { - if ( ! isset($array[$index])) + is_bool($xss_clean) OR $xss_clean = $this->_enable_xss; + + // If $index is NULL, it means that the whole $array is requested + isset($index) OR $index = array_keys($array); + + // allow fetching multiple keys at once + if (is_array($index)) { - return FALSE; + $output = array(); + foreach ($index as $key) + { + $output[$key] = $this->_fetch_from_array($array, $key, $xss_clean); + } + + return $output; } - if ($xss_clean === TRUE) + if (isset($array[$index])) { - return $this->security->xss_clean($array[$index]); + $value = $array[$index]; + } + elseif (($count = preg_match_all('/(?:^[^\[]+)|\[[^]]*\]/', $index, $matches)) > 1) // Does the index contain array notation + { + $value = $array; + for ($i = 0; $i < $count; $i++) + { + $key = trim($matches[0][$i], '[]'); + if ($key === '') // Empty notation will return the value as array + { + break; + } + + if (isset($value[$key])) + { + $value = $value[$key]; + } + else + { + return NULL; + } + } + } + else + { + return NULL; } - return $array[$index]; + return ($xss_clean === TRUE) + ? $this->security->xss_clean($value) + : $value; } // -------------------------------------------------------------------- /** - * Fetch an item from the GET array - * - * @access public - * @param string - * @param bool - * @return string - */ - function get($index = NULL, $xss_clean = FALSE) + * Fetch an item from the GET array + * + * @param mixed $index Index for item to be fetched from $_GET + * @param bool $xss_clean Whether to apply XSS filtering + * @return mixed + */ + public function get($index = NULL, $xss_clean = NULL) { - // 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) + * Fetch an item from the POST array + * + * @param mixed $index Index for item to be fetched from $_POST + * @param bool $xss_clean Whether to apply XSS filtering + * @return mixed + */ + public function post($index = NULL, $xss_clean = NULL) { - // 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) + * Fetch an item from POST data with fallback to GET + * + * @param string $index Index for item to be fetched from $_POST or $_GET + * @param bool $xss_clean Whether to apply XSS filtering + * @return mixed + */ + public function post_get($index, $xss_clean = NULL) { - if ( ! isset($_POST[$index]) ) - { - return $this->get($index, $xss_clean); - } - else - { - return $this->post($index, $xss_clean); - } + return isset($_POST[$index]) + ? $this->post($index, $xss_clean) + : $this->get($index, $xss_clean); } // -------------------------------------------------------------------- /** - * Fetch an item from the COOKIE array - * - * @access public - * @param string - * @param bool - * @return string - */ - function cookie($index = '', $xss_clean = FALSE) + * Fetch an item from GET data with fallback to POST + * + * @param string $index Index for item to be fetched from $_GET or $_POST + * @param bool $xss_clean Whether to apply XSS filtering + * @return mixed + */ + public function get_post($index, $xss_clean = NULL) + { + return isset($_GET[$index]) + ? $this->get($index, $xss_clean) + : $this->post($index, $xss_clean); + } + + // -------------------------------------------------------------------- + + /** + * Fetch an item from the COOKIE array + * + * @param mixed $index Index for item to be fetched from $_COOKIE + * @param bool $xss_clean Whether to apply XSS filtering + * @return mixed + */ + public function cookie($index = NULL, $xss_clean = NULL) { return $this->_fetch_from_array($_COOKIE, $index, $xss_clean); } + // -------------------------------------------------------------------- + + /** + * Fetch an item from the SERVER array + * + * @param mixed $index Index for item to be fetched from $_SERVER + * @param bool $xss_clean Whether to apply XSS filtering + * @return mixed + */ + public function server($index, $xss_clean = NULL) + { + return $this->_fetch_from_array($_SERVER, $index, $xss_clean); + } + // ------------------------------------------------------------------------ /** - * 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) + * Fetch an item from the php://input stream + * + * Useful when you need to access PUT, DELETE or PATCH request data. + * + * @param string $index Index for item to be fetched + * @param bool $xss_clean Whether to apply XSS filtering + * @return mixed + */ + public function input_stream($index = NULL, $xss_clean = NULL) + { + // Prior to PHP 5.6, the input stream can only be read once, + // so we'll need to check if we have already done that first. + if ( ! is_array($this->_input_stream)) + { + // $this->raw_input_stream will trigger __get(). + parse_str($this->raw_input_stream, $this->_input_stream); + is_array($this->_input_stream) OR $this->_input_stream = array(); + } + + return $this->_fetch_from_array($this->_input_stream, $index, $xss_clean); + } + + // ------------------------------------------------------------------------ + + /** + * Set cookie + * + * Accepts an arbitrary number of parameters (up to 7) or an associative + * array in the first parameter containing all the values. + * + * @param string|mixed[] $name Cookie name or an array containing parameters + * @param string $value Cookie value + * @param int $expire Cookie expiration time in seconds + * @param string $domain Cookie domain (e.g.: '.yourdomain.com') + * @param string $path Cookie path (default: '/') + * @param string $prefix Cookie name prefix + * @param bool $secure Whether to only transfer cookies via SSL + * @param bool $httponly Whether to only makes the cookie accessible via HTTP (no javascript) + * @return void + */ + public function set_cookie($name, $value = '', $expire = '', $domain = '', $path = '/', $prefix = '', $secure = FALSE, $httponly = 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) + foreach (array('value', 'expire', 'domain', 'path', 'prefix', 'secure', 'httponly', 'name') as $item) { if (isset($name[$item])) { @@ -258,23 +373,31 @@ class CI_Input { } } - if ($prefix == '' AND config_item('cookie_prefix') != '') + if ($prefix === '' && config_item('cookie_prefix') !== '') { $prefix = config_item('cookie_prefix'); } - if ($domain == '' AND config_item('cookie_domain') != '') + + if ($domain == '' && config_item('cookie_domain') != '') { $domain = config_item('cookie_domain'); } - if ($path == '/' AND config_item('cookie_path') != '/') + + if ($path === '/' && config_item('cookie_path') !== '/') { $path = config_item('cookie_path'); } - if ($secure == FALSE AND config_item('cookie_secure') != FALSE) + + if ($secure === FALSE && config_item('cookie_secure') === TRUE) { $secure = config_item('cookie_secure'); } + if ($httponly === FALSE && config_item('cookie_httponly') !== FALSE) + { + $httponly = config_item('cookie_httponly'); + } + if ( ! is_numeric($expire)) { $expire = time() - 86500; @@ -284,31 +407,18 @@ class CI_Input { $expire = ($expire > 0) ? time() + $expire : 0; } - setcookie($prefix.$name, $value, $expire, $path, $domain, $secure); + setcookie($prefix.$name, $value, $expire, $path, $domain, $secure, $httponly); } // -------------------------------------------------------------------- /** - * 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 - */ + * Fetch the IP Address + * + * Determines and validates the visitor's IP address. + * + * @return string IP address + */ public function ip_address() { if ($this->ip_address !== FALSE) @@ -317,25 +427,27 @@ class CI_Input { } $proxy_ips = config_item('proxy_ips'); - if ( ! empty($proxy_ips)) + if ( ! empty($proxy_ips) && ! is_array($proxy_ips)) { $proxy_ips = explode(',', str_replace(' ', '', $proxy_ips)); + } + + $this->ip_address = $this->server('REMOTE_ADDR'); + + if ($proxy_ips) + { foreach (array('HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_X_CLIENT_IP', 'HTTP_X_CLUSTER_CLIENT_IP') as $header) { - if (($spoof = $this->server($header)) !== FALSE) + if (($spoof = $this->server($header)) !== NULL) { // Some proxies typically list the whole chain of IP // addresses through which the client has reached us. // e.g. client_ip, proxy_ip1, proxy_ip2, etc. - if (strpos($spoof, ',') !== FALSE) - { - $spoof = explode(',', $spoof, 2); - $spoof = $spoof[0]; - } + sscanf($spoof, '%[^,]', $spoof); if ( ! $this->valid_ip($spoof)) { - $spoof = FALSE; + $spoof = NULL; } else { @@ -344,17 +456,92 @@ class CI_Input { } } - $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 ($spoof) + { + for ($i = 0, $c = count($proxy_ips); $i < $c; $i++) + { + // Check if we have an IP address or a subnet + if (strpos($proxy_ips[$i], '/') === FALSE) + { + // An IP address (and not a subnet) is specified. + // We can compare right away. + if ($proxy_ips[$i] === $this->ip_address) + { + $this->ip_address = $spoof; + break; + } + + continue; + } + + // We have a subnet ... now the heavy lifting begins + isset($separator) OR $separator = $this->valid_ip($this->ip_address, 'ipv6') ? ':' : '.'; + + // If the proxy entry doesn't match the IP protocol - skip it + if (strpos($proxy_ips[$i], $separator) === FALSE) + { + continue; + } + + // Convert the REMOTE_ADDR IP address to binary, if needed + if ( ! isset($ip, $sprintf)) + { + if ($separator === ':') + { + // Make sure we're have the "full" IPv6 format + $ip = explode(':', + str_replace('::', + str_repeat(':', 9 - substr_count($this->ip_address, ':')), + $this->ip_address + ) + ); + + for ($j = 0; $j < 8; $j++) + { + $ip[$j] = intval($ip[$j], 16); + } + + $sprintf = '%016b%016b%016b%016b%016b%016b%016b%016b'; + } + else + { + $ip = explode('.', $this->ip_address); + $sprintf = '%08b%08b%08b%08b'; + } + + $ip = vsprintf($sprintf, $ip); + } + + // Split the netmask length off the network address + sscanf($proxy_ips[$i], '%[^/]/%d', $netaddr, $masklen); + + // Again, an IPv6 address is most likely in a compressed form + if ($separator === ':') + { + $netaddr = explode(':', str_replace('::', str_repeat(':', 9 - substr_count($netaddr, ':')), $netaddr)); + for ($i = 0; $i < 8; $i++) + { + $netaddr[$i] = intval($netaddr[$i], 16); + } + } + else + { + $netaddr = explode('.', $netaddr); + } + + // Convert to binary and finally compare + if (strncmp($ip, vsprintf($sprintf, $netaddr), $masklen) === 0) + { + $this->ip_address = $spoof; + break; + } + } + } } if ( ! $this->valid_ip($this->ip_address)) { - $this->ip_address = '0.0.0.0'; + return $this->ip_address = '0.0.0.0'; } return $this->ip_address; @@ -363,256 +550,72 @@ class CI_Input { // -------------------------------------------------------------------- /** - * Validate IP Address - * - * @access public - * @param string - * @param string ipv4 or ipv6 - * @return bool - */ + * Validate IP Address + * + * @param string $ip IP address + * @param string $which IP protocol: '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 (strtolower($which)) { - 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); + case 'ipv4': + $which = FILTER_FLAG_IPV4; + break; + case 'ipv6': + $which = FILTER_FLAG_IPV6; + break; + default: + $which = NULL; + break; } - 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); + return (bool) filter_var($ip, FILTER_VALIDATE_IP, $which); } // -------------------------------------------------------------------- /** - * Validate IPv4 Address - * - * Updated version suggested by Geert De Deckere - * - * @access protected - * @param string - * @return bool - */ - protected function _valid_ipv4($ip) + * Fetch User Agent string + * + * @return string|null User Agent string or NULL if it doesn't exist + */ + public function user_agent($xss_clean = NULL) { - $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; + return $this->_fetch_from_array($_SERVER, 'HTTP_USER_AGENT', $xss_clean); } // -------------------------------------------------------------------- /** - * Validate IPv6 Address - * - * @access protected - * @param string - * @return bool - */ - protected function _valid_ipv6($str) + * Sanitize Globals + * + * Internal method serving for the following purposes: + * + * - Unsets $_GET data, if query strings are not enabled + * - Cleans POST, COOKIE and SERVER data + * - Standardizes newline characters to PHP_EOL + * + * @return void + */ + protected function _sanitize_globals() { - // 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) + if ($this->_allow_get_array === FALSE) { $_GET = array(); } - else + elseif (is_array($_GET)) { - if (is_array($_GET) AND count($_GET) > 0) + foreach ($_GET as $key => $val) { - foreach ($_GET as $key => $val) - { - $_GET[$this->_clean_input_keys($key)] = $this->_clean_input_data($val); - } + $_GET[$this->_clean_input_keys($key)] = $this->_clean_input_data($val); } } // Clean $_POST Data - if (is_array($_POST) AND count($_POST) > 0) + if (is_array($_POST)) { foreach ($_POST as $key => $val) { @@ -621,72 +624,57 @@ class CI_Input { } // Clean $_COOKIE Data - if (is_array($_COOKIE) AND count($_COOKIE) > 0) + if (is_array($_COOKIE)) { // Also get rid of specially treated cookies that might be set by a server // or silly application, that are of no use to a CI application anyway // but that when present will trip our 'Disallowed Key Characters' alarm // http://www.ietf.org/rfc/rfc2109.txt // note that the key names below are single quoted strings, and are not PHP variables - unset($_COOKIE['$Version']); - unset($_COOKIE['$Path']); - unset($_COOKIE['$Domain']); - - // Work-around for PHP bug #66827 (https://bugs.php.net/bug.php?id=66827) - // - // The session ID sanitizer doesn't check for the value type and blindly does - // an implicit cast to string, which triggers an 'Array to string' E_NOTICE. - $sess_cookie_name = config_item('cookie_prefix').config_item('sess_cookie_name'); - if (isset($_COOKIE[$sess_cookie_name]) && ! is_string($_COOKIE[$sess_cookie_name])) - { - unset($_COOKIE[$sess_cookie_name]); - } + unset( + $_COOKIE['$Version'], + $_COOKIE['$Path'], + $_COOKIE['$Domain'] + ); foreach ($_COOKIE as $key => $val) { - // _clean_input_data() has been reported to break encrypted cookies - if ($key === $sess_cookie_name && config_item('sess_encrypt_cookie')) + if (($cookie_key = $this->_clean_input_keys($key)) !== FALSE) { - continue; + $_COOKIE[$cookie_key] = $this->_clean_input_data($val); + } + else + { + unset($_COOKIE[$key]); } - - $_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"); + log_message('debug', 'Global POST, GET 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) + * Clean Input Data + * + * Internal method that aids in escaping data and + * standardizing newline characters to PHP_EOL. + * + * @param string|string[] $str Input string(s) + * @return string + */ + protected function _clean_input_data($str) { if (is_array($str)) { $new_array = array(); - foreach ($str as $key => $val) + foreach (array_keys($str) as $key) { - $new_array[$this->_clean_input_keys($key)] = $this->_clean_input_data($val); + $new_array[$this->_clean_input_keys($key)] = $this->_clean_input_data($str[$key]); } return $new_array; } @@ -694,7 +682,7 @@ class CI_Input { /* 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. + it will probably not exist in future versions at all. */ if ( ! is_php('5.4') && get_magic_quotes_gpc()) { @@ -708,21 +696,12 @@ class CI_Input { } // 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); - } + $str = remove_invisible_characters($str, FALSE); // Standardize newlines if needed - if ($this->_standardize_newlines == TRUE) + if ($this->_standardize_newlines === TRUE) { - if (strpos($str, "\r") !== FALSE) - { - $str = str_replace(array("\r\n", "\r", "\r\n\n"), PHP_EOL, $str); - } + return preg_replace('/(?:\r\n|[\r\n])/', PHP_EOL, $str); } return $str; @@ -731,27 +710,38 @@ class CI_Input { // -------------------------------------------------------------------- /** - * 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) + * Clean Keys + * + * Internal method that helps to prevent malicious users + * from trying to exploit keys we make sure that keys are + * only named with alpha-numeric text and a few other items. + * + * @param string $str Input string + * @param bool $fatal Whether to terminate script exection + * or to return FALSE if an invalid + * key is encountered + * @return string|bool + */ + protected function _clean_input_keys($str, $fatal = TRUE) { - if ( ! preg_match("/^[a-z0-9:_\/-]+$/i", $str)) + if ( ! preg_match('/^[a-z0-9:_\/|-]+$/i', $str)) { - exit('Disallowed Key Characters.'); + if ($fatal === TRUE) + { + return FALSE; + } + else + { + set_status_header(503); + echo 'Disallowed Key Characters.'; + exit(7); // EXIT_USER_INPUT + } } // Clean UTF-8 if supported if (UTF8_ENABLED === TRUE) { - $str = $this->uni->clean_string($str); + return $this->uni->clean_string($str); } return $str; @@ -762,42 +752,37 @@ class CI_Input { /** * 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 + * @param bool $xss_clean Whether to apply XSS filtering + * @return array */ public function request_headers($xss_clean = FALSE) { - // Look at Apache go! + // If header is already defined, return it immediately + if ( ! empty($this->headers)) + { + return $this->headers; + } + + // In Apache, you can simply call apache_request_headers() if (function_exists('apache_request_headers')) { - $headers = apache_request_headers(); + return $this->headers = apache_request_headers(); } - else - { - $headers['Content-Type'] = (isset($_SERVER['CONTENT_TYPE'])) ? $_SERVER['CONTENT_TYPE'] : @getenv('CONTENT_TYPE'); - foreach ($_SERVER as $key => $val) + $this->headers['Content-Type'] = isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : @getenv('CONTENT_TYPE'); + + foreach ($_SERVER as $key => $val) + { + if (sscanf($key, 'HTTP_%s', $header) === 1) { - 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 + $header = str_replace('_', ' ', strtolower($header)); + $header = str_replace(' ', '-', ucwords($header)); + + $this->headers[$header] = $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; } @@ -808,59 +793,103 @@ class CI_Input { * * 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 + * @param string $index Header name + * @param bool $xss_clean Whether to apply XSS filtering + * @return string|null The requested header on success or NULL on failure */ public function get_request_header($index, $xss_clean = FALSE) { - if (empty($this->headers)) + static $headers; + + if ( ! isset($headers)) { - $this->request_headers(); + empty($this->headers) && $this->request_headers(); + foreach ($this->headers as $key => $value) + { + $headers[strtolower($key)] = $value; + } } - if ( ! isset($this->headers[$index])) + $index = strtolower($index); + + if ( ! isset($headers[$index])) { - return FALSE; + return NULL; } - if ($xss_clean === TRUE) - { - return $this->security->xss_clean($this->headers[$index]); - } - - return $this->headers[$index]; + return ($xss_clean === TRUE) + ? $this->security->xss_clean($headers[$index]) + : $headers[$index]; } // -------------------------------------------------------------------- /** - * Is ajax Request? + * 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 + * Test to see if a request contains the HTTP_X_REQUESTED_WITH header. * * @return bool */ + public function is_ajax_request() + { + return ( ! empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'); + } + + // -------------------------------------------------------------------- + + /** + * Is CLI request? + * + * Test to see if a request was made from the command line. + * + * @deprecated 3.0.0 Use is_cli() instead + * @return bool + */ public function is_cli_request() { - return (php_sapi_name() === 'cli' OR defined('STDIN')); + return is_cli(); + } + + // -------------------------------------------------------------------- + + /** + * Get Request Method + * + * Return the request method + * + * @param bool $upper Whether to return in upper or lower case + * (default: FALSE) + * @return string + */ + public function method($upper = FALSE) + { + return ($upper) + ? strtoupper($this->server('REQUEST_METHOD')) + : strtolower($this->server('REQUEST_METHOD')); + } + + // ------------------------------------------------------------------------ + + /** + * Magic __get() + * + * Allows read access to protected properties + * + * @param string $name + * @return mixed + */ + public function __get($name) + { + if ($name === 'raw_input_stream') + { + isset($this->_raw_input_stream) OR $this->_raw_input_stream = file_get_contents('php://input'); + return $this->_raw_input_stream; + } + elseif ($name === 'ip_address') + { + return $this->ip_address; + } } } - -/* End of file Input.php */ -/* Location: ./system/core/Input.php */ \ No newline at end of file diff --git a/src/system/core/Lang.php b/src/system/core/Lang.php index 28497034..1fcff078 100644 --- a/src/system/core/Lang.php +++ b/src/system/core/Lang.php @@ -1,20 +1,41 @@ -load($value, $idiom, $return, $add_suffix, $alt_path); + } + + return; + } + $langfile = str_replace('.php', '', $langfile); - if ($add_suffix == TRUE) + if ($add_suffix === TRUE) { - $langfile = str_replace('_lang.', '', $langfile).'_lang'; + $langfile = preg_replace('/_lang$/', '', $langfile).'_lang'; } $langfile .= '.php'; - if (in_array($langfile, $this->is_loaded, TRUE)) + if (empty($idiom) OR ! preg_match('/^[a-z_-]+$/i', $idiom)) + { + $config =& get_config(); + $idiom = empty($config['language']) ? 'english' : $config['language']; + } + + if ($return === FALSE && isset($this->is_loaded[$langfile]) && $this->is_loaded[$langfile] === $idiom) { return; } - $config =& get_config(); - - if ($idiom == '') + // Load the base file, so any others found can override it + $basepath = BASEPATH.'language/'.$idiom.'/'.$langfile; + if (($found = file_exists($basepath)) === TRUE) { - $deft_lang = ( ! isset($config['language'])) ? 'english' : $config['language']; - $idiom = ($deft_lang == '') ? 'english' : $deft_lang; + include($basepath); } - // Determine where the language file is and load it - if ($alt_path != '' && file_exists($alt_path.'language/'.$idiom.'/'.$langfile)) + // Do we have an alternative path to look in? + if ($alt_path !== '') { - include($alt_path.'language/'.$idiom.'/'.$langfile); + $alt_path .= 'language/'.$idiom.'/'.$langfile; + if (file_exists($alt_path)) + { + include($alt_path); + $found = TRUE; + } } else { - $found = FALSE; - foreach (get_instance()->load->get_package_paths(TRUE) as $package_path) { - if (file_exists($package_path.'language/'.$idiom.'/'.$langfile)) + $package_path .= 'language/'.$idiom.'/'.$langfile; + if ($basepath !== $package_path && file_exists($package_path)) { - include($package_path.'language/'.$idiom.'/'.$langfile); + include($package_path); $found = TRUE; break; } } - - if ($found !== TRUE) - { - show_error('Unable to load the requested language file: language/'.$idiom.'/'.$langfile); - } } + if ($found !== TRUE) + { + show_error('Unable to load the requested language file: language/'.$idiom.'/'.$langfile); + } - if ( ! isset($lang)) + if ( ! isset($lang) OR ! is_array($lang)) { log_message('error', 'Language file contains no data: language/'.$idiom.'/'.$langfile); + + if ($return === TRUE) + { + return array(); + } return; } - if ($return == TRUE) + if ($return === TRUE) { return $lang; } - $this->is_loaded[] = $langfile; + $this->is_loaded[$langfile] = $idiom; $this->language = array_merge($this->language, $lang); - unset($lang); - log_message('debug', 'Language file loaded: language/'.$idiom.'/'.$langfile); + log_message('info', 'Language file loaded: language/'.$idiom.'/'.$langfile); return TRUE; } // -------------------------------------------------------------------- /** - * Fetch a single line of text from the language array + * Language line * - * @access public - * @param string $line the language line - * @return string + * Fetches a single line of text from the language array + * + * @param string $line Language line key + * @param bool $log_errors Whether to log an error message if the line is not found + * @return string Translation */ - function line($line = '') + public function line($line, $log_errors = TRUE) { - $value = ($line == '' OR ! isset($this->language[$line])) ? FALSE : $this->language[$line]; + $value = isset($this->language[$line]) ? $this->language[$line] : FALSE; // Because killer robots like unicorns! - if ($value === FALSE) + if ($value === FALSE && $log_errors === TRUE) { log_message('error', 'Could not find the language line "'.$line.'"'); } @@ -155,7 +201,3 @@ class CI_Lang { } } -// END Language Class - -/* End of file Lang.php */ -/* Location: ./system/core/Lang.php */ diff --git a/src/system/core/Loader.php b/src/system/core/Loader.php index 40090db6..c742ae71 100644 --- a/src/system/core/Loader.php +++ b/src/system/core/Loader.php @@ -1,31 +1,52 @@ - TRUE); + /** * List of paths to load libraries from * - * @var array - * @access protected + * @var array */ - protected $_ci_library_paths = array(); + protected $_ci_library_paths = array(APPPATH, BASEPATH); + /** * List of paths to load models from * - * @var array - * @access protected + * @var array */ - protected $_ci_model_paths = array(); + protected $_ci_model_paths = array(APPPATH); + /** * List of paths to load helpers from * - * @var array - * @access protected + * @var array */ - protected $_ci_helper_paths = array(); - /** - * List of loaded base classes - * Set by the controller class - * - * @var array - * @access protected - */ - protected $_base_classes = array(); // Set by the controller class + protected $_ci_helper_paths = array(APPPATH, BASEPATH); + /** * List of cached variables * - * @var array - * @access protected + * @var array */ - protected $_ci_cached_vars = array(); + protected $_ci_cached_vars = array(); + /** * List of loaded classes * - * @var array - * @access protected + * @var array */ - protected $_ci_classes = array(); - /** - * List of loaded files - * - * @var array - * @access protected - */ - protected $_ci_loaded_files = array(); + protected $_ci_classes = array(); + /** * List of loaded models * - * @var array - * @access protected + * @var array */ - protected $_ci_models = array(); + protected $_ci_models = array(); + /** * List of loaded helpers * - * @var array - * @access protected + * @var array */ - protected $_ci_helpers = array(); + protected $_ci_helpers = array(); + /** * List of class name mappings * - * @var array - * @access protected + * @var array */ - protected $_ci_varmap = array('unit_test' => 'unit', - 'user_agent' => 'agent'); + protected $_ci_varmap = array( + 'unit_test' => 'unit', + 'user_agent' => 'agent' + ); + + // -------------------------------------------------------------------- /** - * Constructor + * Class constructor * - * Sets the path to the view files and gets the initial output buffering level + * Sets component load paths, gets the initial output buffering level. + * + * @return void */ public function __construct() { - $this->_ci_ob_level = ob_get_level(); - $this->_ci_library_paths = array(APPPATH, BASEPATH); - $this->_ci_helper_paths = array(APPPATH, BASEPATH); - $this->_ci_model_paths = array(APPPATH); - $this->_ci_view_paths = array(APPPATH.'views/' => TRUE); + $this->_ci_ob_level = ob_get_level(); + $this->_ci_classes =& is_loaded(); - log_message('debug', "Loader Class Initialized"); + log_message('info', 'Loader Class Initialized'); } // -------------------------------------------------------------------- /** - * Initialize the Loader + * Initializer * - * This method is called once in CI_Controller. - * - * @param array - * @return object + * @todo Figure out a way to move this to the constructor + * without breaking *package_path*() methods. + * @uses CI_Loader::_ci_autoloader() + * @used-by CI_Controller::__construct() + * @return void */ public function initialize() { - $this->_ci_classes = array(); - $this->_ci_loaded_files = array(); - $this->_ci_models = array(); - $this->_base_classes =& is_loaded(); - $this->_ci_autoloader(); - - return $this; } // -------------------------------------------------------------------- @@ -160,61 +162,61 @@ class CI_Loader { /** * Is Loaded * - * A utility function to test if a class is in the self::$_ci_classes array. - * This function returns the object name if the class tested for is loaded, - * and returns FALSE if it isn't. + * A utility method to test if a class is in the self::$_ci_classes array. * - * It is mainly used in the form_helper -> _get_validation_object() + * @used-by Mainly used by Form Helper function _get_validation_object(). * - * @param string class being checked for - * @return mixed class object name on the CI SuperObject or FALSE + * @param string $class Class name to check for + * @return string|bool Class object name if loaded or FALSE */ public function is_loaded($class) { - if (isset($this->_ci_classes[$class])) - { - return $this->_ci_classes[$class]; - } - - return FALSE; + return array_search(ucfirst($class), $this->_ci_classes, TRUE); } // -------------------------------------------------------------------- /** - * Class Loader + * Library Loader * - * This function lets users load and instantiate classes. - * It is designed to be called from a user's app controllers. + * Loads and instantiates libraries. + * Designed to be called from application controllers. * - * @param string the name of the class - * @param mixed the optional parameters - * @param string an optional object name - * @return void + * @param string $library Library name + * @param array $params Optional parameters to pass to the library class constructor + * @param string $object_name An optional object name to assign to + * @return object */ - public function library($library = '', $params = NULL, $object_name = NULL) + public function library($library, $params = NULL, $object_name = NULL) { - if (is_array($library)) + if (empty($library)) { - foreach ($library as $class) + return $this; + } + elseif (is_array($library)) + { + foreach ($library as $key => $value) { - $this->library($class, $params); + if (is_int($key)) + { + $this->library($value, $params); + } + else + { + $this->library($key, $params, $value); + } } - return; + return $this; } - if ($library == '' OR isset($this->_base_classes[$library])) - { - return FALSE; - } - - if ( ! is_null($params) && ! is_array($params)) + if ($params !== NULL && ! is_array($params)) { $params = NULL; } - $this->_ci_load_class($library, $params, $object_name); + $this->_ci_load_library($library, $params, $object_name); + return $this; } // -------------------------------------------------------------------- @@ -222,27 +224,27 @@ class CI_Loader { /** * Model Loader * - * This function lets users load and instantiate models. + * Loads and instantiates models. * - * @param string the name of the class - * @param string name for the model - * @param bool database connection - * @return void + * @param string $model Model name + * @param string $name An optional object name to assign to + * @param bool $db_conn An optional database connection configuration to initialize + * @return object */ public function model($model, $name = '', $db_conn = FALSE) { - if (is_array($model)) + if (empty($model)) { - foreach ($model as $babe) - { - $this->model($babe); - } - return; + return $this; } - - if ($model == '') + elseif (is_array($model)) { - return; + foreach ($model as $key => $value) + { + is_int($key) ? $this->model($value, '', $db_conn) : $this->model($key, $value, $db_conn); + } + + return $this; } $path = ''; @@ -251,64 +253,105 @@ class CI_Loader { if (($last_slash = strrpos($model, '/')) !== FALSE) { // The path is in front of the last slash - $path = substr($model, 0, $last_slash + 1); + $path = substr($model, 0, ++$last_slash); // And the model name behind it - $model = substr($model, $last_slash + 1); + $model = substr($model, $last_slash); } - if ($name == '') + if (empty($name)) { $name = $model; } if (in_array($name, $this->_ci_models, TRUE)) { - return; + return $this; } $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); + throw new RuntimeException('The model name you are loading is the name of a resource that is already being used: '.$name); } - $model = strtolower($model); - - foreach ($this->_ci_model_paths as $mod_path) + if ($db_conn !== FALSE && ! class_exists('CI_DB', FALSE)) { - if ( ! file_exists($mod_path.'models/'.$path.$model.'.php')) + if ($db_conn === TRUE) { - continue; + $db_conn = ''; } - if ($db_conn !== FALSE AND ! class_exists('CI_DB')) + $this->database($db_conn, FALSE, TRUE); + } + + // Note: All of the code under this condition used to be just: + // + // load_class('Model', 'core'); + // + // However, load_class() instantiates classes + // to cache them for later use and that prevents + // MY_Model from being an abstract class and is + // sub-optimal otherwise anyway. + if ( ! class_exists('CI_Model', FALSE)) + { + $app_path = APPPATH.'core'.DIRECTORY_SEPARATOR; + if (file_exists($app_path.'Model.php')) { - if ($db_conn === TRUE) + require_once($app_path.'Model.php'); + if ( ! class_exists('CI_Model', FALSE)) { - $db_conn = ''; + throw new RuntimeException($app_path."Model.php exists, but doesn't declare class CI_Model"); + } + } + elseif ( ! class_exists('CI_Model', FALSE)) + { + require_once(BASEPATH.'core'.DIRECTORY_SEPARATOR.'Model.php'); + } + + $class = config_item('subclass_prefix').'Model'; + if (file_exists($app_path.$class.'.php')) + { + require_once($app_path.$class.'.php'); + if ( ! class_exists($class, FALSE)) + { + throw new RuntimeException($app_path.$class.".php exists, but doesn't declare class ".$class); + } + } + } + + $model = ucfirst($model); + if ( ! class_exists($model, FALSE)) + { + foreach ($this->_ci_model_paths as $mod_path) + { + if ( ! file_exists($mod_path.'models/'.$path.$model.'.php')) + { + continue; } - $CI->load->database($db_conn, FALSE, TRUE); + require_once($mod_path.'models/'.$path.$model.'.php'); + if ( ! class_exists($model, FALSE)) + { + throw new RuntimeException($mod_path."models/".$path.$model.".php exists, but doesn't declare class ".$model); + } + + break; } - if ( ! class_exists('CI_Model')) + if ( ! class_exists($model, FALSE)) { - load_class('Model', 'core'); + throw new RuntimeException('Unable to locate the model you have specified: '.$model); } - - require_once($mod_path.'models/'.$path.$model.'.php'); - - $model = ucfirst($model); - - $CI->$name = new $model(); - - $this->_ci_models[] = $name; - return; + } + elseif ( ! is_subclass_of($model, 'CI_Model')) + { + throw new RuntimeException("Class ".$model." already exists and doesn't extend CI_Model"); } - // couldn't find the model - show_error('Unable to locate the model you have specified: '.$model); + $this->_ci_models[] = $name; + $CI->$name = new $model(); + return $this; } // -------------------------------------------------------------------- @@ -316,18 +359,21 @@ class CI_Loader { /** * 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 + * @param mixed $params Database configuration options + * @param bool $return Whether to return the database object + * @param bool $query_builder Whether to enable Query Builder + * (overrides the configuration setting) + * + * @return object|bool Database object if $return is set to TRUE, + * FALSE on failure, CI_Loader instance in any other case */ - public function database($params = '', $return = FALSE, $active_record = NULL) + public function database($params = '', $return = FALSE, $query_builder = NULL) { // Grab the super object $CI =& get_instance(); // Do we even need to load the database class? - if (class_exists('CI_DB') AND $return == FALSE AND $active_record == NULL AND isset($CI->db) AND is_object($CI->db)) + if ($return === FALSE && $query_builder === NULL && isset($CI->db) && is_object($CI->db) && ! empty($CI->db->conn_id)) { return FALSE; } @@ -336,42 +382,48 @@ class CI_Loader { if ($return === TRUE) { - return DB($params, $active_record); + return DB($params, $query_builder); } - // Initialize the db variable. Needed to prevent + // Initialize the db variable. Needed to prevent // reference errors with some configurations $CI->db = ''; // Load the DB class - $CI->db =& DB($params, $active_record); + $CI->db =& DB($params, $query_builder); + return $this; } // -------------------------------------------------------------------- /** - * Load the Utilities Class + * Load the Database Utilities Class * - * @return string + * @param object $db Database object + * @param bool $return Whether to return the DB Utilities class object or not + * @return object */ - public function dbutil() + public function dbutil($db = NULL, $return = FALSE) { - if ( ! class_exists('CI_DB')) - { - $this->database(); - } - $CI =& get_instance(); - // for backwards compatibility, load dbforge so we can extend dbutils off it - // this use is deprecated and strongly discouraged - $CI->load->dbforge(); + if ( ! is_object($db) OR ! ($db instanceof CI_DB)) + { + class_exists('CI_DB', FALSE) OR $this->database(); + $db =& $CI->db; + } require_once(BASEPATH.'database/DB_utility.php'); - require_once(BASEPATH.'database/drivers/'.$CI->db->dbdriver.'/'.$CI->db->dbdriver.'_utility.php'); - $class = 'CI_DB_'.$CI->db->dbdriver.'_utility'; + require_once(BASEPATH.'database/drivers/'.$db->dbdriver.'/'.$db->dbdriver.'_utility.php'); + $class = 'CI_DB_'.$db->dbdriver.'_utility'; - $CI->dbutil = new $class(); + if ($return === TRUE) + { + return new $class($db); + } + + $CI->dbutil = new $class($db); + return $this; } // -------------------------------------------------------------------- @@ -379,41 +431,58 @@ class CI_Loader { /** * Load the Database Forge Class * - * @return string + * @param object $db Database object + * @param bool $return Whether to return the DB Forge class object or not + * @return object */ - public function dbforge() + public function dbforge($db = NULL, $return = FALSE) { - if ( ! class_exists('CI_DB')) + $CI =& get_instance(); + if ( ! is_object($db) OR ! ($db instanceof CI_DB)) { - $this->database(); + class_exists('CI_DB', FALSE) OR $this->database(); + $db =& $CI->db; } - $CI =& get_instance(); - require_once(BASEPATH.'database/DB_forge.php'); - require_once(BASEPATH.'database/drivers/'.$CI->db->dbdriver.'/'.$CI->db->dbdriver.'_forge.php'); - $class = 'CI_DB_'.$CI->db->dbdriver.'_forge'; + require_once(BASEPATH.'database/drivers/'.$db->dbdriver.'/'.$db->dbdriver.'_forge.php'); - $CI->dbforge = new $class(); + if ( ! empty($db->subdriver)) + { + $driver_path = BASEPATH.'database/drivers/'.$db->dbdriver.'/subdrivers/'.$db->dbdriver.'_'.$db->subdriver.'_forge.php'; + if (file_exists($driver_path)) + { + require_once($driver_path); + $class = 'CI_DB_'.$db->dbdriver.'_'.$db->subdriver.'_forge'; + } + } + else + { + $class = 'CI_DB_'.$db->dbdriver.'_forge'; + } + + if ($return === TRUE) + { + return new $class($db); + } + + $CI->dbforge = new $class($db); + return $this; } // -------------------------------------------------------------------- /** - * Load View + * View Loader * - * This function is used to load a "view" file. It has three parameters: + * Loads "view" files. * - * 1. The name of the "view" file to be included. - * 2. An associative array of data to be extracted for use in the view. - * 3. TRUE/FALSE - whether to return the data or load it. In - * some cases it's advantageous to be able to return data so that - * a developer can process it in some way. - * - * @param string - * @param array - * @param bool - * @return void + * @param string $view View name + * @param array $vars An associative array of data + * to be extracted for use in the view + * @param bool $return Whether to return the view output + * or leave it to the Output class + * @return object|string */ public function view($view, $vars = array(), $return = FALSE) { @@ -423,13 +492,11 @@ class CI_Loader { // -------------------------------------------------------------------- /** - * Load File + * Generic File Loader * - * This is a generic file loader - * - * @param string - * @param bool - * @return string + * @param string $path File path + * @param bool $return Whether to return the file output + * @return object|string */ public function file($path, $return = FALSE) { @@ -444,26 +511,45 @@ class CI_Loader { * Once variables are set they become available within * the controller class and its "view" files. * - * @param array - * @param string - * @return void + * @param array|object|string $vars + * An associative array or object containing values + * to be set, or a value's name if string + * @param string $val Value to set, only used if $vars is a string + * @return object */ - public function vars($vars = array(), $val = '') + public function vars($vars, $val = '') { - if ($val != '' AND is_string($vars)) + if (is_string($vars)) { $vars = array($vars => $val); } $vars = $this->_ci_object_to_array($vars); - if (is_array($vars) AND count($vars) > 0) + if (is_array($vars) && count($vars) > 0) { foreach ($vars as $key => $val) { $this->_ci_cached_vars[$key] = $val; } } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Clear Cached Variables + * + * Clears the cached variables. + * + * @return CI_Loader + */ + public function clear_vars() + { + $this->_ci_cached_vars = array(); + return $this; } // -------------------------------------------------------------------- @@ -473,8 +559,8 @@ class CI_Loader { * * Check if a variable is set and retrieve it. * - * @param array - * @return void + * @param string $key Variable name + * @return mixed The variable or NULL if not found */ public function get_var($key) { @@ -484,12 +570,24 @@ class CI_Loader { // -------------------------------------------------------------------- /** - * Load Helper + * Get Variables * - * This function loads the specified helper file. + * Retrieves all loaded variables. * - * @param mixed - * @return void + * @return array + */ + public function get_vars() + { + return $this->_ci_cached_vars; + } + + // -------------------------------------------------------------------- + + /** + * Helper Loader + * + * @param string|string[] $helpers Helper name(s) + * @return object */ public function helper($helpers = array()) { @@ -500,27 +598,34 @@ class CI_Loader { continue; } - $ext_helper = APPPATH.'helpers/'.config_item('subclass_prefix').$helper.'.php'; - // Is this a helper extension request? - if (file_exists($ext_helper)) + $ext_helper = config_item('subclass_prefix').$helper; + $ext_loaded = FALSE; + foreach ($this->_ci_helper_paths as $path) + { + if (file_exists($path.'helpers/'.$ext_helper.'.php')) + { + include_once($path.'helpers/'.$ext_helper.'.php'); + $ext_loaded = TRUE; + } + } + + // If we have loaded extensions - check if the base one is here + if ($ext_loaded === TRUE) { $base_helper = BASEPATH.'helpers/'.$helper.'.php'; - if ( ! file_exists($base_helper)) { show_error('Unable to load the requested file: helpers/'.$helper.'.php'); } - include_once($ext_helper); include_once($base_helper); - $this->_ci_helpers[$helper] = TRUE; - log_message('debug', 'Helper loaded: '.$helper); + log_message('info', 'Helper loaded: '.$helper); continue; } - // Try to load the helper + // No extensions found ... try loading regular helpers and/or overrides foreach ($this->_ci_helper_paths as $path) { if (file_exists($path.'helpers/'.$helper.'.php')) @@ -528,7 +633,7 @@ class CI_Loader { include_once($path.'helpers/'.$helper.'.php'); $this->_ci_helpers[$helper] = TRUE; - log_message('debug', 'Helper loaded: '.$helper); + log_message('info', 'Helper loaded: '.$helper); break; } } @@ -539,6 +644,8 @@ class CI_Loader { show_error('Unable to load the requested file: helpers/'.$helper.'.php'); } } + + return $this; } // -------------------------------------------------------------------- @@ -546,82 +653,96 @@ class CI_Loader { /** * Load Helpers * - * This is simply an alias to the above function in case the - * user has written the plural form of this function. + * An alias for the helper() method in case the developer has + * written the plural form of it. * - * @param array - * @return void + * @uses CI_Loader::helper() + * @param string|string[] $helpers Helper name(s) + * @return object */ public function helpers($helpers = array()) { - $this->helper($helpers); + return $this->helper($helpers); } // -------------------------------------------------------------------- /** - * Loads a language file + * Language Loader * - * @param array - * @param string - * @return void + * Loads language files. + * + * @param string|string[] $files List of language file names to load + * @param string Language name + * @return object */ - public function language($file = array(), $lang = '') + public function language($files, $lang = '') { - $CI =& get_instance(); - - if ( ! is_array($file)) - { - $file = array($file); - } - - foreach ($file as $langfile) - { - $CI->lang->load($langfile, $lang); - } + get_instance()->lang->load($files, $lang); + return $this; } // -------------------------------------------------------------------- /** - * Loads a config file + * Config Loader * - * @param string - * @param bool - * @param bool - * @return void + * Loads a config file (an alias for CI_Config::load()). + * + * @uses CI_Config::load() + * @param string $file Configuration file name + * @param bool $use_sections Whether configuration values should be loaded into their own section + * @param bool $fail_gracefully Whether to just return FALSE or display an error message + * @return bool TRUE if the file was loaded correctly or FALSE on failure */ - public function config($file = '', $use_sections = FALSE, $fail_gracefully = FALSE) + public function config($file, $use_sections = FALSE, $fail_gracefully = FALSE) { - $CI =& get_instance(); - $CI->config->load($file, $use_sections, $fail_gracefully); + return get_instance()->config->load($file, $use_sections, $fail_gracefully); } // -------------------------------------------------------------------- /** - * Driver + * Driver Loader * - * Loads a driver library + * Loads a driver library. * - * @param string the name of the class - * @param mixed the optional parameters - * @param string an optional object name - * @return void + * @param string|string[] $library Driver name(s) + * @param array $params Optional parameters to pass to the driver + * @param string $object_name An optional object name to assign to + * + * @return object|bool Object or FALSE on failure if $library is a string + * and $object_name is set. CI_Loader instance otherwise. */ - public function driver($library = '', $params = NULL, $object_name = NULL) + public function driver($library, $params = NULL, $object_name = NULL) { - if ( ! class_exists('CI_Driver_Library')) + if (is_array($library)) { - // we aren't instantiating an object here, that'll be done by the Library itself - require BASEPATH.'libraries/Driver.php'; - } + foreach ($library as $key => $value) + { + if (is_int($key)) + { + $this->driver($value, $params); + } + else + { + $this->driver($key, $params, $value); + } + } - if ($library == '') + return $this; + } + elseif (empty($library)) { return FALSE; } + if ( ! class_exists('CI_Driver_Library', FALSE)) + { + // We aren't instantiating an object here, just making the base class available + require BASEPATH.'libraries/Driver.php'; + } + // We can save the loader some time since Drivers will *always* be in a subfolder, // and typically identically named to the library if ( ! strpos($library, '/')) @@ -637,13 +758,19 @@ class CI_Loader { /** * Add Package Path * - * Prepends a parent path to the library, model, helper, and config path arrays + * Prepends a parent path to the library, model, helper and config + * path arrays. * - * @param string - * @param boolean - * @return void + * @see CI_Loader::$_ci_library_paths + * @see CI_Loader::$_ci_model_paths + * @see CI_Loader::$_ci_helper_paths + * @see CI_Config::$_config_paths + * + * @param string $path Path to add + * @param bool $view_cascade (default: TRUE) + * @return object */ - public function add_package_path($path, $view_cascade=TRUE) + public function add_package_path($path, $view_cascade = TRUE) { $path = rtrim($path, '/').'/'; @@ -655,7 +782,9 @@ class CI_Loader { // Add config file path $config =& $this->_ci_get_component('config'); - array_unshift($config->_config_paths, $path); + $config->_config_paths[] = $path; + + return $this; } // -------------------------------------------------------------------- @@ -663,14 +792,14 @@ class CI_Loader { /** * Get Package Paths * - * Return a list of all package paths, by default it will ignore BASEPATH. + * Return a list of all package paths. * - * @param string - * @return void + * @param bool $include_base Whether to include BASEPATH (default: FALSE) + * @return array */ public function get_package_paths($include_base = FALSE) { - return $include_base === TRUE ? $this->_ci_library_paths : $this->_ci_model_paths; + return ($include_base === TRUE) ? $this->_ci_library_paths : $this->_ci_model_paths; } // -------------------------------------------------------------------- @@ -678,24 +807,24 @@ class CI_Loader { /** * Remove Package Path * - * Remove a path from the library, model, and helper path arrays if it exists - * If no path is provided, the most recently added path is removed. + * Remove a path from the library, model, helper and/or config + * path arrays if it exists. If no path is provided, the most recently + * added path will be removed removed. * - * @param type - * @param bool - * @return type + * @param string $path Path to remove + * @return object */ - public function remove_package_path($path = '', $remove_config_path = TRUE) + public function remove_package_path($path = '') { $config =& $this->_ci_get_component('config'); - if ($path == '') + 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); + array_shift($this->_ci_library_paths); + array_shift($this->_ci_model_paths); + array_shift($this->_ci_helper_paths); + array_shift($this->_ci_view_paths); + array_pop($config->_config_paths); } else { @@ -725,32 +854,37 @@ class CI_Loader { $this->_ci_model_paths = array_unique(array_merge($this->_ci_model_paths, array(APPPATH))); $this->_ci_view_paths = array_merge($this->_ci_view_paths, array(APPPATH.'views/' => TRUE)); $config->_config_paths = array_unique(array_merge($config->_config_paths, array(APPPATH))); + + return $this; } // -------------------------------------------------------------------- /** - * Loader + * Internal CI Data Loader + * + * Used to load views and files. * - * This function is used to load views and files. * Variables are prefixed with _ci_ to avoid symbol collision with - * variables made available to view files + * variables made available to view files. * - * @param array - * @return void + * @used-by CI_Loader::view() + * @used-by CI_Loader::file() + * @param array $_ci_data Data to load + * @return object */ protected function _ci_load($_ci_data) { // Set the default data variables foreach (array('_ci_view', '_ci_vars', '_ci_path', '_ci_return') as $_ci_val) { - $$_ci_val = ( ! isset($_ci_data[$_ci_val])) ? FALSE : $_ci_data[$_ci_val]; + $$_ci_val = isset($_ci_data[$_ci_val]) ? $_ci_data[$_ci_val] : FALSE; } $file_exists = FALSE; // Set the path to the requested file - if ($_ci_path != '') + if (is_string($_ci_path) && $_ci_path !== '') { $_ci_x = explode('/', $_ci_path); $_ci_file = end($_ci_x); @@ -758,13 +892,13 @@ class CI_Loader { else { $_ci_ext = pathinfo($_ci_view, PATHINFO_EXTENSION); - $_ci_file = ($_ci_ext == '') ? $_ci_view.'.php' : $_ci_view; + $_ci_file = ($_ci_ext === '') ? $_ci_view.'.php' : $_ci_view; - foreach ($this->_ci_view_paths as $view_file => $cascade) + foreach ($this->_ci_view_paths as $_ci_view_file => $cascade) { - if (file_exists($view_file.$_ci_file)) + if (file_exists($_ci_view_file.$_ci_file)) { - $_ci_path = $view_file.$_ci_file; + $_ci_path = $_ci_view_file.$_ci_file; $file_exists = TRUE; break; } @@ -783,7 +917,6 @@ class CI_Loader { // 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) { @@ -796,13 +929,21 @@ class CI_Loader { /* * Extract and cache variables * - * You can either set variables using the dedicated $this->load_vars() + * 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)) { + foreach (array_keys($_ci_vars) as $key) + { + if (strncmp($key, '_ci_', 4) === 0) + { + unset($_ci_vars[$key]); + } + } + $this->_ci_cached_vars = array_merge($this->_ci_cached_vars, $_ci_vars); } extract($this->_ci_cached_vars); @@ -812,29 +953,27 @@ class CI_Loader { * * 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. + * 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) + if ( ! is_php('5.4') && ! ini_get('short_open_tag') && config_item('rewrite_short_tags') === TRUE) { - echo eval('?>'.preg_replace("/;*\s*\?>/", "; ?>", str_replace(''.preg_replace('/;*\s*\?>/', '; ?>', str_replace(' $this->_ci_ob_level + 1) { @@ -863,21 +1001,24 @@ class CI_Loader { $_ci_CI->output->append_output(ob_get_contents()); @ob_end_clean(); } + + return $this; } // -------------------------------------------------------------------- /** - * Load class + * Internal CI Library Loader * - * This function loads the requested class. + * @used-by CI_Loader::library() + * @uses CI_Loader::_ci_init_library() * - * @param string the item that is being loaded - * @param mixed any additional parameters - * @param string an optional object name + * @param string $class Class name to load + * @param mixed $params Optional parameters to pass to the class constructor + * @param string $object_name Optional object name to assign to * @return void */ - protected function _ci_load_class($class, $params = NULL, $object_name = NULL) + protected function _ci_load_library($class, $params = NULL, $object_name = NULL) { // Get the class name, and while we're at it trim any slashes. // The directory path can be included as part of the class name, @@ -886,128 +1027,184 @@ class CI_Loader { // 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); + $subdir = substr($class, 0, ++$last_slash); // Get the filename from the path - $class = substr($class, $last_slash + 1); + $class = substr($class, $last_slash); + } + else + { + $subdir = ''; } - // We'll test for both lowercase and capitalized versions of the file name - foreach (array(ucfirst($class), strtolower($class)) as $class) + $class = ucfirst($class); + + // Is this a stock library? There are a few special conditions if so ... + if (file_exists(BASEPATH.'libraries/'.$subdir.$class.'.php')) { - $subclass = APPPATH.'libraries/'.$subdir.config_item('subclass_prefix').$class.'.php'; + return $this->_ci_load_stock_library($class, $subdir, $params, $object_name); + } - // Is this a class extension request? - if (file_exists($subclass)) + // Let's search for the requested library file and load it. + foreach ($this->_ci_library_paths as $path) + { + // BASEPATH has already been checked for + if ($path === BASEPATH) { - $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); + continue; } - // 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'; + + // Safety: Was the class already loaded by a previous call? + if (class_exists($class, FALSE)) { - $filepath = $path.'libraries/'.$subdir.$class.'.php'; - - // Does the file exist? No? Bummer... - if ( ! file_exists($filepath)) + // Before we deem this to be a duplicate request, let's see + // if a custom object name is being supplied. If so, we'll + // return a new instance of the object + if ($object_name !== NULL) { - 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)) { - $CI =& get_instance(); - if ( ! isset($CI->$object_name)) - { - return $this->_ci_init_class($class, '', $params, $object_name); - } + return $this->_ci_init_library($class, '', $params, $object_name); } - - $is_duplicate = TRUE; - log_message('debug', $class." class already loaded. Second attempt ignored."); - return; } - include_once($filepath); - $this->_ci_loaded_files[] = $filepath; - return $this->_ci_init_class($class, '', $params, $object_name); + log_message('debug', $class.' class already loaded. Second attempt ignored.'); + return; + } + // Does the file exist? No? Bummer... + elseif ( ! file_exists($filepath)) + { + continue; } - } // END FOREACH + include_once($filepath); + return $this->_ci_init_library($class, '', $params, $object_name); + } - // One last attempt. Maybe the library is in a subdirectory, but it wasn't specified? - if ($subdir == '') + // One last attempt. Maybe the library is in a subdirectory, but it wasn't specified? + if ($subdir === '') { - $path = strtolower($class).'/'.$class; - return $this->_ci_load_class($path, $params); + return $this->_ci_load_library($class.'/'.$class, $params, $object_name); } // If we got this far we were unable to find the requested class. - // We do not issue errors if the load call failed due to a duplicate request - if ($is_duplicate == FALSE) - { - log_message('error', "Unable to load the requested class: ".$class); - show_error("Unable to load the requested class: ".$class); - } + log_message('error', 'Unable to load the requested class: '.$class); + show_error('Unable to load the requested class: '.$class); } // -------------------------------------------------------------------- /** - * Instantiates a class + * Internal CI Stock Library Loader * - * @param string - * @param string - * @param bool - * @param string an optional object name - * @return null + * @used-by CI_Loader::_ci_load_library() + * @uses CI_Loader::_ci_init_library() + * + * @param string $library Library name to load + * @param string $file_path Path to the library filename, relative to libraries/ + * @param mixed $params Optional parameters to pass to the class constructor + * @param string $object_name Optional object name to assign to + * @return void */ - protected function _ci_init_class($class, $prefix = '', $config = FALSE, $object_name = NULL) + protected function _ci_load_stock_library($library_name, $file_path, $params, $object_name) { - // Is there an associated config file for this class? Note: these should always be lowercase + $prefix = 'CI_'; + + if (class_exists($prefix.$library_name, FALSE)) + { + if (class_exists(config_item('subclass_prefix').$library_name, FALSE)) + { + $prefix = config_item('subclass_prefix'); + } + + // Before we deem this to be a duplicate request, let's see + // if a custom object name is being supplied. If so, we'll + // return a new instance of the object + if ($object_name !== NULL) + { + $CI =& get_instance(); + if ( ! isset($CI->$object_name)) + { + return $this->_ci_init_library($library_name, $prefix, $params, $object_name); + } + } + + log_message('debug', $library_name.' class already loaded. Second attempt ignored.'); + return; + } + + $paths = $this->_ci_library_paths; + array_pop($paths); // BASEPATH + array_pop($paths); // APPPATH (needs to be the first path checked) + array_unshift($paths, APPPATH); + + foreach ($paths as $path) + { + if (file_exists($path = $path.'libraries/'.$file_path.$library_name.'.php')) + { + // Override + include_once($path); + if (class_exists($prefix.$library_name, FALSE)) + { + return $this->_ci_init_library($library_name, $prefix, $params, $object_name); + } + else + { + log_message('debug', $path.' exists, but does not declare '.$prefix.$library_name); + } + } + } + + include_once(BASEPATH.'libraries/'.$file_path.$library_name.'.php'); + + // Check for extensions + $subclass = config_item('subclass_prefix').$library_name; + foreach ($paths as $path) + { + if (file_exists($path = $path.'libraries/'.$file_path.$subclass.'.php')) + { + include_once($path); + if (class_exists($subclass, FALSE)) + { + $prefix = config_item('subclass_prefix'); + break; + } + else + { + log_message('debug', $path.' exists, but does not declare '.$subclass); + } + } + } + + return $this->_ci_init_library($library_name, $prefix, $params, $object_name); + } + + // -------------------------------------------------------------------- + + /** + * Internal CI Library Instantiator + * + * @used-by CI_Loader::_ci_load_stock_library() + * @used-by CI_Loader::_ci_load_library() + * + * @param string $class Class name + * @param string $prefix Class name prefix + * @param array|null|bool $config Optional configuration to pass to the class constructor: + * FALSE to skip; + * NULL to search in config paths; + * array containing configuration data + * @param string $object_name Optional object name to assign to + * @return void + */ + protected function _ci_init_library($class, $prefix, $config = FALSE, $object_name = NULL) + { + // Is there an associated config file for this class? Note: these should always be lowercase if ($config === NULL) { // Fetch the config paths containing any package paths @@ -1015,117 +1212,111 @@ class CI_Loader { if (is_array($config_component->_config_paths)) { - // Break on the first found file, thus package files - // are not overridden by default paths + $found = FALSE; foreach ($config_component->_config_paths as $path) { // We test for both uppercase and lowercase, for servers that - // are case-sensitive with regard to file names. Check for environment - // first, global next - if (defined('ENVIRONMENT') AND file_exists($path .'config/'.ENVIRONMENT.'/'.strtolower($class).'.php')) + // are case-sensitive with regard to file names. Load global first, + // override with environment next + if (file_exists($path.'config/'.strtolower($class).'.php')) { - include($path .'config/'.ENVIRONMENT.'/'.strtolower($class).'.php'); - break; + include($path.'config/'.strtolower($class).'.php'); + $found = TRUE; } - elseif (defined('ENVIRONMENT') AND file_exists($path .'config/'.ENVIRONMENT.'/'.ucfirst(strtolower($class)).'.php')) + elseif (file_exists($path.'config/'.ucfirst(strtolower($class)).'.php')) { - include($path .'config/'.ENVIRONMENT.'/'.ucfirst(strtolower($class)).'.php'); - break; + include($path.'config/'.ucfirst(strtolower($class)).'.php'); + $found = TRUE; } - elseif (file_exists($path .'config/'.strtolower($class).'.php')) + + if (file_exists($path.'config/'.ENVIRONMENT.'/'.strtolower($class).'.php')) { - include($path .'config/'.strtolower($class).'.php'); - break; + include($path.'config/'.ENVIRONMENT.'/'.strtolower($class).'.php'); + $found = TRUE; } - elseif (file_exists($path .'config/'.ucfirst(strtolower($class)).'.php')) + elseif (file_exists($path.'config/'.ENVIRONMENT.'/'.ucfirst(strtolower($class)).'.php')) + { + include($path.'config/'.ENVIRONMENT.'/'.ucfirst(strtolower($class)).'.php'); + $found = TRUE; + } + + // Break on the first found configuration, thus package + // files are not overridden by default paths + if ($found === TRUE) { - 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; - } + $class_name = $prefix.$class; // Is the class name valid? - if ( ! class_exists($name)) + if ( ! class_exists($class_name, FALSE)) { - log_message('error', "Non-existent class: ".$name); - show_error("Non-existent class: ".$class); + log_message('error', 'Non-existent class: '.$class_name); + show_error('Non-existent class: '.$class_name); } // Set the variable name we will assign the class to - // Was a custom class name supplied? If so we'll use it - $class = strtolower($class); - - if (is_null($object_name)) + // Was a custom class name supplied? If so we'll use it + if (empty($object_name)) { - $classvar = ( ! isset($this->_ci_varmap[$class])) ? $class : $this->_ci_varmap[$class]; + $object_name = strtolower($class); + if (isset($this->_ci_varmap[$object_name])) + { + $object_name = $this->_ci_varmap[$object_name]; + } } - else + + // Don't overwrite existing properties + $CI =& get_instance(); + if (isset($CI->$object_name)) { - $classvar = $object_name; + if ($CI->$object_name instanceof $class_name) + { + log_message('debug', $class_name." has already been instantiated as '".$object_name."'. Second attempt aborted."); + return; + } + + show_error("Resource '".$object_name."' already exists and is not a ".$class_name." instance."); } // Save the class name and object name - $this->_ci_classes[$class] = $classvar; + $this->_ci_classes[$object_name] = $class; // Instantiate the class - $CI =& get_instance(); - if ($config !== NULL) - { - $CI->$classvar = new $name($config); - } - else - { - $CI->$classvar = new $name; - } + $CI->$object_name = isset($config) + ? new $class_name($config) + : new $class_name(); } // -------------------------------------------------------------------- /** - * Autoloader + * CI Autoloader * - * The config/autoload.php file contains an array that permits sub-systems, - * libraries, and helpers to be loaded automatically. + * Loads component listed in the config/autoload.php file. * - * @param array + * @used-by CI_Loader::initialize() * @return void */ - private function _ci_autoloader() + protected function _ci_autoloader() { - if (defined('ENVIRONMENT') AND file_exists(APPPATH.'config/'.ENVIRONMENT.'/autoload.php')) - { - include(APPPATH.'config/'.ENVIRONMENT.'/autoload.php'); - } - else + if (file_exists(APPPATH.'config/autoload.php')) { include(APPPATH.'config/autoload.php'); } + if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/autoload.php')) + { + include(APPPATH.'config/'.ENVIRONMENT.'/autoload.php'); + } + if ( ! isset($autoload)) { - return FALSE; + return; } // Autoload packages @@ -1140,31 +1331,29 @@ class CI_Loader { // Load any custom config file if (count($autoload['config']) > 0) { - $CI =& get_instance(); - foreach ($autoload['config'] as $key => $val) + foreach ($autoload['config'] as $val) { - $CI->config->load($val); + $this->config($val); } } // Autoload helpers and languages foreach (array('helper', 'language') as $type) { - if (isset($autoload[$type]) AND count($autoload[$type]) > 0) + if (isset($autoload[$type]) && 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 drivers + if (isset($autoload['drivers'])) { - $autoload['libraries'] = $autoload['core']; + $this->driver($autoload['drivers']); } // Load libraries - if (isset($autoload['libraries']) AND count($autoload['libraries']) > 0) + if (isset($autoload['libraries']) && count($autoload['libraries']) > 0) { // Load the database driver. if (in_array('database', $autoload['libraries'])) @@ -1174,10 +1363,7 @@ class CI_Loader { } // Load all other libraries - foreach ($autoload['libraries'] as $item) - { - $this->library($item); - } + $this->library($autoload['libraries']); } // Autoload models @@ -1190,24 +1376,27 @@ class CI_Loader { // -------------------------------------------------------------------- /** - * Object to Array + * CI Object to Array translator * - * Takes an object as input and converts the class variables to array key/vals + * Takes an object as input and converts the class variables to + * an associative array with key/value pairs. * - * @param object + * @param object $object Object data to translate * @return array */ protected function _ci_object_to_array($object) { - return (is_object($object)) ? get_object_vars($object) : $object; + return is_object($object) ? get_object_vars($object) : $object; } // -------------------------------------------------------------------- /** - * Get a reference to a specific library or model + * CI Component getter * - * @param string + * Get a reference to a specific library or model. + * + * @param string $component Component name * @return bool */ protected function &_ci_get_component($component) @@ -1221,29 +1410,28 @@ class CI_Loader { /** * Prep filename * - * This function preps the name of various items to make loading them more reliable. + * This function prepares filenames of various items to + * make their loading more reliable. * - * @param mixed - * @param string + * @param string|string[] $filename Filename(s) + * @param string $extension Filename extension * @return array */ protected function _ci_prep_filename($filename, $extension) { if ( ! is_array($filename)) { - return array(strtolower(str_replace('.php', '', str_replace($extension, '', $filename)).$extension)); + return array(strtolower(str_replace(array($extension, '.php'), '', $filename).$extension)); } else { foreach ($filename as $key => $val) { - $filename[$key] = strtolower(str_replace('.php', '', str_replace($extension, '', $val)).$extension); + $filename[$key] = strtolower(str_replace(array($extension, '.php'), '', $val).$extension); } return $filename; } } -} -/* End of file Loader.php */ -/* Location: ./system/core/Loader.php */ \ No newline at end of file +} diff --git a/src/system/core/Log.php b/src/system/core/Log.php new file mode 100644 index 00000000..1abdaa00 --- /dev/null +++ b/src/system/core/Log.php @@ -0,0 +1,247 @@ + 1, 'DEBUG' => 2, 'INFO' => 3, 'ALL' => 4); + + // -------------------------------------------------------------------- + + /** + * Class constructor + * + * @return void + */ + public function __construct() + { + $config =& get_config(); + + $this->_log_path = ($config['log_path'] !== '') ? $config['log_path'] : APPPATH.'logs/'; + $this->_file_ext = (isset($config['log_file_extension']) && $config['log_file_extension'] !== '') + ? ltrim($config['log_file_extension'], '.') : 'php'; + + file_exists($this->_log_path) OR mkdir($this->_log_path, 0755, TRUE); + + if ( ! is_dir($this->_log_path) OR ! is_really_writable($this->_log_path)) + { + $this->_enabled = FALSE; + } + + if (is_numeric($config['log_threshold'])) + { + $this->_threshold = (int) $config['log_threshold']; + } + elseif (is_array($config['log_threshold'])) + { + $this->_threshold = 0; + $this->_threshold_array = array_flip($config['log_threshold']); + } + + if ( ! empty($config['log_date_format'])) + { + $this->_date_fmt = $config['log_date_format']; + } + + if ( ! empty($config['log_file_permissions']) && is_int($config['log_file_permissions'])) + { + $this->_file_permissions = $config['log_file_permissions']; + } + } + + // -------------------------------------------------------------------- + + /** + * Write Log File + * + * Generally this function will be called using the global log_message() function + * + * @param string $level The error level: 'error', 'debug' or 'info' + * @param string $msg The error message + * @return bool + */ + public function write_log($level, $msg) + { + if ($this->_enabled === FALSE) + { + return FALSE; + } + + $level = strtoupper($level); + + if (( ! isset($this->_levels[$level]) OR ($this->_levels[$level] > $this->_threshold)) + && ! isset($this->_threshold_array[$this->_levels[$level]])) + { + return FALSE; + } + + $filepath = $this->_log_path.'log-'.date('Y-m-d').'.'.$this->_file_ext; + $message = ''; + + if ( ! file_exists($filepath)) + { + $newfile = TRUE; + // Only add protection to php files + if ($this->_file_ext === 'php') + { + $message .= "\n\n"; + } + } + + if ( ! $fp = @fopen($filepath, 'ab')) + { + return FALSE; + } + + flock($fp, LOCK_EX); + + // Instantiating DateTime with microseconds appended to initial date is needed for proper support of this format + if (strpos($this->_date_fmt, 'u') !== FALSE) + { + $microtime_full = microtime(TRUE); + $microtime_short = sprintf("%06d", ($microtime_full - floor($microtime_full)) * 1000000); + $date = new DateTime(date('Y-m-d H:i:s.'.$microtime_short, $microtime_full)); + $date = $date->format($this->_date_fmt); + } + else + { + $date = date($this->_date_fmt); + } + + $message .= $this->_format_line($level, $date, $msg); + + for ($written = 0, $length = strlen($message); $written < $length; $written += $result) + { + if (($result = fwrite($fp, substr($message, $written))) === FALSE) + { + break; + } + } + + flock($fp, LOCK_UN); + fclose($fp); + + if (isset($newfile) && $newfile === TRUE) + { + chmod($filepath, $this->_file_permissions); + } + + return is_int($result); + } + + // -------------------------------------------------------------------- + + /** + * Format the log line. + * + * This is for extensibility of log formatting + * If you want to change the log format, extend the CI_Log class and override this method + * + * @param string $level The error level + * @param string $date Formatted date string + * @param string $msg The log message + * @return string Formatted log line with a new line character '\n' at the end + */ + protected function _format_line($level, $date, $message) + { + return $level.' - '.$date.' --> '.$message."\n"; + } +} diff --git a/src/system/core/Model.php b/src/system/core/Model.php index 9bbb1fcd..941881a9 100644 --- a/src/system/core/Model.php +++ b/src/system/core/Model.php @@ -1,58 +1,80 @@ -$key; + // Debugging note: + // If you're here because you're getting an error message + // saying 'Undefined Property: system/core/Model.php', it's + // most likely a typo in your model code. + return get_instance()->$key; } -} -// END Model Class -/* End of file Model.php */ -/* Location: ./system/core/Model.php */ \ No newline at end of file +} diff --git a/src/system/core/Output.php b/src/system/core/Output.php index fc88f6e5..ec9c21b9 100644 --- a/src/system/core/Output.php +++ b/src/system/core/Output.php @@ -1,113 +1,147 @@ -_zlib_oc = @ini_get('zlib.output_compression'); + $this->_zlib_oc = (bool) ini_get('zlib.output_compression'); + $this->_compress_output = ( + $this->_zlib_oc === FALSE + && config_item('compress_output') === TRUE + && extension_loaded('zlib') + ); // 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->mimes =& get_mimes(); - - $this->mime_types = $mimes; - - log_message('debug', "Output Class Initialized"); + log_message('info', 'Output Class Initialized'); } // -------------------------------------------------------------------- @@ -115,12 +149,11 @@ class CI_Output { /** * Get Output * - * Returns the current output string + * Returns the current output string. * - * @access public * @return string */ - function get_output() + public function get_output() { return $this->final_output; } @@ -130,16 +163,14 @@ class CI_Output { /** * Set Output * - * Sets the output string + * Sets the output string. * - * @access public - * @param string - * @return void + * @param string $output Output data + * @return CI_Output */ - function set_output($output) + public function set_output($output) { $this->final_output = $output; - return $this; } @@ -148,23 +179,14 @@ class CI_Output { /** * Append Output * - * Appends data onto the output string + * Appends data onto the output string. * - * @access public - * @param string - * @return void + * @param string $output Data to append + * @return CI_Output */ - function append_output($output) + public function append_output($output) { - if ($this->final_output == '') - { - $this->final_output = $output; - } - else - { - $this->final_output .= $output; - } - + $this->final_output .= $output; return $this; } @@ -173,52 +195,49 @@ class CI_Output { /** * Set Header * - * Lets you set a server header which will be outputted with the final display. + * Lets you set a server header which will be sent with the final output. * - * Note: If a file is cached, headers will not be sent. We need to figure out - * how to permit header data to be saved with the cache data... + * Note: If a file is cached, headers will not be sent. + * @todo We need to figure out how to permit headers to be cached. * - * @access public - * @param string - * @param bool - * @return void + * @param string $header Header + * @param bool $replace Whether to replace the old header value, if already set + * @return CI_Output */ - function set_header($header, $replace = TRUE) + public function set_header($header, $replace = TRUE) { // If zlib.output_compression is enabled it will compress the output, // but it will not modify the content-length header to compensate for // the reduction, causing the browser to hang waiting for more data. // We'll just skip content-length in those cases. - - if ($this->_zlib_oc && strncasecmp($header, 'content-length', 14) == 0) + if ($this->_zlib_oc && strncasecmp($header, 'content-length', 14) === 0) { - return; + return $this; } $this->headers[] = array($header, $replace); - return $this; } // -------------------------------------------------------------------- /** - * Set Content Type Header + * Set Content-Type Header * - * @access public - * @param string extension of the file we're outputting - * @return void + * @param string $mime_type Extension of the file we're outputting + * @param string $charset Character set (default: NULL) + * @return CI_Output */ - function set_content_type($mime_type) + public function set_content_type($mime_type, $charset = NULL) { if (strpos($mime_type, '/') === FALSE) { $extension = ltrim($mime_type, '.'); // Is this extension supported? - if (isset($this->mime_types[$extension])) + if (isset($this->mimes[$extension])) { - $mime_type =& $this->mime_types[$extension]; + $mime_type =& $this->mimes[$extension]; if (is_array($mime_type)) { @@ -227,28 +246,88 @@ class CI_Output { } } - $header = 'Content-Type: '.$mime_type; + $this->mime_type = $mime_type; + + if (empty($charset)) + { + $charset = config_item('charset'); + } + + $header = 'Content-Type: '.$mime_type + .(empty($charset) ? '' : '; charset='.$charset); $this->headers[] = array($header, TRUE); - return $this; } // -------------------------------------------------------------------- /** - * Set HTTP Status Header - * moved to Common procedural functions in 1.7.2 + * Get Current Content-Type Header * - * @access public - * @param int the status code - * @param string - * @return void + * @return string 'text/html', if not already set */ - function set_status_header($code = 200, $text = '') + public function get_content_type() + { + for ($i = 0, $c = count($this->headers); $i < $c; $i++) + { + if (sscanf($this->headers[$i][0], 'Content-Type: %[^;]', $content_type) === 1) + { + return $content_type; + } + } + + return 'text/html'; + } + + // -------------------------------------------------------------------- + + /** + * Get Header + * + * @param string $header_name + * @return string + */ + public function get_header($header) + { + // Combine headers already sent with our batched headers + $headers = array_merge( + // We only need [x][0] from our multi-dimensional array + array_map('array_shift', $this->headers), + headers_list() + ); + + if (empty($headers) OR empty($header)) + { + return NULL; + } + + for ($i = 0, $c = count($headers); $i < $c; $i++) + { + if (strncasecmp($header, $headers[$i], $l = strlen($header)) === 0) + { + return trim(substr($headers[$i], $l+1)); + } + } + + return NULL; + } + + // -------------------------------------------------------------------- + + /** + * Set HTTP Status Header + * + * As of version 1.7.2, this is an alias for common function + * set_status_header(). + * + * @param int $code Status code (default: 200) + * @param string $text Optional message + * @return CI_Output + */ + public function set_status_header($code = 200, $text = '') { set_status_header($code, $text); - return $this; } @@ -257,14 +336,12 @@ class CI_Output { /** * Enable/disable Profiler * - * @access public - * @param bool - * @return void + * @param bool $val TRUE to enable or FALSE to disable + * @return CI_Output */ - function enable_profiler($val = TRUE) + public function enable_profiler($val = TRUE) { - $this->enable_profiler = (is_bool($val)) ? $val : TRUE; - + $this->enable_profiler = is_bool($val) ? $val : TRUE; return $this; } @@ -273,17 +350,23 @@ class CI_Output { /** * Set Profiler Sections * - * Allows override of default / config settings for Profiler section display + * Allows override of default/config settings for + * Profiler section display. * - * @access public - * @param array - * @return void + * @param array $sections Profiler sections + * @return CI_Output */ - function set_profiler_sections($sections) + public function set_profiler_sections($sections) { + if (isset($sections['query_toggle_count'])) + { + $this->_profiler_sections['query_toggle_count'] = (int) $sections['query_toggle_count']; + unset($sections['query_toggle_count']); + } + foreach ($sections as $section => $enable) { - $this->_profiler_sections[$section] = ($enable !== FALSE) ? TRUE : FALSE; + $this->_profiler_sections[$section] = ($enable !== FALSE); } return $this; @@ -294,14 +377,12 @@ class CI_Output { /** * Set Cache * - * @access public - * @param integer - * @return void + * @param int $time Cache expiration time in minutes + * @return CI_Output */ - function cache($time) + public function cache($time) { - $this->cache_expiration = ( ! is_numeric($time)) ? 0 : $time; - + $this->cache_expiration = is_numeric($time) ? $time : 0; return $this; } @@ -310,27 +391,27 @@ class CI_Output { /** * Display Output * - * All "view" data is automatically put into this variable by the controller class: + * Processes and sends finalized output data to the browser along + * with any server headers and profile data. It also stops benchmark + * timers so the page rendering speed and memory usage can be shown. * - * $this->final_output + * Note: All "view" data is automatically put into $this->final_output + * by controller class. * - * This function sends the finalized output data to the browser along - * with any server headers and profile data. It also stops the - * benchmark timer so the page rendering speed and memory usage can be shown. - * - * @access public - * @param string - * @return mixed + * @uses CI_Output::$final_output + * @param string $output Output data override + * @return void */ - function _display($output = '') + public function _display($output = '') { - // Note: We use globals because we can't use $CI =& get_instance() + // Note: We use load_class() because we can't use $CI =& get_instance() // since this function is sometimes called by the caching mechanism, // which happens before the CI super object is available. - global $BM, $CFG; + $BM =& load_class('Benchmark', 'core'); + $CFG =& load_class('Config', 'core'); // Grab the super object if we can. - if (class_exists('CI_Controller')) + if (class_exists('CI_Controller', FALSE)) { $CI =& get_instance(); } @@ -338,14 +419,14 @@ class CI_Output { // -------------------------------------------------------------------- // Set the output data - if ($output == '') + if ($output === '') { $output =& $this->final_output; } // -------------------------------------------------------------------- - // Do we need to write a cache file? Only if the controller does not have its + // Do we need to write a cache file? Only if the controller does not have its // own _output() method and we are not dealing with a cache file, which we // can determine by the existence of the $CI object above if ($this->cache_expiration > 0 && isset($CI) && ! method_exists($CI, '_output')) @@ -362,24 +443,18 @@ class CI_Output { 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); + $memory = round(memory_get_usage() / 1024 / 1024, 2).'MB'; + $output = str_replace(array('{elapsed_time}', '{memory_usage}'), array($elapsed, $memory), $output); } // -------------------------------------------------------------------- // Is compression requested? - if ($CFG->item('compress_output') === TRUE && $this->_zlib_oc == FALSE) + if (isset($CI) // This means that we're not serving a cache file, if we were, it would already be compressed + && $this->_compress_output === TRUE + && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE) { - if (extension_loaded('zlib')) - { - if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) AND strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE) - { - ob_start('ob_gzhandler'); - } - } + ob_start('ob_gzhandler'); } // -------------------------------------------------------------------- @@ -400,20 +475,34 @@ class CI_Output { // simply echo out the data and exit. if ( ! isset($CI)) { + if ($this->_compress_output === TRUE) + { + if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE) + { + header('Content-Encoding: gzip'); + header('Content-Length: '.strlen($output)); + } + else + { + // User agent doesn't support gzip compression, + // so we'll have to decompress our cache + $output = gzinflate(substr($output, 10, -8)); + } + } + echo $output; - log_message('debug', "Final output sent to browser"); - log_message('debug', "Total execution time: ".$elapsed); - return TRUE; + log_message('info', 'Final output sent to browser'); + log_message('debug', 'Total execution time: '.$elapsed); + return; } // -------------------------------------------------------------------- // Do we need to generate profile data? // If so, load the Profile class and run it. - if ($this->enable_profiler == TRUE) + if ($this->enable_profiler === TRUE) { $CI->load->library('profiler'); - if ( ! empty($this->_profiler_sections)) { $CI->profiler->set_sections($this->_profiler_sections); @@ -421,20 +510,13 @@ class CI_Output { // 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, -1, $count).$CI->profiler->run(); + if ($count > 0) { - $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')) @@ -443,133 +525,276 @@ class CI_Output { } else { - echo $output; // Send it to the browser! + echo $output; // Send it to the browser! } - log_message('debug', "Final output sent to browser"); - log_message('debug', "Total execution time: ".$elapsed); + log_message('info', 'Final output sent to browser'); + log_message('debug', 'Total execution time: '.$elapsed); } // -------------------------------------------------------------------- /** - * Write a Cache File + * Write Cache * - * @access public - * @param string + * @param string $output Output data to cache * @return void */ - function _write_cache($output) + public function _write_cache($output) { $CI =& get_instance(); $path = $CI->config->item('cache_path'); - - $cache_path = ($path == '') ? APPPATH.'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); + 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(); + $uri = $CI->config->item('base_url') + .$CI->config->item('index_page') + .$CI->uri->uri_string(); + + if (($cache_query_string = $CI->config->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING'])) + { + if (is_array($cache_query_string)) + { + $uri .= '?'.http_build_query(array_intersect_key($_GET, array_flip($cache_query_string))); + } + else + { + $uri .= '?'.$_SERVER['QUERY_STRING']; + } + } $cache_path .= md5($uri); - if ( ! $fp = @fopen($cache_path, FOPEN_WRITE_CREATE_DESTRUCTIVE)) + if ( ! $fp = @fopen($cache_path, 'w+b')) { - log_message('error', "Unable to write cache file: ".$cache_path); + 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); + // If output compression is enabled, compress the cache + // itself, so that we don't have to do that each time + // we're serving it + if ($this->_compress_output === TRUE) + { + $output = gzencode($output); + + if ($this->get_header('content-type') === NULL) + { + $this->set_content_type($this->mime_type); + } + } + + $expire = time() + ($this->cache_expiration * 60); + + // Put together our serialized info. + $cache_info = serialize(array( + 'expire' => $expire, + 'headers' => $this->headers + )); + + $output = $cache_info.'ENDCI--->'.$output; + + for ($written = 0, $length = strlen($output); $written < $length; $written += $result) + { + if (($result = fwrite($fp, substr($output, $written))) === FALSE) + { + break; + } + } + flock($fp, LOCK_UN); } else { - log_message('error', "Unable to secure a file lock for file at: ".$cache_path); + 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); + fclose($fp); + + if (is_int($result)) + { + chmod($cache_path, 0640); + log_message('debug', 'Cache file written: '.$cache_path); + + // Send HTTP cache-control headers to browser to match file cache settings. + $this->set_cache_header($_SERVER['REQUEST_TIME'], $expire); + } + else + { + @unlink($cache_path); + log_message('error', 'Unable to write the complete cache content at: '.$cache_path); + } } // -------------------------------------------------------------------- /** - * Update/serve a cached file + * Update/serve cached output * - * @access public - * @param object config class - * @param object uri class - * @return void + * @uses CI_Config + * @uses CI_URI + * + * @param object &$CFG CI_Config class instance + * @param object &$URI CI_URI class instance + * @return bool TRUE on success or FALSE on failure */ - function _display_cache(&$CFG, &$URI) + public function _display_cache(&$CFG, &$URI) { - $cache_path = ($CFG->item('cache_path') == '') ? APPPATH.'cache/' : $CFG->item('cache_path'); + $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; + // Build the file path. The file name is an MD5 hash of the full URI + $uri = $CFG->item('base_url').$CFG->item('index_page').$URI->uri_string; + + if (($cache_query_string = $CFG->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING'])) + { + if (is_array($cache_query_string)) + { + $uri .= '?'.http_build_query(array_intersect_key($_GET, array_flip($cache_query_string))); + } + else + { + $uri .= '?'.$_SERVER['QUERY_STRING']; + } + } $filepath = $cache_path.md5($uri); - if ( ! @file_exists($filepath)) - { - return FALSE; - } - - if ( ! $fp = @fopen($filepath, FOPEN_READ)) + if ( ! file_exists($filepath) OR ! $fp = @fopen($filepath, 'rb')) { return FALSE; } flock($fp, LOCK_SH); - $cache = ''; - if (filesize($filepath) > 0) - { - $cache = fread($fp, filesize($filepath)); - } + $cache = (filesize($filepath) > 0) ? fread($fp, filesize($filepath)) : ''; flock($fp, LOCK_UN); fclose($fp); - // Strip out the embedded timestamp - if ( ! preg_match("/(\d+TS--->)/", $cache, $match)) + // Look for embedded serialized file info. + if ( ! preg_match('/^(.*)ENDCI--->/', $cache, $match)) { return FALSE; } - // Has the file expired? If so we'll delete it. - if (time() >= trim(str_replace('TS--->', '', $match['1']))) + $cache_info = unserialize($match[1]); + $expire = $cache_info['expire']; + + $last_modified = filemtime($filepath); + + // Has the file expired? + if ($_SERVER['REQUEST_TIME'] >= $expire && is_really_writable($cache_path)) { - if (is_really_writable($cache_path)) - { - @unlink($filepath); - log_message('debug', "Cache file has expired. File deleted"); - return FALSE; - } + // If so we'll delete it. + @unlink($filepath); + log_message('debug', 'Cache file has expired. File deleted.'); + return FALSE; + } + else + { + // Or else send the HTTP cache control headers. + $this->set_cache_header($last_modified, $expire); + } + + // Add headers from cache file. + foreach ($cache_info['headers'] as $header) + { + $this->set_header($header[0], $header[1]); } // Display the cache - $this->_display(str_replace($match['0'], '', $cache)); - log_message('debug', "Cache file is current. Sending it to browser."); + $this->_display(substr($cache, strlen($match[0]))); + log_message('debug', 'Cache file is current. Sending it to browser.'); return TRUE; } + // -------------------------------------------------------------------- + + /** + * Delete cache + * + * @param string $uri URI string + * @return bool + */ + public function delete_cache($uri = '') + { + $CI =& get_instance(); + $cache_path = $CI->config->item('cache_path'); + if ($cache_path === '') + { + $cache_path = APPPATH.'cache/'; + } + + if ( ! is_dir($cache_path)) + { + log_message('error', 'Unable to find cache path: '.$cache_path); + return FALSE; + } + + if (empty($uri)) + { + $uri = $CI->uri->uri_string(); + + if (($cache_query_string = $CI->config->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING'])) + { + if (is_array($cache_query_string)) + { + $uri .= '?'.http_build_query(array_intersect_key($_GET, array_flip($cache_query_string))); + } + else + { + $uri .= '?'.$_SERVER['QUERY_STRING']; + } + } + } + + $cache_path .= md5($CI->config->item('base_url').$CI->config->item('index_page').ltrim($uri, '/')); + + if ( ! @unlink($cache_path)) + { + log_message('error', 'Unable to delete cache file for '.$uri); + return FALSE; + } + + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Set Cache Header + * + * Set the HTTP headers to match the server-side file cache settings + * in order to reduce bandwidth. + * + * @param int $last_modified Timestamp of when the page was last modified + * @param int $expiration Timestamp of when should the requested page expire from cache + * @return void + */ + public function set_cache_header($last_modified, $expiration) + { + $max_age = $expiration - $_SERVER['REQUEST_TIME']; + + if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $last_modified <= strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])) + { + $this->set_status_header(304); + exit; + } + else + { + header('Pragma: public'); + header('Cache-Control: max-age='.$max_age.', public'); + header('Expires: '.gmdate('D, d M Y H:i:s', $expiration).' GMT'); + header('Last-modified: '.gmdate('D, d M Y H:i:s', $last_modified).' GMT'); + } + } } -// END Output Class - -/* End of file Output.php */ -/* Location: ./system/core/Output.php */ \ No newline at end of file diff --git a/src/system/core/Router.php b/src/system/core/Router.php index b39dc163..045d3668 100644 --- a/src/system/core/Router.php +++ b/src/system/core/Router.php @@ -1,20 +1,41 @@ -config =& load_class('Config', 'core'); $this->uri =& load_class('URI', 'core'); - log_message('debug', "Router Class Initialized"); + + $this->enable_query_strings = ( ! is_cli() && $this->config->item('enable_query_strings') === TRUE); + + // If a directory override is configured, it has to be set before any dynamic routing logic + is_array($routing) && isset($routing['directory']) && $this->set_directory($routing['directory']); + $this->_set_routing(); + + // Set any routing overrides that may exist in the main index file + if (is_array($routing)) + { + empty($routing['controller']) OR $this->set_class($routing['controller']); + empty($routing['function']) OR $this->set_method($routing['function']); + } + + log_message('info', 'Router Class Initialized'); } // -------------------------------------------------------------------- /** - * Set the route mapping + * Set route mapping * - * This function determines what should be served based on the URI request, + * 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() + protected 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')) + // Load the routes.php file. It would be great if we could + // skip this for enable_query_strings = TRUE, but then + // default_controller would be empty ... + if (file_exists(APPPATH.'config/routes.php')) { include(APPPATH.'config/routes.php'); } - $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) + if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/routes.php')) { - return $this->_validate_request($segments); + include(APPPATH.'config/'.ENVIRONMENT.'/routes.php'); } - // 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 == '') + // Validate & get reserved routes + if (isset($route) && is_array($route)) { - return $this->_set_default_controller(); + isset($route['default_controller']) && $this->default_controller = $route['default_controller']; + isset($route['translate_uri_dashes']) && $this->translate_uri_dashes = $route['translate_uri_dashes']; + unset($route['default_controller'], $route['translate_uri_dashes']); + $this->routes = $route; } - // 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) + // Are query strings enabled in the config file? Normally CI doesn't utilize query strings + // since URI segments are more search-engine friendly, but they can optionally be used. + // If this feature is enabled, we will gather the directory/class/method a little differently + if ($this->enable_query_strings) { - show_error("Unable to determine what should be displayed. A default route has not been specified in the routing file."); + // If the directory is set at this time, it means an override exists, so skip the checks + if ( ! isset($this->directory)) + { + $_d = $this->config->item('directory_trigger'); + $_d = isset($_GET[$_d]) ? trim($_GET[$_d], " \t\n\r\0\x0B/") : ''; + + if ($_d !== '') + { + $this->uri->filter_uri($_d); + $this->set_directory($_d); + } + } + + $_c = trim($this->config->item('controller_trigger')); + if ( ! empty($_GET[$_c])) + { + $this->uri->filter_uri($_GET[$_c]); + $this->set_class($_GET[$_c]); + + $_f = trim($this->config->item('function_trigger')); + if ( ! empty($_GET[$_f])) + { + $this->uri->filter_uri($_GET[$_f]); + $this->set_method($_GET[$_f]); + } + + $this->uri->rsegments = array( + 1 => $this->class, + 2 => $this->method + ); + } + else + { + $this->_set_default_controller(); + } + + // Routing rules don't apply to query strings and we don't need to detect + // directories, so we're done here + return; } - // Is 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); + // Is there anything to parse? + if ($this->uri->uri_string !== '') + { + $this->_parse_routes(); } else { - $this->set_class($this->default_controller); - $this->set_method('index'); - $this->_set_request(array($this->default_controller, 'index')); + $this->_set_default_controller(); } - - // 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 + * Set request route * - * This function takes an array of URI segments as - * input, and sets the current class/method + * Takes an array of URI segments as input and sets the class/method + * to be called. * - * @access private - * @param array - * @param bool + * @used-by CI_Router::_parse_routes() + * @param array $segments URI segments * @return void */ - function _set_request($segments = array()) + protected function _set_request($segments = array()) { $segments = $this->_validate_request($segments); - - if (count($segments) == 0) + // If we don't have any segments left - try the default controller; + // WARNING: Directories get shifted out of the segments array! + if (empty($segments)) { - return $this->_set_default_controller(); + $this->_set_default_controller(); + return; + } + + if ($this->translate_uri_dashes === TRUE) + { + $segments[0] = str_replace('-', '_', $segments[0]); + if (isset($segments[1])) + { + $segments[1] = str_replace('-', '_', $segments[1]); + } } $this->set_class($segments[0]); - if (isset($segments[1])) { - // 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 + array_unshift($segments, NULL); + unset($segments[0]); $this->uri->rsegments = $segments; } // -------------------------------------------------------------------- /** - * Validates the supplied segments. Attempts to determine the path to - * the controller. + * Set default controller * - * @access private - * @param array - * @return array + * @return void */ - function _validate_request($segments) + protected function _set_default_controller() { - if (count($segments) == 0) + if (empty($this->default_controller)) { - return $segments; + show_error('Unable to determine what should be displayed. A default route has not been specified in the routing file.'); } - // Does the requested controller exist in the root folder? - if (file_exists(APPPATH.'controllers/'.$segments[0].'.php')) + // Is the method being specified? + if (sscanf($this->default_controller, '%[^/]/%s', $class, $method) !== 2) { - return $segments; + $method = 'index'; } - // Is the controller in a sub-folder? - if (is_dir(APPPATH.'controllers/'.$segments[0])) + if ( ! file_exists(APPPATH.'controllers/'.$this->directory.ucfirst($class).'.php')) { - // 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; + // This will trigger 404 later + return; } + $this->set_class($class); + $this->set_method($method); - // 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']); + // Assign routed segments, index starting from 1 + $this->uri->rsegments = array( + 1 => $class, + 2 => $method + ); - $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]); + log_message('debug', 'No URI present. Default controller set.'); } // -------------------------------------------------------------------- /** - * Parse Routes + * Validate request * - * 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. + * Attempts validate the URI request and determine the controller path. + * + * @used-by CI_Router::_set_request() + * @param array $segments URI segments + * @return mixed URI segments + */ + protected function _validate_request($segments) + { + $c = count($segments); + $directory_override = isset($this->directory); + + // Loop through our segments and return as soon as a controller + // is found or when such a directory doesn't exist + while ($c-- > 0) + { + $test = $this->directory + .ucfirst($this->translate_uri_dashes === TRUE ? str_replace('-', '_', $segments[0]) : $segments[0]); + + if ( ! file_exists(APPPATH.'controllers/'.$test.'.php') + && $directory_override === FALSE + && is_dir(APPPATH.'controllers/'.$this->directory.$segments[0]) + ) + { + $this->set_directory(array_shift($segments), TRUE); + continue; + } + + return $segments; + } + + // This means that all segments were actually directories + return $segments; + } + + // -------------------------------------------------------------------- + + /** + * Parse Routes + * + * Matches any routes that may exist in the config/routes.php file + * against the URI to determine if the class/method need to be remapped. * - * @access private * @return void */ - function _parse_routes() + protected 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])); - } + // Get HTTP verb + $http_verb = isset($_SERVER['REQUEST_METHOD']) ? strtolower($_SERVER['REQUEST_METHOD']) : 'cli'; - // Loop through the route array looking for wild-cards + // Loop through the route array looking for wildcards foreach ($this->routes as $key => $val) { - // Convert wild-cards to RegEx - $key = str_replace(':any', '.+', str_replace(':num', '[0-9]+', $key)); + // Check if route format is using HTTP verbs + if (is_array($val)) + { + $val = array_change_key_case($val, CASE_LOWER); + if (isset($val[$http_verb])) + { + $val = $val[$http_verb]; + } + else + { + continue; + } + } + + // Convert wildcards to RegEx + $key = str_replace(array(':any', ':num'), array('[^/]+', '[0-9]+'), $key); // Does the RegEx match? - if (preg_match('#^'.$key.'$#', $uri)) + if (preg_match('#^'.$key.'$#', $uri, $matches)) { - // Do we have a back-reference? - if (strpos($val, '$') !== FALSE AND strpos($key, '(') !== FALSE) + // Are we using callbacks to process back-references? + if ( ! is_string($val) && is_callable($val)) + { + // Remove the original string from the matches array. + array_shift($matches); + + // Execute the callback using the values in matches as its parameters. + $val = call_user_func_array($val, $matches); + } + // Are we using the default routing method for back-references? + elseif (strpos($val, '$') !== FALSE && strpos($key, '(') !== FALSE) { $val = preg_replace('#^'.$key.'$#', $val, $uri); } - return $this->_set_request(explode('/', $val)); + $this->_set_request(explode('/', $val)); + return; } } // If we got this far it means we didn't encounter a // matching route so we'll set the site default route - $this->_set_request($this->uri->segments); + $this->_set_request(array_values($this->uri->segments)); } // -------------------------------------------------------------------- /** - * Set the class name + * Set class name * - * @access public - * @param string + * @param string $class Class name * @return void */ - function set_class($class) + public function set_class($class) { $this->class = str_replace(array('/', '.'), '', $class); } @@ -415,10 +441,10 @@ class CI_Router { /** * Fetch the current class * - * @access public + * @deprecated 3.0.0 Read the 'class' property instead * @return string */ - function fetch_class() + public function fetch_class() { return $this->class; } @@ -426,13 +452,12 @@ class CI_Router { // -------------------------------------------------------------------- /** - * Set the method name + * Set method name * - * @access public - * @param string + * @param string $method Method name * @return void */ - function set_method($method) + public function set_method($method) { $this->method = $method; } @@ -440,84 +465,51 @@ class CI_Router { // -------------------------------------------------------------------- /** - * Fetch the current method + * Fetch the current method * - * @access public + * @deprecated 3.0.0 Read the 'method' property instead * @return string */ - function fetch_method() + public function fetch_method() { - if ($this->method == $this->fetch_class()) - { - return 'index'; - } - return $this->method; } // -------------------------------------------------------------------- /** - * Set the directory name + * Set directory name * - * @access public - * @param string + * @param string $dir Directory name + * @param bool $append Whether we're appending rather than setting the full value * @return void */ - function set_directory($dir) + public function set_directory($dir, $append = FALSE) { - $this->directory = str_replace(array('/', '.'), '', $dir).'/'; + if ($append !== TRUE OR empty($this->directory)) + { + $this->directory = str_replace('.', '', trim($dir, '/')).'/'; + } + else + { + $this->directory .= str_replace('.', '', trim($dir, '/')).'/'; + } } // -------------------------------------------------------------------- /** - * Fetch the sub-directory (if any) that contains the requested controller class + * Fetch directory * - * @access public + * Feches the sub-directory (if any) that contains the requested + * controller class. + * + * @deprecated 3.0.0 Read the 'directory' property instead * @return string */ - function fetch_directory() + public 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/src/system/core/Security.php b/src/system/core/Security.php index 4c265d4d..d5305d1c 100644 --- a/src/system/core/Security.php +++ b/src/system/core/Security.php @@ -1,20 +1,41 @@ -', '<', '>', + "'", '"', '&', '$', '#', + '{', '}', '[', ']', '=', + ';', '?', '%20', '%22', + '%3c', // < + '%253c', // < + '%3e', // > + '%0e', // > + '%28', // ( + '%29', // ) + '%2528', // ( + '%26', // & + '%24', // $ + '%3f', // ? + '%3b', // ; + '%3d' // = + ); + /** - * Random Hash for Cross Site Request Forgery Protection Cookie + * Character set * - * @var string - * @access protected + * Will be overridden by the constructor. + * + * @var string */ - protected $_csrf_hash = ''; + public $charset = 'UTF-8'; + /** - * Expiration time for Cross Site Request Forgery Protection Cookie - * Defaults to two hours (in seconds) + * XSS Hash * - * @var int - * @access protected + * Random Hash for protecting URLs. + * + * @var string */ - protected $_csrf_expire = 7200; + protected $_xss_hash; + /** - * Token name for Cross Site Request Forgery Protection Cookie + * CSRF Hash * - * @var string - * @access protected + * Random hash for Cross Site Request Forgery protection cookie + * + * @var string */ - protected $_csrf_token_name = 'ci_csrf_token'; + protected $_csrf_hash; + /** - * Cookie name for Cross Site Request Forgery Protection Cookie + * CSRF Expire time * - * @var string - * @access protected + * Expiration time for Cross Site Request Forgery protection cookie. + * Defaults to two hours (in seconds). + * + * @var int */ - protected $_csrf_cookie_name = 'ci_csrf_token'; + protected $_csrf_expire = 7200; + + /** + * CSRF Token name + * + * Token name for Cross Site Request Forgery protection cookie. + * + * @var string + */ + protected $_csrf_token_name = 'ci_csrf_token'; + + /** + * CSRF Cookie name + * + * Cookie name for Cross Site Request Forgery protection cookie. + * + * @var string + */ + protected $_csrf_cookie_name = 'ci_csrf_token'; + /** * List of never allowed strings * - * @var array - * @access protected + * @var array */ - protected $_never_allowed_str = array( + protected $_never_allowed_str = array( 'document.cookie' => '[removed]', 'document.write' => '[removed]', '.parentNode' => '[removed]', @@ -81,12 +144,10 @@ class CI_Security { '' => '<comment>' ); - /* never allowed, regex replacement */ /** - * List of never allowed regex replacement + * List of never allowed regex replacements * - * @var array - * @access protected + * @var array */ protected $_never_allowed_regex = array( 'javascript\s*:', @@ -96,48 +157,50 @@ class CI_Security { 'wscript\s*:', // IE 'jscript\s*:', // IE 'vbs\s*:', // IE - 'Redirect\s+30\d:', + 'Redirect\s+30\d', "([\"'])?data\s*:[^\\1]*?base64[^\\1]*?,[^\\1]*?\\1?" ); /** - * Constructor + * Class constructor * * @return void */ public function __construct() { // Is CSRF protection enabled? - if (config_item('csrf_protection') === TRUE) + if (config_item('csrf_protection')) { // CSRF config foreach (array('csrf_expire', 'csrf_token_name', 'csrf_cookie_name') as $key) { - if (FALSE !== ($val = config_item($key))) + if (NULL !== ($val = config_item($key))) { $this->{'_'.$key} = $val; } } // Append application specific cookie prefix - if (config_item('cookie_prefix')) + if ($cookie_prefix = config_item('cookie_prefix')) { - $this->_csrf_cookie_name = config_item('cookie_prefix').$this->_csrf_cookie_name; + $this->_csrf_cookie_name = $cookie_prefix.$this->_csrf_cookie_name; } // Set the CSRF hash $this->_csrf_set_hash(); } - log_message('debug', "Security Class Initialized"); + $this->charset = strtoupper(config_item('charset')); + + log_message('info', 'Security Class Initialized'); } // -------------------------------------------------------------------- /** - * Verify Cross Site Request Forgery Protection + * CSRF Verify * - * @return object + * @return CI_Security */ public function csrf_verify() { @@ -147,52 +210,72 @@ class CI_Security { return $this->csrf_set_cookie(); } + // Check if URI has been whitelisted from CSRF checks + if ($exclude_uris = config_item('csrf_exclude_uris')) + { + $uri = load_class('URI', 'core'); + foreach ($exclude_uris as $excluded) + { + if (preg_match('#^'.$excluded.'$#i'.(UTF8_ENABLED ? 'u' : ''), $uri->uri_string())) + { + return $this; + } + } + } + // Do the tokens exist in both the _POST and _COOKIE arrays? - if ( ! isset($_POST[$this->_csrf_token_name], $_COOKIE[$this->_csrf_cookie_name])) + if ( ! isset($_POST[$this->_csrf_token_name], $_COOKIE[$this->_csrf_cookie_name]) + OR $_POST[$this->_csrf_token_name] !== $_COOKIE[$this->_csrf_cookie_name]) // Do the tokens match? { $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 + // 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]); + // Regenerate on every submission? + if (config_item('csrf_regenerate')) + { + // Nothing should last forever + unset($_COOKIE[$this->_csrf_cookie_name]); + $this->_csrf_hash = NULL; + } + $this->_csrf_set_hash(); $this->csrf_set_cookie(); - log_message('debug', 'CSRF token verified'); - + log_message('info', 'CSRF token verified'); return $this; } // -------------------------------------------------------------------- /** - * Set Cross Site Request Forgery Protection Cookie + * CSRF Set Cookie * - * @return object + * @codeCoverageIgnore + * @return CI_Security */ public function csrf_set_cookie() { $expire = time() + $this->_csrf_expire; - $secure_cookie = (config_item('cookie_secure') === TRUE) ? 1 : 0; + $secure_cookie = (bool) config_item('cookie_secure'); - if ($secure_cookie && (empty($_SERVER['HTTPS']) OR strtolower($_SERVER['HTTPS']) === 'off')) + if ($secure_cookie && ! is_https()) { return FALSE; } - setcookie($this->_csrf_cookie_name, $this->_csrf_hash, $expire, config_item('cookie_path'), config_item('cookie_domain'), $secure_cookie); - - log_message('debug', "CRSF cookie Set"); + setcookie( + $this->_csrf_cookie_name, + $this->_csrf_hash, + $expire, + config_item('cookie_path'), + config_item('cookie_domain'), + $secure_cookie, + config_item('cookie_httponly') + ); + log_message('info', 'CSRF cookie sent'); return $this; } @@ -206,7 +289,7 @@ class CI_Security { */ public function csrf_show_error() { - show_error('The action you have requested is not allowed.'); + show_error('The action you have requested is not allowed.', 403); } // -------------------------------------------------------------------- @@ -214,9 +297,8 @@ class CI_Security { /** * Get CSRF Hash * - * Getter Method - * - * @return string self::_csrf_hash + * @see CI_Security::$_csrf_hash + * @return string CSRF hash */ public function get_csrf_hash() { @@ -228,9 +310,8 @@ class CI_Security { /** * Get CSRF Token Name * - * Getter Method - * - * @return string self::csrf_token_name + * @see CI_Security::$_csrf_token_name + * @return string CSRF token name */ public function get_csrf_token_name() { @@ -243,26 +324,26 @@ class CI_Security { * XSS Clean * * Sanitizes data so that Cross Site Scripting Hacks can be - * prevented. This function does a fair amount of work but + * prevented. This method does a fair amount of work but * it is extremely thorough, designed to prevent even the * most obscure XSS attempts. Nothing is ever 100% foolproof, * of course, but I haven't been able to get anything passed * the filter. * - * Note: This function should only be used to deal with data - * upon submission. It's not something that should - * be used for general runtime processing. + * Note: 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 + * @link http://channel.bitflux.ch/wiki/XSS_Prevention + * Based in part on some code and ideas from Bitflux. * - * 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 + * @link http://ha.ckers.org/xss.html + * To help develop this script I used this great list of + * vulnerabilities along with a few other hacks I've + * harvested from examining vulnerabilities in other programs. * - * @param mixed string or array - * @param bool + * @param string|string[] $str Input data + * @param bool $is_image Whether the input is an image * @return string */ public function xss_clean($str, $is_image = FALSE) @@ -278,7 +359,7 @@ class CI_Security { return $str; } - //Remove Invisible Characters + // Remove Invisible Characters $str = remove_invisible_characters($str); /* @@ -355,7 +436,7 @@ class CI_Security { $words = array( 'javascript', 'expression', 'vbscript', 'jscript', 'wscript', 'vbs', 'script', 'base64', 'applet', 'alert', 'document', - 'write', 'cookie', 'window', 'confirm', 'prompt' + 'write', 'cookie', 'window', 'confirm', 'prompt', 'eval' ); foreach ($words as $word) @@ -398,13 +479,9 @@ class CI_Security { $str = preg_replace('##si', '[removed]', $str); } } - while($original !== $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 * @@ -414,20 +491,41 @@ class CI_Security { * So this: * Becomes: <blink> */ - $naughty = 'alert|prompt|confirm|applet|audio|basefont|base|behavior|bgsound|blink|body|embed|expression|form|frameset|frame|head|html|ilayer|iframe|input|button|select|isindex|layer|link|meta|keygen|object|plaintext|style|script|textarea|title|math|video|svg|xml|xss'; - $str = preg_replace_callback('#<(/*\s*)('.$naughty.')([^><]*)([><]*)#is', array($this, '_sanitize_naughty_html'), $str); + $pattern = '#' + .'<((?/*\s*)(?[a-z0-9]+)(?=[^a-z0-9]|$)' // tag start and name, followed by a non-tag character + .'[^\s\042\047a-z0-9>/=]*' // a valid attribute character immediately after the tag would count as a separator + // optional attributes + .'(?(?:[\s\042\047/=]*' // non-attribute characters, excluding > (tag close) for obvious reasons + .'[^\s\042\047>/=]+' // attribute characters + // optional attribute-value + .'(?:\s*=' // attribute-value separator + .'(?:[^\s\042\047=><`]+|\s*\042[^\042]*\042|\s*\047[^\047]*\047|\s*(?U:[^\s\042\047=><`]*))' // single, double or non-quoted value + .')?' // end optional attribute-value group + .')*)' // end optional attributes group + .'[^>]*)(?\>)?#isS'; + + // Note: It would be nice to optimize this for speed, BUT + // only matching the naughty elements here results in + // false positives and in turn - vulnerabilities! + do + { + $old_str = $str; + $str = preg_replace_callback($pattern, array($this, '_sanitize_naughty_html'), $str); + } + while ($old_str !== $str); + unset($old_str); /* * Sanitize naughty scripting elements * * Similar to above, only instead of looking for * tags it looks for PHP and JavaScript commands - * that are disallowed. Rather than removing the + * 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') + * Becomes: eval('some code') */ $str = preg_replace( '#(alert|prompt|confirm|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)\((.*?)\)#si', @@ -449,29 +547,32 @@ class CI_Security { * 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); } - log_message('debug', "XSS Filtering completed"); return $str; } // -------------------------------------------------------------------- /** - * Random Hash for protecting URLs + * XSS Hash * - * @return string + * Generates the XSS hash if needed and returns it. + * + * @see CI_Security::$_xss_hash + * @return string XSS hash */ public function xss_hash() { - if ($this->_xss_hash == '') + if ($this->_xss_hash === NULL) { - mt_srand(); - $this->_xss_hash = md5(time() + mt_rand(0, 1999999999)); + $rand = $this->get_random_bytes(16); + $this->_xss_hash = ($rand === FALSE) + ? md5(uniqid(mt_rand(), TRUE)) + : bin2hex($rand); } return $this->_xss_hash; @@ -479,22 +580,82 @@ class CI_Security { // -------------------------------------------------------------------- + /** + * Get random bytes + * + * @param int $length Output length + * @return string + */ + public function get_random_bytes($length) + { + if (empty($length) OR ! ctype_digit((string) $length)) + { + return FALSE; + } + + if (function_exists('random_bytes')) + { + try + { + // The cast is required to avoid TypeError + return random_bytes((int) $length); + } + catch (Exception $e) + { + // If random_bytes() can't do the job, we can't either ... + // There's no point in using fallbacks. + log_message('error', $e->getMessage()); + return FALSE; + } + } + + // Unfortunately, none of the following PRNGs is guaranteed to exist ... + if (defined('MCRYPT_DEV_URANDOM') && ($output = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM)) !== FALSE) + { + return $output; + } + + + if (is_readable('/dev/urandom') && ($fp = fopen('/dev/urandom', 'rb')) !== FALSE) + { + // Try not to waste entropy ... + is_php('5.4') && stream_set_chunk_size($fp, $length); + $output = fread($fp, $length); + fclose($fp); + if ($output !== FALSE) + { + return $output; + } + } + + if (function_exists('openssl_random_pseudo_bytes')) + { + return openssl_random_pseudo_bytes($length); + } + + return FALSE; + } + + // -------------------------------------------------------------------- + /** * HTML Entities Decode * - * This function is a replacement for html_entity_decode() + * A replacement for html_entity_decode() * * The reason we are not using html_entity_decode() by itself is because * while it is not technically correct to leave out the semicolon * at the end of an entity most browsers will still interpret the entity - * correctly. html_entity_decode() does not convert entities without + * 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 + * @link http://php.net/html-entity-decode + * + * @param string $str Input + * @param string $charset Character set * @return string */ - public function entity_decode($str, $charset='UTF-8') + public function entity_decode($str, $charset = NULL) { if (strpos($str, '&') === FALSE) { @@ -503,7 +664,7 @@ class CI_Security { static $_entities; - isset($charset) OR $charset = strtoupper(config_item('charset')); + isset($charset) OR $charset = $this->charset; $flag = is_php('5.4') ? ENT_COMPAT | ENT_HTML5 : ENT_COMPAT; @@ -513,7 +674,7 @@ class CI_Security { $str_compare = $str; // Decode standard entities, avoiding false positives - if (preg_match_all('/\&[a-z]{2,}(?![a-z;])/i', $str, $matches)) + if (preg_match_all('/&[a-z]{2,}(?![a-z;])/i', $str, $matches)) { if ( ! isset($_entities)) { @@ -563,32 +724,15 @@ class CI_Security { // -------------------------------------------------------------------- /** - * Filename Security + * Sanitize Filename * - * @param string - * @param bool + * @param string $str Input file name + * @param bool $relative_path Whether to preserve paths * @return string */ public function sanitize_filename($str, $relative_path = FALSE) { - $bad = array( - '../', '', '<', '>', - "'", '"', '&', '$', '#', - '{', '}', '[', ']', '=', - ';', '?', '%20', '%22', - '%3c', // < - '%253c', // < - '%3e', // > - '%0e', // > - '%28', // ( - '%29', // ) - '%2528', // ( - '%26', // & - '%24', // $ - '%3f', // ? - '%3b', // ; - '%3d' // = - ); + $bad = $this->filename_bad_chars; if ( ! $relative_path) { @@ -610,14 +754,35 @@ class CI_Security { // ---------------------------------------------------------------- + /** + * Strip Image Tags + * + * @param string $str + * @return string + */ + public function strip_image_tags($str) + { + return preg_replace( + array( + '##i', + '#`]+)).*?\>#i' + ), + '\\2', + $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 + * Callback method for xss_clean() to remove whitespace from + * things like 'j a v a s c r i p t'. * - * @param type - * @return type + * @used-by CI_Security::xss_clean() + * @param array $matches + * @return string */ protected function _compact_exploded_words($matches) { @@ -626,82 +791,93 @@ class CI_Security { // -------------------------------------------------------------------- - /* - * 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', 'form', 'xlink:href', 'FSCommand', 'seekSegmentTime'); - - 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 with quotes (042 and 047 are octal quotes) - preg_match_all('/(?]*)/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$2 $4$6$7$8', $str, -1, $count); - } - - } - while ($count); - - return $str; - } - - // -------------------------------------------------------------------- - /** * Sanitize Naughty HTML * - * Callback function for xss_clean() to remove naughty HTML elements + * Callback method for xss_clean() to remove naughty HTML elements. * - * @param array + * @used-by CI_Security::xss_clean() + * @param array $matches * @return string */ protected function _sanitize_naughty_html($matches) { - return '<'.$matches[1].$matches[2].$matches[3] // encode opening brace - // encode captured opening or closing brace to prevent recursive vectors: - .str_replace(array('>', '<'), array('>', '<'), $matches[4]); + static $naughty_tags = array( + 'alert', 'prompt', 'confirm', 'applet', 'audio', 'basefont', 'base', 'behavior', 'bgsound', + 'blink', 'body', 'embed', 'expression', 'form', 'frameset', 'frame', 'head', 'html', 'ilayer', + 'iframe', 'input', 'button', 'select', 'isindex', 'layer', 'link', 'meta', 'keygen', 'object', + 'plaintext', 'style', 'script', 'textarea', 'title', 'math', 'video', 'svg', 'xml', 'xss' + ); + + static $evil_attributes = array( + 'on\w+', 'style', 'xmlns', 'formaction', 'form', 'xlink:href', 'FSCommand', 'seekSegmentTime' + ); + + // First, escape unclosed tags + if (empty($matches['closeTag'])) + { + return '<'.$matches[1]; + } + // Is the element that we caught naughty? If so, escape it + elseif (in_array(strtolower($matches['tagName']), $naughty_tags, TRUE)) + { + return '<'.$matches[1].'>'; + } + // For other tags, see if their attributes are "evil" and strip those + elseif (isset($matches['attributes'])) + { + // We'll store the already fitlered attributes here + $attributes = array(); + + // Attribute-catching pattern + $attributes_pattern = '#' + .'(?[^\s\042\047>/=]+)' // attribute characters + // optional attribute-value + .'(?:\s*=(?[^\s\042\047=><`]+|\s*\042[^\042]*\042|\s*\047[^\047]*\047|\s*(?U:[^\s\042\047=><`]*)))' // attribute-value separator + .'#i'; + + // Blacklist pattern for evil attribute names + $is_evil_pattern = '#^('.implode('|', $evil_attributes).')$#i'; + + // Each iteration filters a single attribute + do + { + // Strip any non-alpha characters that may preceed an attribute. + // Browsers often parse these incorrectly and that has been a + // of numerous XSS issues we've had. + $matches['attributes'] = preg_replace('#^[^a-z]+#i', '', $matches['attributes']); + + if ( ! preg_match($attributes_pattern, $matches['attributes'], $attribute, PREG_OFFSET_CAPTURE)) + { + // No (valid) attribute found? Discard everything else inside the tag + break; + } + + if ( + // Is it indeed an "evil" attribute? + preg_match($is_evil_pattern, $attribute['name'][0]) + // Or does it have an equals sign, but no value and not quoted? Strip that too! + OR (trim($attribute['value'][0]) === '') + ) + { + $attributes[] = 'xss=removed'; + } + else + { + $attributes[] = $attribute[0][0]; + } + + $matches['attributes'] = substr($matches['attributes'], $attribute[0][1] + strlen($attribute[0][0])); + } + while ($matches['attributes'] !== ''); + + $attributes = empty($attributes) + ? '' + : ' '.implode(' ', $attributes); + return '<'.$matches['slash'].$matches['tagName'].$attributes.'>'; + } + + return $matches[0]; } // -------------------------------------------------------------------- @@ -709,12 +885,14 @@ class CI_Security { /** * JS Link Removal * - * Callback function for xss_clean() to sanitize links + * Callback method for xss_clean() to sanitize links. + * * This limits the PCRE backtracks, making it more performance friendly * and prevents PREG_BACKTRACK_LIMIT_ERROR from being triggered in - * PHP 5.2+ on link-heavy strings + * PHP 5.2+ on link-heavy strings. * - * @param array + * @used-by CI_Security::xss_clean() + * @param array $match * @return string */ protected function _js_link_removal($match) @@ -724,7 +902,7 @@ class CI_Security { preg_replace( '#href=.*?(?:(?:alert|prompt|confirm)(?:\(|&\#40;)|javascript:|livescript:|mocha:|charset=|window\.|document\.|\.cookie|_filter_attributes(str_replace(array('<', '>'), '', $match[1])) + $this->_filter_attributes($match[1]) ), $match[0] ); @@ -735,12 +913,14 @@ class CI_Security { /** * JS Image Removal * - * Callback function for xss_clean() to sanitize image tags + * Callback method for xss_clean() to sanitize image tags. + * * This limits the PCRE backtracks, making it more performance friendly * and prevents PREG_BACKTRACK_LIMIT_ERROR from being triggered in - * PHP 5.2+ on image tag heavy strings + * PHP 5.2+ on image tag heavy strings. * - * @param array + * @used-by CI_Security::xss_clean() + * @param array $match * @return string */ protected function _js_img_removal($match) @@ -748,9 +928,9 @@ class CI_Security { return str_replace( $match[1], preg_replace( - '#src=.*?(?:(?:alert|prompt|confirm)(?:\(|&\#40;)|javascript:|livescript:|mocha:|charset=|window\.|document\.|\.cookie|_filter_attributes(str_replace(array('<', '>'), '', $match[1])) + $this->_filter_attributes($match[1]) ), $match[0] ); @@ -761,9 +941,8 @@ class CI_Security { /** * Attribute Conversion * - * Used as a callback for XSS Clean - * - * @param array + * @used-by CI_Security::xss_clean() + * @param array $match * @return string */ protected function _convert_attribute($match) @@ -776,20 +955,21 @@ class CI_Security { /** * Filter Attributes * - * Filters tag attributes for consistency and safety + * Filters tag attributes for consistency and safety. * - * @param string + * @used-by CI_Security::_js_img_removal() + * @used-by CI_Security::_js_link_removal() + * @param string $str * @return string */ protected function _filter_attributes($str) { $out = ''; - if (preg_match_all('#\s*[a-z\-]+\s*=\s*(\042|\047)([^\\1]*?)\\1#is', $str, $matches)) { foreach ($matches[0] as $match) { - $out .= preg_replace("#/\*.*?\*/#s", '', $match); + $out .= preg_replace('#/\*.*?\*/#s', '', $match); } } @@ -801,9 +981,8 @@ class CI_Security { /** * HTML Entity Decode Callback * - * Used as a callback for XSS Clean - * - * @param array + * @used-by CI_Security::xss_clean() + * @param array $match * @return string */ protected function _decode_entity($match) @@ -816,17 +995,16 @@ class CI_Security { return str_replace( $this->xss_hash(), '&', - $this->entity_decode($match, strtoupper(config_item('charset'))) + $this->entity_decode($match, $this->charset) ); } - // ---------------------------------------------------------------------- + // -------------------------------------------------------------------- /** * Do Never Allowed * - * A utility function for xss_clean() - * + * @used-by CI_Security::xss_clean() * @param string * @return string */ @@ -845,31 +1023,31 @@ class CI_Security { // -------------------------------------------------------------------- /** - * Set Cross Site Request Forgery Protection Cookie + * Set CSRF Hash and Cookie * * @return string */ protected function _csrf_set_hash() { - if ($this->_csrf_hash == '') + if ($this->_csrf_hash === NULL) { - // If the cookie exists we will use it's value. + // If the cookie exists we will use its value. // We don't necessarily want to regenerate it with // each page load since a page could contain embedded // sub-pages causing this feature to fail - if (isset($_COOKIE[$this->_csrf_cookie_name]) && - preg_match('#^[0-9a-f]{32}$#iS', $_COOKIE[$this->_csrf_cookie_name]) === 1) + if (isset($_COOKIE[$this->_csrf_cookie_name]) && is_string($_COOKIE[$this->_csrf_cookie_name]) + && preg_match('#^[0-9a-f]{32}$#iS', $_COOKIE[$this->_csrf_cookie_name]) === 1) { return $this->_csrf_hash = $_COOKIE[$this->_csrf_cookie_name]; } - return $this->_csrf_hash = md5(uniqid(rand(), TRUE)); + $rand = $this->get_random_bytes(16); + $this->_csrf_hash = ($rand === FALSE) + ? md5(uniqid(mt_rand(), TRUE)) + : bin2hex($rand); } return $this->_csrf_hash; } } - -/* End of file Security.php */ -/* Location: ./system/core/Security.php */ \ No newline at end of file diff --git a/src/system/core/URI.php b/src/system/core/URI.php index a66cd715..544f6c85 100644 --- a/src/system/core/URI.php +++ b/src/system/core/URI.php @@ -1,20 +1,41 @@ -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') + // If query strings are enabled, we don't need to parse any segments. + // However, they don't make sense under CLI. + if (is_cli() OR $this->config->item('enable_query_strings') !== TRUE) { - // Is the request coming from the command line? - if (php_sapi_name() == 'cli' or defined('STDIN')) + $this->_permitted_uri_chars = $this->config->item('permitted_uri_chars'); + + // If it's a CLI request, ignore the configuration + if (is_cli()) { - $this->_set_uri_string($this->_parse_cli_args()); - return; + $uri = $this->_parse_argv(); + } + else + { + $protocol = $this->config->item('uri_protocol'); + empty($protocol) && $protocol = 'REQUEST_URI'; + + switch ($protocol) + { + case 'AUTO': // For BC purposes only + case 'REQUEST_URI': + $uri = $this->_parse_request_uri(); + break; + case 'QUERY_STRING': + $uri = $this->_parse_query_string(); + break; + case 'PATH_INFO': + default: + $uri = isset($_SERVER[$protocol]) + ? $_SERVER[$protocol] + : $this->_parse_request_uri(); + break; + } } - // 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; + $this->_set_uri_string($uri); } - $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); + log_message('info', 'URI Class Initialized'); } // -------------------------------------------------------------------- /** - * Set the URI String + * Set URI String * - * @access public - * @param string - * @return string + * @param string $str + * @return void */ - function _set_uri_string($str) + protected function _set_uri_string($str) { - // Filter out control characters - $str = remove_invisible_characters($str, FALSE); + // Filter out control characters and trim slashes + $this->uri_string = trim(remove_invisible_characters($str, FALSE), '/'); - // If the URI contains only a slash we'll kill it - $this->uri_string = ($str == '/') ? '' : $str; + if ($this->uri_string !== '') + { + // Remove the URL suffix, if present + if (($suffix = (string) $this->config->item('url_suffix')) !== '') + { + $slen = strlen($suffix); + + if (substr($this->uri_string, -$slen) === $suffix) + { + $this->uri_string = substr($this->uri_string, 0, -$slen); + } + } + + $this->segments[0] = NULL; + // Populate the segments array + foreach (explode('/', trim($this->uri_string, '/')) as $val) + { + $val = trim($val); + // Filter segments for security + $this->filter_uri($val); + + if ($val !== '') + { + $this->segments[] = $val; + } + } + + unset($this->segments[0]); + } } // -------------------------------------------------------------------- /** - * Detects the URI + * Parse REQUEST_URI * - * This function will detect the URI automatically and fix the query string - * if necessary. + * Will parse REQUEST_URI and automatically detect the URI from it, + * while fixing the query string if necessary. * - * @access private * @return string */ - private function _detect_uri() + protected function _parse_request_uri() { - if ( ! isset($_SERVER['REQUEST_URI']) OR ! isset($_SERVER['SCRIPT_NAME'])) + if ( ! isset($_SERVER['REQUEST_URI'], $_SERVER['SCRIPT_NAME'])) { return ''; } - $uri = $_SERVER['REQUEST_URI']; - if (strpos($uri, $_SERVER['SCRIPT_NAME']) === 0) + // parse_url() returns false if no host is present, but the path or query string + // contains a colon followed by a number + $uri = parse_url('http://dummy'.$_SERVER['REQUEST_URI']); + $query = isset($uri['query']) ? $uri['query'] : ''; + $uri = isset($uri['path']) ? $uri['path'] : ''; + + if (isset($_SERVER['SCRIPT_NAME'][0])) { - $uri = substr($uri, strlen($_SERVER['SCRIPT_NAME'])); - } - elseif (strpos($uri, dirname($_SERVER['SCRIPT_NAME'])) === 0) - { - $uri = substr($uri, strlen(dirname($_SERVER['SCRIPT_NAME']))); + if (strpos($uri, $_SERVER['SCRIPT_NAME']) === 0) + { + $uri = (string) substr($uri, strlen($_SERVER['SCRIPT_NAME'])); + } + elseif (strpos($uri, dirname($_SERVER['SCRIPT_NAME'])) === 0) + { + $uri = (string) substr($uri, strlen(dirname($_SERVER['SCRIPT_NAME']))); + } } // This section ensures that even on servers that require the URI to be in the query string (Nginx) a correct // URI is found, and also fixes the QUERY_STRING server var and $_GET array. - if (strncmp($uri, '?/', 2) === 0) + if (trim($uri, '/') === '' && strncmp($query, '/', 1) === 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); + $query = explode('?', $query, 2); + $uri = $query[0]; + $_SERVER['QUERY_STRING'] = isset($query[1]) ? $query[1] : ''; } else { - $_SERVER['QUERY_STRING'] = ''; - $_GET = array(); + $_SERVER['QUERY_STRING'] = $query; } - if ($uri == '/' || empty($uri)) + parse_str($_SERVER['QUERY_STRING'], $_GET); + + if ($uri === '/' OR $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, '/')); + return $this->_remove_relative_directory($uri); } // -------------------------------------------------------------------- /** - * Parse cli arguments + * Parse QUERY_STRING + * + * Will parse QUERY_STRING and automatically detect the URI from it. + * + * @return string + */ + protected function _parse_query_string() + { + $uri = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : @getenv('QUERY_STRING'); + + if (trim($uri, '/') === '') + { + return ''; + } + elseif (strncmp($uri, '/', 1) === 0) + { + $uri = explode('?', $uri, 2); + $_SERVER['QUERY_STRING'] = isset($uri[1]) ? $uri[1] : ''; + $uri = $uri[0]; + } + + parse_str($_SERVER['QUERY_STRING'], $_GET); + + return $this->_remove_relative_directory($uri); + } + + // -------------------------------------------------------------------- + + /** + * Parse CLI arguments * * Take each command line argument and assume it is a URI segment. * - * @access private * @return string */ - private function _parse_cli_args() + protected function _parse_argv() { $args = array_slice($_SERVER['argv'], 1); - - return $args ? '/' . implode('/', $args) : ''; + return $args ? implode('/', $args) : ''; } // -------------------------------------------------------------------- /** - * Filter segments for malicious characters + * Remove relative directory (../) and multi slashes (///) * - * @access private - * @param string + * Do some final cleaning of the URI and return it, currently only used in self::_parse_request_uri() + * + * @param string $uri * @return string */ - function _filter_uri($str) + protected function _remove_relative_directory($uri) { - if ($str != '' && $this->config->item('permitted_uri_chars') != '' && $this->config->item('enable_query_strings') == FALSE) + $uris = array(); + $tok = strtok($uri, '/'); + while ($tok !== 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)) + if (( ! empty($tok) OR $tok === '0') && $tok !== '..') { - show_error('The URI you submitted has disallowed characters.', 400); + $uris[] = $tok; } + $tok = strtok('/'); } - // Convert programatic characters to entities - $bad = array('$', '(', ')', '%28', '%29'); - $good = array('$', '(', ')', '(', ')'); - - return str_replace($bad, $good, $str); + return implode('/', $uris); } // -------------------------------------------------------------------- /** - * Remove the suffix from the URL if needed + * Filter URI * - * @access private + * Filters segments for malicious characters. + * + * @param string $str * @return void */ - function _remove_url_suffix() + public function filter_uri(&$str) { - if ($this->config->item('url_suffix') != "") + if ( ! empty($str) && ! empty($this->_permitted_uri_chars) && ! preg_match('/^['.$this->_permitted_uri_chars.']+$/i'.(UTF8_ENABLED ? 'u' : ''), $str)) { - $this->uri_string = preg_replace("|".preg_quote($this->config->item('url_suffix'))."$|", "", $this->uri_string); + show_error('The URI you submitted has disallowed characters.', 400); } } // -------------------------------------------------------------------- /** - * Explode the URI Segments. The individual segments will - * be stored in the $this->segments array. + * Fetch URI Segment * - * @access private - * @return void + * @see CI_URI::$segments + * @param int $n Index + * @param mixed $no_result What to return if the segment index is not found + * @return mixed */ - function _explode_segments() + public function segment($n, $no_result = NULL) { - 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]); + return isset($this->segments[$n]) ? $this->segments[$n] : $no_result; } // -------------------------------------------------------------------- /** - * Fetch a URI Segment + * Fetch URI "routed" Segment * - * This function returns the URI segment based on the number provided. + * Returns the re-routed URI segment (assuming routing rules are used) + * based on the index provided. If there is no routing, will return + * the same result as CI_URI::segment(). * - * @access public - * @param integer - * @param bool - * @return string + * @see CI_URI::$rsegments + * @see CI_URI::segment() + * @param int $n Index + * @param mixed $no_result What to return if the segment index is not found + * @return mixed */ - function segment($n, $no_result = FALSE) + public function rsegment($n, $no_result = NULL) { - return ( ! isset($this->segments[$n])) ? $no_result : $this->segments[$n]; + return isset($this->rsegments[$n]) ? $this->rsegments[$n] : $no_result; } // -------------------------------------------------------------------- /** - * Fetch a URI "routed" Segment + * URI to assoc * - * 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: + * Generates an associative array of URI data starting at the supplied + * segment index. For example, if this is your URI: * * example.com/user/search/name/joe/location/UK/gender/male * - * You can use this function to generate an array with this prototype: + * You can use this method to generate an array with this prototype: * - * array ( - * name => joe - * location => UK - * gender => male - * ) + * array ( + * name => joe + * location => UK + * gender => male + * ) * - * @access public - * @param integer the starting segment number - * @param array an array of default values + * @param int $n Index (default: 3) + * @param array $default Default values * @return array */ - function uri_to_assoc($n = 3, $default = array()) + public 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 + * Routed URI to assoc * - * @access public - * @param integer the starting segment number - * @param array an array of default values + * Identical to CI_URI::uri_to_assoc(), only it uses the re-routed + * segment array. + * + * @see CI_URI::uri_to_assoc() + * @param int $n Index (default: 3) + * @param array $default Default values * @return array - * */ - function ruri_to_assoc($n = 3, $default = array()) + public function ruri_to_assoc($n = 3, $default = array()) { return $this->_uri_to_assoc($n, $default, 'rsegment'); } @@ -407,57 +414,43 @@ class CI_URI { // -------------------------------------------------------------------- /** - * Generate a key value pair from the URI string or Re-routed URI string + * Internal URI-to-assoc * - * @access private - * @param integer the starting segment number - * @param array an array of default values - * @param string which array we should use + * Generates a key/value pair from the URI string or re-routed URI string. + * + * @used-by CI_URI::uri_to_assoc() + * @used-by CI_URI::ruri_to_assoc() + * @param int $n Index (default: 3) + * @param array $default Default values + * @param string $which Array name ('segment' or 'rsegment') * @return array */ - function _uri_to_assoc($n = 3, $default = array(), $which = 'segment') + protected 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])) + if (isset($this->keyval[$which], $this->keyval[$which][$n])) { - return $this->keyval[$n]; + return $this->keyval[$which][$n]; } + $total_segments = "total_{$which}s"; + $segment_array = "{$which}_array"; + if ($this->$total_segments() < $n) { - if (count($default) == 0) - { - return array(); - } - - $retval = array(); - foreach ($default as $val) - { - $retval[$val] = FALSE; - } - return $retval; + return (count($default) === 0) + ? array() + : array_fill_keys($default, NULL); } $segments = array_slice($this->$segment_array(), ($n - 1)); - $i = 0; $lastval = ''; - $retval = array(); + $retval = array(); foreach ($segments as $seg) { if ($i % 2) @@ -466,7 +459,7 @@ class CI_URI { } else { - $retval[$seg] = FALSE; + $retval[$seg] = NULL; $lastval = $seg; } @@ -479,30 +472,31 @@ class CI_URI { { if ( ! array_key_exists($val, $retval)) { - $retval[$val] = FALSE; + $retval[$val] = NULL; } } } // Cache the array for reuse - $this->keyval[$n] = $retval; + isset($this->keyval[$which]) OR $this->keyval[$which] = array(); + $this->keyval[$which][$n] = $retval; return $retval; } // -------------------------------------------------------------------- /** - * Generate a URI string from an associative array + * Assoc to URI * + * Generates a URI string from an associative array. * - * @access public - * @param array an associative array of key/values - * @return array + * @param array $array Input array of key/value pairs + * @return string URI string */ - function assoc_to_uri($array) + public function assoc_to_uri($array) { $temp = array(); - foreach ((array)$array as $key => $val) + foreach ((array) $array as $key => $val) { $temp[] = $key; $temp[] = $val; @@ -514,14 +508,15 @@ class CI_URI { // -------------------------------------------------------------------- /** - * Fetch a URI Segment and add a trailing slash + * Slash segment * - * @access public - * @param integer - * @param string + * Fetches an URI segment with a slash. + * + * @param int $n Index + * @param string $where Where to add the slash ('trailing' or 'leading') * @return string */ - function slash_segment($n, $where = 'trailing') + public function slash_segment($n, $where = 'trailing') { return $this->_slash_segment($n, $where, 'segment'); } @@ -529,14 +524,15 @@ class CI_URI { // -------------------------------------------------------------------- /** - * Fetch a URI Segment and add a trailing slash + * Slash routed segment * - * @access public - * @param integer - * @param string + * Fetches an URI routed segment with a slash. + * + * @param int $n Index + * @param string $where Where to add the slash ('trailing' or 'leading') * @return string */ - function slash_rsegment($n, $where = 'trailing') + public function slash_rsegment($n, $where = 'trailing') { return $this->_slash_segment($n, $where, 'rsegment'); } @@ -544,24 +540,27 @@ class CI_URI { // -------------------------------------------------------------------- /** - * Fetch a URI Segment and add a trailing slash - helper function + * Internal Slash segment * - * @access private - * @param integer - * @param string - * @param string + * Fetches an URI Segment and adds a slash to it. + * + * @used-by CI_URI::slash_segment() + * @used-by CI_URI::slash_rsegment() + * + * @param int $n Index + * @param string $where Where to add the slash ('trailing' or 'leading') + * @param string $which Array name ('segment' or 'rsegment') * @return string */ - function _slash_segment($n, $where = 'trailing', $which = 'segment') + protected function _slash_segment($n, $where = 'trailing', $which = 'segment') { - $leading = '/'; - $trailing = '/'; + $leading = $trailing = '/'; - if ($where == 'trailing') + if ($where === 'trailing') { $leading = ''; } - elseif ($where == 'leading') + elseif ($where === 'leading') { $trailing = ''; } @@ -574,10 +573,9 @@ class CI_URI { /** * Segment Array * - * @access public - * @return array + * @return array CI_URI::$segments */ - function segment_array() + public function segment_array() { return $this->segments; } @@ -587,10 +585,9 @@ class CI_URI { /** * Routed Segment Array * - * @access public - * @return array + * @return array CI_URI::$rsegments */ - function rsegment_array() + public function rsegment_array() { return $this->rsegments; } @@ -600,10 +597,9 @@ class CI_URI { /** * Total number of segments * - * @access public - * @return integer + * @return int */ - function total_segments() + public function total_segments() { return count($this->segments); } @@ -613,10 +609,9 @@ class CI_URI { /** * Total number of routed segments * - * @access public - * @return integer + * @return int */ - function total_rsegments() + public function total_rsegments() { return count($this->rsegments); } @@ -624,32 +619,25 @@ class CI_URI { // -------------------------------------------------------------------- /** - * Fetch the entire URI string + * Fetch URI string * - * @access public - * @return string + * @return string CI_URI::$uri_string */ - function uri_string() + public function uri_string() { return $this->uri_string; } - // -------------------------------------------------------------------- /** - * Fetch the entire Re-routed URI string + * Fetch Re-routed URI string * - * @access public * @return string */ - function ruri_string() + public function ruri_string() { - return '/'.implode('/', $this->rsegment_array()); + return ltrim(load_class('Router', 'core')->directory, '/').implode('/', $this->rsegments); } } -// END URI Class - -/* End of file URI.php */ -/* Location: ./system/core/URI.php */ \ No newline at end of file diff --git a/src/system/core/Utf8.php b/src/system/core/Utf8.php index efb78ce1..f2f42e6c 100644 --- a/src/system/core/Utf8.php +++ b/src/system/core/Utf8.php @@ -1,20 +1,41 @@ -item('charset') == 'UTF-8' // Application charset must be UTF-8 + defined('PREG_BAD_UTF8_ERROR') // PCRE must support UTF-8 + && (ICONV_ENABLED === TRUE OR MB_ENABLED === TRUE) // iconv or mbstring must be installed + && strtoupper(config_item('charset')) === 'UTF-8' // Application charset must be UTF-8 ) { - 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); - } + log_message('debug', 'UTF-8 Support Enabled'); } else { - log_message('debug', "UTF-8 Support Disabled"); define('UTF8_ENABLED', FALSE); + log_message('debug', 'UTF-8 Support Disabled'); } + + log_message('info', 'Utf8 Class Initialized'); } // -------------------------------------------------------------------- @@ -77,17 +82,23 @@ class CI_Utf8 { /** * Clean UTF-8 strings * - * Ensures strings are UTF-8 + * Ensures strings contain only valid UTF-8 characters. * - * @access public - * @param string + * @param string $str String to clean * @return string */ - function clean_string($str) + public function clean_string($str) { - if ($this->_is_ascii($str) === FALSE) + if ($this->is_ascii($str) === FALSE) { - $str = @iconv('UTF-8', 'UTF-8//IGNORE', $str); + if (MB_ENABLED) + { + $str = mb_convert_encoding($str, 'UTF-8', 'UTF-8'); + } + elseif (ICONV_ENABLED) + { + $str = @iconv('UTF-8', 'UTF-8//IGNORE', $str); + } } return $str; @@ -100,13 +111,12 @@ class CI_Utf8 { * * Removes all ASCII control characters except horizontal tabs, * line feeds, and carriage returns, as all others can cause - * problems in XML + * problems in XML. * - * @access public - * @param string + * @param string $str String to clean * @return string */ - function safe_ascii_for_xml($str) + public function safe_ascii_for_xml($str) { return remove_invisible_characters($str, FALSE); } @@ -116,29 +126,24 @@ class CI_Utf8 { /** * Convert to UTF-8 * - * Attempts to convert a string to UTF-8 + * Attempts to convert a string to UTF-8. * - * @access public - * @param string - * @param string - input encoding - * @return string + * @param string $str Input string + * @param string $encoding Input encoding + * @return string $str encoded in UTF-8 or FALSE on failure */ - function convert_to_utf8($str, $encoding) + public function convert_to_utf8($str, $encoding) { - if (function_exists('iconv')) + if (MB_ENABLED) { - $str = @iconv($encoding, 'UTF-8', $str); + return mb_convert_encoding($str, 'UTF-8', $encoding); } - elseif (function_exists('mb_convert_encoding')) + elseif (ICONV_ENABLED) { - $str = @mb_convert_encoding($str, 'UTF-8', $encoding); - } - else - { - return FALSE; + return @iconv($encoding, 'UTF-8', $str); } - return $str; + return FALSE; } // -------------------------------------------------------------------- @@ -146,21 +151,14 @@ class CI_Utf8 { /** * Is ASCII? * - * Tests if a string is standard 7-bit ASCII or not + * Tests if a string is standard 7-bit ASCII or not. * - * @access public - * @param string + * @param string $str String to check * @return bool */ - function _is_ascii($str) + public function is_ascii($str) { - return (preg_match('/[^\x00-\x7F]/S', $str) == 0); + 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/src/system/core/compat/hash.php b/src/system/core/compat/hash.php new file mode 100644 index 00000000..6854e4c2 --- /dev/null +++ b/src/system/core/compat/hash.php @@ -0,0 +1,245 @@ + 32, + 'haval128,3' => 128, + 'haval160,3' => 128, + 'haval192,3' => 128, + 'haval224,3' => 128, + 'haval256,3' => 128, + 'haval128,4' => 128, + 'haval160,4' => 128, + 'haval192,4' => 128, + 'haval224,4' => 128, + 'haval256,4' => 128, + 'haval128,5' => 128, + 'haval160,5' => 128, + 'haval192,5' => 128, + 'haval224,5' => 128, + 'haval256,5' => 128, + 'md2' => 16, + 'md4' => 64, + 'md5' => 64, + 'ripemd128' => 64, + 'ripemd160' => 64, + 'ripemd256' => 64, + 'ripemd320' => 64, + 'salsa10' => 64, + 'salsa20' => 64, + 'sha1' => 64, + 'sha224' => 64, + 'sha256' => 64, + 'sha384' => 128, + 'sha512' => 128, + 'snefru' => 32, + 'snefru256' => 32, + 'tiger128,3' => 64, + 'tiger160,3' => 64, + 'tiger192,3' => 64, + 'tiger128,4' => 64, + 'tiger160,4' => 64, + 'tiger192,4' => 64, + 'whirlpool' => 64 + ); + + if (isset($block_sizes[$algo]) && strlen($password) > $block_sizes[$algo]) + { + $password = hash($algo, $password, TRUE); + } + + $hash = ''; + // Note: Blocks are NOT 0-indexed + for ($bc = ceil($length / $hash_length), $bi = 1; $bi <= $bc; $bi++) + { + $key = $derived_key = hash_hmac($algo, $salt.pack('N', $bi), $password, TRUE); + for ($i = 1; $i < $iterations; $i++) + { + $derived_key ^= $key = hash_hmac($algo, $key, $password, TRUE); + } + + $hash .= $derived_key; + } + + // This is not RFC-compatible, but we're aiming for natural PHP compatibility + return substr($raw_output ? $hash : bin2hex($hash), 0, $length); + } +} diff --git a/src/system/core/compat/index.html b/src/system/core/compat/index.html new file mode 100644 index 00000000..b702fbc3 --- /dev/null +++ b/src/system/core/compat/index.html @@ -0,0 +1,11 @@ + + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + diff --git a/src/system/core/compat/mbstring.php b/src/system/core/compat/mbstring.php new file mode 100644 index 00000000..554d1004 --- /dev/null +++ b/src/system/core/compat/mbstring.php @@ -0,0 +1,149 @@ + 0, 'algoName' => 'unknown', 'options' => array()) + : array('algo' => 1, 'algoName' => 'bcrypt', 'options' => array('cost' => $hash)); + } +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('password_hash')) +{ + /** + * password_hash() + * + * @link http://php.net/password_hash + * @param string $password + * @param int $algo + * @param array $options + * @return mixed + */ + function password_hash($password, $algo, array $options = array()) + { + static $func_override; + isset($func_override) OR $func_override = (extension_loaded('mbstring') && ini_get('mbstring.func_override')); + + if ($algo !== 1) + { + trigger_error('password_hash(): Unknown hashing algorithm: '.(int) $algo, E_USER_WARNING); + return NULL; + } + + if (isset($options['cost']) && ($options['cost'] < 4 OR $options['cost'] > 31)) + { + trigger_error('password_hash(): Invalid bcrypt cost parameter specified: '.(int) $options['cost'], E_USER_WARNING); + return NULL; + } + + if (isset($options['salt']) && ($saltlen = ($func_override ? mb_strlen($options['salt'], '8bit') : strlen($options['salt']))) < 22) + { + trigger_error('password_hash(): Provided salt is too short: '.$saltlen.' expecting 22', E_USER_WARNING); + return NULL; + } + elseif ( ! isset($options['salt'])) + { + if (defined('MCRYPT_DEV_URANDOM')) + { + $options['salt'] = mcrypt_create_iv(16, MCRYPT_DEV_URANDOM); + } + elseif (function_exists('openssl_random_pseudo_bytes')) + { + $options['salt'] = openssl_random_pseudo_bytes(16); + } + elseif (DIRECTORY_SEPARATOR === '/' && (is_readable($dev = '/dev/arandom') OR is_readable($dev = '/dev/urandom'))) + { + if (($fp = fopen($dev, 'rb')) === FALSE) + { + log_message('error', 'compat/password: Unable to open '.$dev.' for reading.'); + return FALSE; + } + + // Try not to waste entropy ... + is_php('5.4') && stream_set_chunk_size($fp, 16); + + $options['salt'] = ''; + for ($read = 0; $read < 16; $read = ($func_override) ? mb_strlen($options['salt'], '8bit') : strlen($options['salt'])) + { + if (($read = fread($fp, 16 - $read)) === FALSE) + { + log_message('error', 'compat/password: Error while reading from '.$dev.'.'); + return FALSE; + } + $options['salt'] .= $read; + } + + fclose($fp); + } + else + { + log_message('error', 'compat/password: No CSPRNG available.'); + return FALSE; + } + + $options['salt'] = str_replace('+', '.', rtrim(base64_encode($options['salt']), '=')); + } + elseif ( ! preg_match('#^[a-zA-Z0-9./]+$#D', $options['salt'])) + { + $options['salt'] = str_replace('+', '.', rtrim(base64_encode($options['salt']), '=')); + } + + isset($options['cost']) OR $options['cost'] = 10; + + return (strlen($password = crypt($password, sprintf('$2y$%02d$%s', $options['cost'], $options['salt']))) === 60) + ? $password + : FALSE; + } +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('password_needs_rehash')) +{ + /** + * password_needs_rehash() + * + * @link http://php.net/password_needs_rehash + * @param string $hash + * @param int $algo + * @param array $options + * @return bool + */ + function password_needs_rehash($hash, $algo, array $options = array()) + { + $info = password_get_info($hash); + + if ($algo !== $info['algo']) + { + return TRUE; + } + elseif ($algo === 1) + { + $options['cost'] = isset($options['cost']) ? (int) $options['cost'] : 10; + return ($info['options']['cost'] !== $options['cost']); + } + + // Odd at first glance, but according to a comment in PHP's own unit tests, + // because it is an unknown algorithm - it's valid and therefore doesn't + // need rehashing. + return FALSE; + } +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('password_verify')) +{ + /** + * password_verify() + * + * @link http://php.net/password_verify + * @param string $password + * @param string $hash + * @return bool + */ + function password_verify($password, $hash) + { + if (strlen($hash) !== 60 OR strlen($password = crypt($password, $hash)) !== 60) + { + return FALSE; + } + + $compare = 0; + for ($i = 0; $i < 60; $i++) + { + $compare |= (ord($password[$i]) ^ ord($hash[$i])); + } + + return ($compare === 0); + } +} diff --git a/src/system/core/compat/standard.php b/src/system/core/compat/standard.php new file mode 100644 index 00000000..47d47aef --- /dev/null +++ b/src/system/core/compat/standard.php @@ -0,0 +1,389 @@ + 0) + { + $output .= "\015".$str[$i++]; + $length--; + $lp = 0; + continue; + } + + if ( + ctype_cntrl($c) + OR (ord($c) === 0x7f) + OR (ord($c) & 0x80) + OR ($c === '=') + OR ($c === ' ' && isset($str[$i]) && $str[$i] === "\015") + ) + { + if ( + (($lp += 3) > 75 && ord($c) <= 0x7f) + OR (ord($c) > 0x7f && ord($c) <= 0xdf && ($lp + 3) > 75) + OR (ord($c) > 0xdf && ord($c) <= 0xef && ($lp + 6) > 75) + OR (ord($c) > 0xef && ord($c) <= 0xf4 && ($lp + 9) > 75) + ) + { + $output .= "=\015\012"; + $lp = 3; + } + + $output .= '='.$hex[ord($c) >> 4].$hex[ord($c) & 0xf]; + continue; + } + + if ((++$lp) > 75) + { + $output .= "=\015\012"; + $lp = 1; + } + + $output .= $c; + } + + return $output; + } +} diff --git a/src/system/core/index.html b/src/system/core/index.html index c942a79c..b702fbc3 100644 --- a/src/system/core/index.html +++ b/src/system/core/index.html @@ -1,3 +1,4 @@ + 403 Forbidden @@ -7,4 +8,4 @@

Directory access is forbidden.

- \ No newline at end of file + diff --git a/src/system/database/DB.php b/src/system/database/DB.php index bb3db3d1..b4b7767e 100644 --- a/src/system/database/DB.php +++ b/src/system/database/DB.php @@ -1,101 +1,141 @@ -load->get_package_paths() as $path) + { + if ($path !== APPPATH) + { + if (file_exists($file_path = $path.'config/'.ENVIRONMENT.'/database.php')) + { + include($file_path); + } + elseif (file_exists($file_path = $path.'config/database.php')) + { + include($file_path); + } + } + } + } + + if ( ! isset($db) OR count($db) === 0) { show_error('No database connection settings were found in the database config file.'); } - if ($params != '') + if ($params !== '') { $active_group = $params; } - if ( ! isset($active_group) OR ! isset($db[$active_group])) + if ( ! isset($active_group)) { - show_error('You have specified an invalid database connection group.'); + show_error('You have not specified a database connection group via $active_group in your config/database.php file.'); + } + elseif ( ! isset($db[$active_group])) + { + show_error('You have specified an invalid database connection group ('.$active_group.') in your config/database.php file.'); } $params = $db[$active_group]; } elseif (is_string($params)) { - - /* parse the URL from the DSN string - * Database settings can be passed as discreet - * parameters or as a data source name in the first - * parameter. DSNs must have this prototype: - * $dsn = 'driver://username:password@hostname/database'; + /** + * Parse the URL from the DSN string + * Database settings can be passed as discreet + * parameters or as a data source name in the first + * parameter. DSNs must have this prototype: + * $dsn = 'driver://username:password@hostname/database'; */ - - if (($dns = @parse_url($params)) === FALSE) + if (($dsn = @parse_url($params)) === FALSE) { show_error('Invalid DB Connection String'); } $params = array( - 'dbdriver' => $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)) : '' - ); + 'dbdriver' => $dsn['scheme'], + 'hostname' => isset($dsn['host']) ? rawurldecode($dsn['host']) : '', + 'port' => isset($dsn['port']) ? rawurldecode($dsn['port']) : '', + 'username' => isset($dsn['user']) ? rawurldecode($dsn['user']) : '', + 'password' => isset($dsn['pass']) ? rawurldecode($dsn['pass']) : '', + 'database' => isset($dsn['path']) ? rawurldecode(substr($dsn['path'], 1)) : '' + ); - // were additional config items set? - if (isset($dns['query'])) + // Were additional config items set? + if (isset($dsn['query'])) { - parse_str($dns['query'], $extra); + parse_str($dsn['query'], $extra); foreach ($extra as $key => $val) { - // booleans please - if (strtoupper($val) == "TRUE") + if (is_string($val) && in_array(strtoupper($val), array('TRUE', 'FALSE', 'NULL'))) { - $val = TRUE; - } - elseif (strtoupper($val) == "FALSE") - { - $val = FALSE; + $val = var_export($val, TRUE); } $params[$key] = $val; @@ -103,61 +143,76 @@ function &DB($params = '', $active_record_override = NULL) } } - // No DB specified yet? Beat them senseless... - if ( ! isset($params['dbdriver']) OR $params['dbdriver'] == '') + // No DB specified yet? Beat them senseless... + if (empty($params['dbdriver'])) { show_error('You have not selected a database type to connect to.'); } - // Load the DB classes. Note: Since the active record class is optional + // Load the DB classes. Note: Since the query builder class is optional // we need to dynamically create a class that extends proper parent class - // based on whether we're using the active record class or not. - // Kudos to Paul for discovering this clever use of eval() - - if ($active_record_override !== NULL) + // based on whether we're using the query builder class or not. + if ($query_builder_override !== NULL) { - $active_record = $active_record_override; + $query_builder = $query_builder_override; + } + // Backwards compatibility work-around for keeping the + // $active_record config variable working. Should be + // removed in v3.1 + elseif ( ! isset($query_builder) && isset($active_record)) + { + $query_builder = $active_record; } require_once(BASEPATH.'database/DB_driver.php'); - if ( ! isset($active_record) OR $active_record == TRUE) + if ( ! isset($query_builder) OR $query_builder === TRUE) { - require_once(BASEPATH.'database/DB_active_rec.php'); - - if ( ! class_exists('CI_DB')) + require_once(BASEPATH.'database/DB_query_builder.php'); + if ( ! class_exists('CI_DB', FALSE)) { - eval('class CI_DB extends CI_DB_active_record { }'); + /** + * CI_DB + * + * Acts as an alias for both CI_DB_driver and CI_DB_query_builder. + * + * @see CI_DB_query_builder + * @see CI_DB_driver + */ + class CI_DB extends CI_DB_query_builder { } } } - else + elseif ( ! class_exists('CI_DB', FALSE)) { - if ( ! class_exists('CI_DB')) - { - eval('class CI_DB extends CI_DB_driver { }'); - } + /** + * @ignore + */ + class CI_DB extends CI_DB_driver { } } - require_once(BASEPATH.'database/drivers/'.$params['dbdriver'].'/'.$params['dbdriver'].'_driver.php'); + // Load the DB driver + $driver_file = BASEPATH.'database/drivers/'.$params['dbdriver'].'/'.$params['dbdriver'].'_driver.php'; + + file_exists($driver_file) OR show_error('Invalid DB driver'); + require_once($driver_file); // Instantiate the DB adapter $driver = 'CI_DB_'.$params['dbdriver'].'_driver'; $DB = new $driver($params); - if ($DB->autoinit == TRUE) + // Check for a subdriver + if ( ! empty($DB->subdriver)) { - $DB->initialize(); - } - - if (isset($params['stricton']) && $params['stricton'] == TRUE) - { - $DB->query('SET SESSION sql_mode="STRICT_ALL_TABLES"'); + $driver_file = BASEPATH.'database/drivers/'.$DB->dbdriver.'/subdrivers/'.$DB->dbdriver.'_'.$DB->subdriver.'_driver.php'; + + if (file_exists($driver_file)) + { + require_once($driver_file); + $driver = 'CI_DB_'.$DB->dbdriver.'_'.$DB->subdriver.'_driver'; + $DB = new $driver($params); + } } + $DB->initialize(); return $DB; } - - - -/* End of file DB.php */ -/* Location: ./system/database/DB.php */ \ No newline at end of file diff --git a/src/system/database/DB_active_rec.php b/src/system/database/DB_active_rec.php deleted file mode 100644 index 33e2ea27..00000000 --- a/src/system/database/DB_active_rec.php +++ /dev/null @@ -1,2046 +0,0 @@ -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 = (int) $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[] = $k2.'-'.$v2; - } - - 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/src/system/database/DB_cache.php b/src/system/database/DB_cache.php index 4551c1e6..8855cc1b 100644 --- a/src/system/database/DB_cache.php +++ b/src/system/database/DB_cache.php @@ -1,46 +1,84 @@ -CI - // and load the file helper since we use it a lot + // Assign the main CI object to $this->CI and load the file helper since we use it a lot $this->CI =& get_instance(); $this->db =& $db; $this->CI->load->helper('file'); + + $this->check_path(); } // -------------------------------------------------------------------- @@ -48,15 +86,14 @@ class CI_DB_Cache { /** * Set Cache Directory Path * - * @access public - * @param string the path to the cache directory + * @param string $path Path to the cache directory * @return bool */ - function check_path($path = '') + public function check_path($path = '') { - if ($path == '') + if ($path === '') { - if ($this->db->cachedir == '') + if ($this->db->cachedir === '') { return $this->db->cache_off(); } @@ -65,14 +102,26 @@ class CI_DB_Cache { } // Add a trailing slash to the path if needed - $path = preg_replace("/(.+?)\/*$/", "\\1/", $path); + $path = realpath($path) + ? rtrim(realpath($path), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR + : rtrim($path, '/').'/'; - if ( ! is_dir($path) OR ! is_really_writable($path)) + if ( ! is_dir($path)) { + log_message('debug', 'DB cache path error: '.$path); + // If the path is wrong we'll turn off caching return $this->db->cache_off(); } + if ( ! is_really_writable($path)) + { + log_message('debug', 'DB cache dir not writable: '.$path); + + // If the path is not really writable we'll turn off caching + return $this->db->cache_off(); + } + $this->db->cachedir = $path; return TRUE; } @@ -83,25 +132,18 @@ class CI_DB_Cache { * 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 + * An MD5 hash of the SQL statement will become the cache file name. * - * @access public + * @param string $sql * @return string */ - function read($sql) + public 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))) + if (FALSE === ($cachedata = @file_get_contents($filepath))) { return FALSE; } @@ -114,32 +156,20 @@ class CI_DB_Cache { /** * Write a query to a cache file * - * @access public + * @param string $sql + * @param object $object * @return bool */ - function write($sql, $object) + public 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 ( ! is_dir($dir_path) && ! @mkdir($dir_path, 0750)) { - if ( ! @mkdir($dir_path, DIR_WRITE_MODE)) - { - return FALSE; - } - - @chmod($dir_path, DIR_WRITE_MODE); + return FALSE; } if (write_file($dir_path.$filename, serialize($object)) === FALSE) @@ -147,7 +177,7 @@ class CI_DB_Cache { return FALSE; } - @chmod($dir_path.$filename, FILE_WRITE_MODE); + chmod($dir_path.$filename, 0640); return TRUE; } @@ -156,23 +186,23 @@ class CI_DB_Cache { /** * Delete cache files within a particular directory * - * @access public - * @return bool + * @param string $segment_one + * @param string $segment_two + * @return void */ - function delete($segment_one = '', $segment_two = '') + public function delete($segment_one = '', $segment_two = '') { - if ($segment_one == '') + if ($segment_one === '') { $segment_one = ($this->CI->uri->segment(1) == FALSE) ? 'default' : $this->CI->uri->segment(1); } - if ($segment_two == '') + 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); } @@ -181,16 +211,11 @@ class CI_DB_Cache { /** * Delete all existing cache files * - * @access public - * @return bool + * @return void */ - function delete_all() + public function delete_all() { - delete_files($this->db->cachedir, TRUE); + delete_files($this->db->cachedir, TRUE, TRUE); } } - - -/* End of file DB_cache.php */ -/* Location: ./system/database/DB_cache.php */ \ No newline at end of file diff --git a/src/system/database/DB_driver.php b/src/system/database/DB_driver.php index b6c1e34b..848516ad 100644 --- a/src/system/database/DB_driver.php +++ b/src/system/database/DB_driver.php @@ -1,20 +1,41 @@ -conn_id) OR is_object($this->conn_id)) + /* If an established connection is available, then there's + * no need to connect and select the database. + * + * Depending on the database driver, conn_id can be either + * boolean TRUE, a resource or an object. + */ + if ($this->conn_id) { return TRUE; } @@ -113,69 +398,139 @@ class CI_DB_driver { // ---------------------------------------------------------------- // Connect to the database and set the connection ID - $this->conn_id = ($this->pconnect == FALSE) ? $this->db_connect() : $this->db_pconnect(); + $this->conn_id = $this->db_connect($this->pconnect); - // No connection resource? Throw an error + // No connection resource? Check if there is a failover else throw an error if ( ! $this->conn_id) { - log_message('error', 'Unable to connect to the database'); - - if ($this->db_debug) + // Check if there is a failover set + if ( ! empty($this->failover) && is_array($this->failover)) { - $this->display_error('db_unable_to_connect'); + // Go over all the failovers + foreach ($this->failover as $failover) + { + // Replace the current settings with those of the failover + foreach ($failover as $key => $val) + { + $this->$key = $val; + } + + // Try to connect + $this->conn_id = $this->db_connect($this->pconnect); + + // If a connection is made break the foreach loop + if ($this->conn_id) + { + break; + } + } } - return FALSE; - } - // ---------------------------------------------------------------- - - // Select the DB... assuming a database name is specified in the config file - if ($this->database != '') - { - if ( ! $this->db_select()) + // We still don't have a connection? + if ( ! $this->conn_id) { - log_message('error', 'Unable to select database: '.$this->database); + log_message('error', 'Unable to connect to the 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; + $this->display_error('db_unable_to_connect'); } - return TRUE; + return FALSE; } } + // Now we set the character set and that's all + return $this->db_set_charset($this->char_set); + } + + // -------------------------------------------------------------------- + + /** + * DB connect + * + * This is just a dummy method that all drivers will override. + * + * @return mixed + */ + public function db_connect() + { return TRUE; } // -------------------------------------------------------------------- + /** + * Persistent database connection + * + * @return mixed + */ + public function db_pconnect() + { + return $this->db_connect(TRUE); + } + + // -------------------------------------------------------------------- + + /** + * Reconnect + * + * Keep / reestablish the db connection if no queries have been + * sent for a length of time exceeding the server's idle timeout. + * + * This is just a dummy method to allow drivers without such + * functionality to not declare it, while others will override it. + * + * @return void + */ + public function reconnect() + { + } + + // -------------------------------------------------------------------- + + /** + * Select database + * + * This is just a dummy method to allow drivers without such + * functionality to not declare it, while others will override it. + * + * @return bool + */ + public function db_select() + { + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Last error + * + * @return array + */ + public function error() + { + return array('code' => NULL, 'message' => NULL); + } + + // -------------------------------------------------------------------- + /** * Set client character set * - * @access public * @param string - * @param string - * @return resource + * @return bool */ - function db_set_charset($charset, $collation) + public function db_set_charset($charset) { - if ( ! $this->_db_set_charset($this->char_set, $this->dbcollat)) + if (method_exists($this, '_db_set_charset') && ! $this->_db_set_charset($charset)) { - log_message('error', 'Unable to set database connection charset: '.$this->char_set); + log_message('error', 'Unable to set database connection charset: '.$charset); if ($this->db_debug) { - $this->display_error('db_unable_to_set_charset', $this->char_set); + $this->display_error('db_unable_to_set_charset', $charset); } return FALSE; @@ -189,10 +544,9 @@ class CI_DB_driver { /** * The name of the platform in use (mysql, mssql, etc...) * - * @access public * @return string */ - function platform() + public function platform() { return $this->dbdriver; } @@ -200,36 +554,39 @@ class CI_DB_driver { // -------------------------------------------------------------------- /** - * Database Version Number. Returns a string containing the - * version of the database being used + * Database version number + * + * Returns a string containing the version of the database being used. + * Most drivers will override this method. * - * @access public * @return string */ - function version() + public function version() { + if (isset($this->data_cache['version'])) + { + return $this->data_cache['version']; + } + if (FALSE === ($sql = $this->_version())) { - if ($this->db_debug) - { - return $this->display_error('db_unsupported_function'); - } - return FALSE; + return ($this->db_debug) ? $this->display_error('db_unsupported_function') : 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'); + $query = $this->query($sql)->row(); + return $this->data_cache['version'] = $query->ver; + } - if (in_array($this->dbdriver, $driver_version_exceptions)) - { - return $sql; - } - else - { - $query = $this->query($sql); - return $query->row('ver'); - } + // -------------------------------------------------------------------- + + /** + * Version number query string + * + * @return string + */ + protected function _version() + { + return 'SELECT VERSION() AS ver'; } // -------------------------------------------------------------------- @@ -238,32 +595,32 @@ class CI_DB_driver { * 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 + * 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 + * @param string $sql + * @param array $binds = FALSE An array of binding data + * @param bool $return_object = NULL * @return mixed */ - function query($sql, $binds = FALSE, $return_object = TRUE) + public function query($sql, $binds = FALSE, $return_object = NULL) { - if ($sql == '') + if ($sql === '') { - if ($this->db_debug) - { - log_message('error', 'Invalid query: '.$sql); - return $this->display_error('db_invalid_query'); - } - return FALSE; + log_message('error', 'Invalid query: '.$sql); + return ($this->db_debug) ? $this->display_error('db_invalid_query') : FALSE; + } + elseif ( ! is_bool($return_object)) + { + $return_object = ! $this->is_write_type($sql); } // Verify table prefix and replace if necessary - if ( ($this->dbprefix != '' AND $this->swap_pre != '') AND ($this->dbprefix != $this->swap_pre) ) + if ($this->dbprefix !== '' && $this->swap_pre !== '' && $this->dbprefix !== $this->swap_pre) { - $sql = preg_replace("/(\W)".$this->swap_pre."(\S+?)/", "\\1".$this->dbprefix."\\2", $sql); + $sql = preg_replace('/(\W)'.$this->swap_pre.'(\S+?)/', '\\1'.$this->dbprefix.'\\2', $sql); } // Compile binds if needed @@ -272,87 +629,88 @@ class CI_DB_driver { $sql = $this->compile_binds($sql, $binds); } - // Is query caching enabled? If the query is a "read type" + // 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_on === TRUE && $return_object === TRUE && $this->_cache_init()) { - if ($this->_cache_init()) + $this->load_rdriver(); + if (FALSE !== ($cache = $this->CACHE->read($sql))) { - $this->load_rdriver(); - if (FALSE !== ($cache = $this->CACHE->read($sql))) - { - return $cache; - } + return $cache; } } - // Save the query for debugging - if ($this->save_queries == TRUE) + // Save the query for debugging + if ($this->save_queries === TRUE) { $this->queries[] = $sql; } // Start the Query Timer - $time_start = list($sm, $ss) = explode(' ', microtime()); + $time_start = microtime(TRUE); // Run the Query if (FALSE === ($this->result_id = $this->simple_query($sql))) { - if ($this->save_queries == TRUE) + 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->_trans_depth !== 0) + { + $this->_trans_status = FALSE; + } + + // Grab the error now, as we might run some additional queries before displaying the error + $error = $this->error(); + + // Log errors + log_message('error', 'Query error: '.$error['message'].' - Invalid query: '.$sql); if ($this->db_debug) { - // 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 + // 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(); + while ($this->_trans_depth !== 0) + { + $trans_depth = $this->_trans_depth; + $this->trans_complete(); + if ($trans_depth === $this->_trans_depth) + { + log_message('error', 'Database: Failure during an automated transaction commit/rollback!'); + break; + } + } - // Log and display errors - log_message('error', 'Query error: '.$error_msg); - return $this->display_error( - array( - 'Error Number: '.$error_no, - $error_msg, - $sql - ) - ); + // Display errors + return $this->display_error(array('Error Number: '.$error['code'], $error['message'], $sql)); } return FALSE; } // Stop and aggregate the query time results - $time_end = list($em, $es) = explode(' ', microtime()); - $this->benchmark += ($em + $es) - ($sm + $ss); + $time_end = microtime(TRUE); + $this->benchmark += $time_end - $time_start; - if ($this->save_queries == TRUE) + if ($this->save_queries === TRUE) { - $this->query_times[] = ($em + $es) - ($sm + $ss); + $this->query_times[] = $time_end - $time_start; } // 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) + // Will we have a result object instantiated? If not - we'll simply return TRUE + if ($return_object !== TRUE) { - // If caching is enabled we'll auto-cleanup any - // existing files related to this particular URI - if ($this->cache_on == TRUE AND $this->cache_autodel == TRUE AND $this->_cache_init()) + // If caching is enabled we'll auto-cleanup any existing files related to this particular URI + if ($this->cache_on === TRUE && $this->cache_autodel === TRUE && $this->_cache_init()) { $this->CACHE->delete(); } @@ -360,35 +718,13 @@ class CI_DB_driver { 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($this); - $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 + // 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()) + if ($this->cache_on === TRUE && $this->_cache_init()) { // We'll create a new instance of the result object // only without the platform specific driver since @@ -396,10 +732,10 @@ class CI_DB_driver { // 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 = new CI_DB_result($this); $CR->result_object = $RES->result_object(); $CR->result_array = $RES->result_array(); + $CR->num_rows = $RES->num_rows(); // Reset these since cached objects can not utilize resource IDs. $CR->conn_id = NULL; @@ -416,17 +752,16 @@ class CI_DB_driver { /** * Load the result drivers * - * @access public * @return string the name of the result class */ - function load_rdriver() + public function load_rdriver() { $driver = 'CI_DB_'.$this->dbdriver.'_result'; - if ( ! class_exists($driver)) + if ( ! class_exists($driver, FALSE)) { - include_once(BASEPATH.'database/DB_result.php'); - include_once(BASEPATH.'database/drivers/'.$this->dbdriver.'/'.$this->dbdriver.'_result.php'); + require_once(BASEPATH.'database/DB_result.php'); + require_once(BASEPATH.'database/drivers/'.$this->dbdriver.'/'.$this->dbdriver.'_result.php'); } return $driver; @@ -436,19 +771,21 @@ class CI_DB_driver { /** * Simple Query - * This is a simplified version of the query() function. Internally + * 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) + public function simple_query($sql) { if ( ! $this->conn_id) { - $this->initialize(); + if ( ! $this->initialize()) + { + return FALSE; + } } return $this->_execute($sql); @@ -460,10 +797,9 @@ class CI_DB_driver { * Disable Transactions * This permits transactions to be disabled at run-time. * - * @access public * @return void */ - function trans_off() + public function trans_off() { $this->trans_enabled = FALSE; } @@ -472,15 +808,18 @@ class CI_DB_driver { /** * 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 + * When strict mode is enabled, if you are running multiple groups of + * transactions, if one group fails all subsequent groups will be + * rolled back. + * + * If strict mode is disabled, each group is treated autonomously, + * meaning a failure of one group will not affect any others + * + * @param bool $mode = TRUE * @return void */ - function trans_strict($mode = TRUE) + public function trans_strict($mode = TRUE) { $this->trans_strict = is_bool($mode) ? $mode : TRUE; } @@ -490,24 +829,17 @@ class CI_DB_driver { /** * Start Transaction * - * @access public - * @return void + * @param bool $test_mode = FALSE + * @return bool */ - function trans_start($test_mode = FALSE) + public 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); + return $this->trans_begin($test_mode); } // -------------------------------------------------------------------- @@ -515,31 +847,23 @@ class CI_DB_driver { /** * Complete Transaction * - * @access public * @return bool */ - function trans_complete() + public 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) + if ($this->_trans_status === FALSE OR $this->_trans_failure === TRUE) { $this->trans_rollback(); // If we are NOT running in strict mode, we will reset - // the _trans_status flag so that subsequent groups of transactions - // will be permitted. + // the _trans_status flag so that subsequent groups of + // transactions will be permitted. if ($this->trans_strict === FALSE) { $this->_trans_status = TRUE; @@ -549,8 +873,7 @@ class CI_DB_driver { return FALSE; } - $this->trans_commit(); - return TRUE; + return $this->trans_commit(); } // -------------------------------------------------------------------- @@ -558,55 +881,157 @@ class CI_DB_driver { /** * Lets you retrieve the transaction flag to determine if it has failed * - * @access public * @return bool */ - function trans_status() + public function trans_status() { return $this->_trans_status; } // -------------------------------------------------------------------- + /** + * Begin Transaction + * + * @param bool $test_mode + * @return bool + */ + public function trans_begin($test_mode = FALSE) + { + if ( ! $this->trans_enabled) + { + return FALSE; + } + // When transactions are nested we only begin/commit/rollback the outermost ones + elseif ($this->_trans_depth > 0) + { + $this->_trans_depth++; + return TRUE; + } + + // Reset the transaction failure flag. + // If the $test_mode flag is set to TRUE transactions will be rolled back + // even if the queries produce a successful result. + $this->_trans_failure = ($test_mode === TRUE); + + if ($this->_trans_begin()) + { + $this->_trans_depth++; + return TRUE; + } + + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Commit Transaction + * + * @return bool + */ + public function trans_commit() + { + if ( ! $this->trans_enabled OR $this->_trans_depth === 0) + { + return FALSE; + } + // When transactions are nested we only begin/commit/rollback the outermost ones + elseif ($this->_trans_depth > 1 OR $this->_trans_commit()) + { + $this->_trans_depth--; + return TRUE; + } + + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Rollback Transaction + * + * @return bool + */ + public function trans_rollback() + { + if ( ! $this->trans_enabled OR $this->_trans_depth === 0) + { + return FALSE; + } + // When transactions are nested we only begin/commit/rollback the outermost ones + elseif ($this->_trans_depth > 1 OR $this->_trans_rollback()) + { + $this->_trans_depth--; + return TRUE; + } + + return FALSE; + } + + // -------------------------------------------------------------------- + /** * Compile Bindings * - * @access public * @param string the sql statement * @param array an array of bind data * @return string */ - function compile_binds($sql, $binds) + public function compile_binds($sql, $binds) { - if (strpos($sql, $this->bind_marker) === FALSE) + if (empty($binds) OR empty($this->bind_marker) OR strpos($sql, $this->bind_marker) === FALSE) + { + return $sql; + } + elseif ( ! is_array($binds)) + { + $binds = array($binds); + $bind_count = 1; + } + else + { + // Make sure we're using numeric keys + $binds = array_values($binds); + $bind_count = count($binds); + } + + // We'll need the marker length later + $ml = strlen($this->bind_marker); + + // Make sure not to replace a chunk inside a string that happens to match the bind marker + if ($c = preg_match_all("/'[^']*'/i", $sql, $matches)) + { + $c = preg_match_all('/'.preg_quote($this->bind_marker, '/').'/i', + str_replace($matches[0], + str_replace($this->bind_marker, str_repeat(' ', $ml), $matches[0]), + $sql, $c), + $matches, PREG_OFFSET_CAPTURE); + + // Bind values' count must match the count of markers in the query + if ($bind_count !== $c) + { + return $sql; + } + } + elseif (($c = preg_match_all('/'.preg_quote($this->bind_marker, '/').'/i', $sql, $matches, PREG_OFFSET_CAPTURE)) !== $bind_count) { return $sql; } - if ( ! is_array($binds)) + do { - $binds = array($binds); + $c--; + $escaped_value = $this->escape($binds[$c]); + if (is_array($escaped_value)) + { + $escaped_value = '('.implode(',', $escaped_value).')'; + } + $sql = substr_replace($sql, $escaped_value, $matches[0][$c][1], $ml); } + while ($c !== 0); - // 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; + return $sql; } // -------------------------------------------------------------------- @@ -614,17 +1039,12 @@ class CI_DB_driver { /** * Determines if a query is a "write" type. * - * @access public * @param string An SQL query string - * @return boolean + * @return bool */ - function is_write_type($sql) + public 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; + return (bool) preg_match('/^\s*"?(SET|INSERT|UPDATE|DELETE|REPLACE|CREATE|DROP|TRUNCATE|LOAD|COPY|ALTER|RENAME|GRANT|REVOKE|LOCK|UNLOCK|REINDEX)\s/i', $sql); } // -------------------------------------------------------------------- @@ -632,11 +1052,10 @@ class CI_DB_driver { /** * Calculate the aggregate query elapsed time * - * @access public - * @param integer The number of decimal places - * @return integer + * @param int The number of decimal places + * @return string */ - function elapsed_time($decimals = 6) + public function elapsed_time($decimals = 6) { return number_format($this->benchmark, $decimals); } @@ -646,10 +1065,9 @@ class CI_DB_driver { /** * Returns the total number of queries * - * @access public - * @return integer + * @return int */ - function total_queries() + public function total_queries() { return $this->query_count; } @@ -659,10 +1077,9 @@ class CI_DB_driver { /** * Returns the last query that was executed * - * @access public - * @return void + * @return string */ - function last_query() + public function last_query() { return end($this->queries); } @@ -675,23 +1092,63 @@ class CI_DB_driver { * Escapes data based on type * Sets boolean and null types * - * @access public * @param string * @return mixed */ - function escape($str) + public function escape($str) { - if (is_string($str)) + if (is_array($str)) { - $str = "'".$this->escape_str($str)."'"; + $str = array_map(array(&$this, 'escape'), $str); + return $str; + } + elseif (is_string($str) OR (is_object($str) && method_exists($str, '__toString'))) + { + return "'".$this->escape_str($str)."'"; } elseif (is_bool($str)) { - $str = ($str === FALSE) ? 0 : 1; + return ($str === FALSE) ? 0 : 1; } - elseif (is_null($str)) + elseif ($str === NULL) { - $str = 'NULL'; + return 'NULL'; + } + + return $str; + } + + // -------------------------------------------------------------------- + + /** + * Escape String + * + * @param string|string[] $str Input string + * @param bool $like Whether or not the string will be used in a LIKE condition + * @return string + */ + public function escape_str($str, $like = FALSE) + { + if (is_array($str)) + { + foreach ($str as $key => $val) + { + $str[$key] = $this->escape_str($val, $like); + } + + return $str; + } + + $str = $this->_escape_str($str); + + // escape LIKE condition wildcards + if ($like === TRUE) + { + return str_replace( + array($this->_like_escape_chr, '%', '_'), + array($this->_like_escape_chr.$this->_like_escape_chr, $this->_like_escape_chr.'%', $this->_like_escape_chr.'_'), + $str + ); } return $str; @@ -705,11 +1162,10 @@ class CI_DB_driver { * Calls the individual driver for platform * specific escaping for LIKE conditions * - * @access public - * @param string + * @param string|string[] * @return mixed */ - function escape_like_str($str) + public function escape_like_str($str) { return $this->escape_str($str, TRUE); } @@ -717,25 +1173,60 @@ class CI_DB_driver { // -------------------------------------------------------------------- /** - * Primary + * Platform-dependant string escape * - * Retrieves the primary key. It assumes that the row in the first - * position is the primary key - * - * @access public - * @param string the table name + * @param string * @return string */ - function primary($table = '') + protected function _escape_str($str) + { + return str_replace("'", "''", remove_invisible_characters($str)); + } + + // -------------------------------------------------------------------- + + /** + * Primary + * + * Retrieves the primary key. It assumes that the row in the first + * position is the primary key + * + * @param string $table Table name + * @return string + */ + public function primary($table) { $fields = $this->list_fields($table); + return is_array($fields) ? current($fields) : FALSE; + } - if ( ! is_array($fields)) + // -------------------------------------------------------------------- + + /** + * "Count All" query + * + * Generates a platform-specific query string that counts all records in + * the specified database + * + * @param string + * @return int + */ + public function count_all($table = '') + { + if ($table === '') { - return FALSE; + return 0; } - return current($fields); + $query = $this->query($this->_count_string.$this->escape_identifiers('numrows').' FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE)); + if ($query->num_rows() === 0) + { + return 0; + } + + $query = $query->row(); + $this->_reset_select(); + return (int) $query->numrows; } // -------------------------------------------------------------------- @@ -743,10 +1234,10 @@ class CI_DB_driver { /** * Returns an array of table names * - * @access public + * @param string $constrain_by_prefix = FALSE * @return array */ - function list_tables($constrain_by_prefix = FALSE) + public function list_tables($constrain_by_prefix = FALSE) { // Is there a cached result? if (isset($this->data_cache['table_names'])) @@ -756,32 +1247,40 @@ class CI_DB_driver { if (FALSE === ($sql = $this->_list_tables($constrain_by_prefix))) { - if ($this->db_debug) - { - return $this->display_error('db_unsupported_function'); - } - return FALSE; + return ($this->db_debug) ? $this->display_error('db_unsupported_function') : FALSE; } - $retval = array(); + $this->data_cache['table_names'] = array(); $query = $this->query($sql); - if ($query->num_rows() > 0) + foreach ($query->result_array() as $row) { - foreach ($query->result_array() as $row) + // Do we know from which column to get the table name? + if ( ! isset($key)) { - if (isset($row['TABLE_NAME'])) + if (isset($row['table_name'])) { - $retval[] = $row['TABLE_NAME']; + $key = 'table_name'; + } + elseif (isset($row['TABLE_NAME'])) + { + $key = 'TABLE_NAME'; } else { - $retval[] = array_shift($row); + /* We have no other choice but to just get the first element's key. + * Due to array_shift() accepting its argument by reference, if + * E_STRICT is on, this would trigger a warning. So we'll have to + * assign it first. + */ + $key = array_keys($row); + $key = array_shift($key); } } + + $this->data_cache['table_names'][] = $row[$key]; } - $this->data_cache['table_names'] = $retval; return $this->data_cache['table_names']; } @@ -789,24 +1288,24 @@ class CI_DB_driver { /** * Determine if a particular table exists - * @access public - * @return boolean + * + * @param string $table_name + * @return bool */ - function table_exists($table_name) + public function table_exists($table_name) { - return ( ! in_array($this->_protect_identifiers($table_name, TRUE, FALSE, FALSE), $this->list_tables())) ? FALSE : TRUE; + return in_array($this->protect_identifiers($table_name, TRUE, FALSE, FALSE), $this->list_tables()); } // -------------------------------------------------------------------- /** - * Fetch MySQL Field Names + * Fetch Field Names * - * @access public - * @param string the table name + * @param string $table Table name * @return array */ - function list_fields($table = '') + public function list_fields($table) { // Is there a cached result? if (isset($this->data_cache['field_names'][$table])) @@ -814,40 +1313,37 @@ class CI_DB_driver { 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; + return ($this->db_debug) ? $this->display_error('db_unsupported_function') : FALSE; } $query = $this->query($sql); + $this->data_cache['field_names'][$table] = array(); - $retval = array(); foreach ($query->result_array() as $row) { - if (isset($row['COLUMN_NAME'])) + // Do we know from where to get the column's name? + if ( ! isset($key)) { - $retval[] = $row['COLUMN_NAME']; - } - else - { - $retval[] = current($row); + if (isset($row['column_name'])) + { + $key = 'column_name'; + } + elseif (isset($row['COLUMN_NAME'])) + { + $key = 'COLUMN_NAME'; + } + else + { + // We have no other choice but to just get the first element's key. + $key = key($row); + } } + + $this->data_cache['field_names'][$table][] = $row[$key]; } - $this->data_cache['field_names'][$table] = $retval; return $this->data_cache['field_names'][$table]; } @@ -855,14 +1351,14 @@ class CI_DB_driver { /** * Determine if a particular field exists - * @access public + * * @param string * @param string - * @return boolean + * @return bool */ - function field_exists($field_name, $table_name) + public function field_exists($field_name, $table_name) { - return ( ! in_array($field_name, $this->list_fields($table_name))) ? FALSE : TRUE; + return in_array($field_name, $this->list_fields($table_name)); } // -------------------------------------------------------------------- @@ -870,24 +1366,75 @@ class CI_DB_driver { /** * Returns an object with field data * - * @access public - * @param string the table name - * @return object + * @param string $table the table name + * @return array */ - function field_data($table = '') + public function field_data($table) { - if ($table == '') + $query = $this->query($this->_field_data($this->protect_identifiers($table, TRUE, NULL, FALSE))); + return ($query) ? $query->field_data() : FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Escape the SQL Identifiers + * + * This function escapes column and table names + * + * @param mixed + * @return mixed + */ + public function escape_identifiers($item) + { + if ($this->_escape_char === '' OR empty($item) OR in_array($item, $this->_reserved_identifiers)) { - if ($this->db_debug) + return $item; + } + elseif (is_array($item)) + { + foreach ($item as $key => $value) { - return $this->display_error('db_field_param_missing'); + $item[$key] = $this->escape_identifiers($value); } - return FALSE; + + return $item; + } + // Avoid breaking functions and literal values inside queries + elseif (ctype_digit($item) OR $item[0] === "'" OR ($this->_escape_char !== '"' && $item[0] === '"') OR strpos($item, '(') !== FALSE) + { + return $item; } - $query = $this->query($this->_field_data($this->_protect_identifiers($table, TRUE, NULL, FALSE))); + static $preg_ec = array(); - return $query->field_data(); + if (empty($preg_ec)) + { + if (is_array($this->_escape_char)) + { + $preg_ec = array( + preg_quote($this->_escape_char[0], '/'), + preg_quote($this->_escape_char[1], '/'), + $this->_escape_char[0], + $this->_escape_char[1] + ); + } + else + { + $preg_ec[0] = $preg_ec[1] = preg_quote($this->_escape_char, '/'); + $preg_ec[2] = $preg_ec[3] = $this->_escape_char; + } + } + + foreach ($this->_reserved_identifiers as $id) + { + if (strpos($item, '.'.$id) !== FALSE) + { + return preg_replace('/'.$preg_ec[0].'?([^'.$preg_ec[1].'\.]+)'.$preg_ec[1].'?\./i', $preg_ec[2].'$1'.$preg_ec[3].'.', $item); + } + } + + return preg_replace('/'.$preg_ec[0].'?([^'.$preg_ec[1].'\.]+)'.$preg_ec[1].'?(\.)?/i', $preg_ec[2].'$1'.$preg_ec[3].'$2', $item); } // -------------------------------------------------------------------- @@ -895,23 +1442,38 @@ class CI_DB_driver { /** * 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) + public function insert_string($table, $data) { - $fields = array(); - $values = array(); + $fields = $values = array(); foreach ($data as $key => $val) { - $fields[] = $this->_escape_identifiers($key); + $fields[] = $this->escape_identifiers($key); $values[] = $this->escape($val); } - return $this->_insert($this->_protect_identifiers($table, TRUE, NULL, FALSE), $fields, $values); + return $this->_insert($this->protect_identifiers($table, TRUE, NULL, FALSE), $fields, $values); + } + + // -------------------------------------------------------------------- + + /** + * Insert statement + * + * Generates a platform-specific insert string from the supplied data + * + * @param string the table name + * @param array the insert keys + * @param array the insert values + * @return string + */ + protected function _insert($table, $keys, $values) + { + return 'INSERT INTO '.$table.' ('.implode(', ', $keys).') VALUES ('.implode(', ', $values).')'; } // -------------------------------------------------------------------- @@ -919,51 +1481,53 @@ class CI_DB_driver { /** * 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) + public function update_string($table, $data, $where) { - if ($where == '') + if (empty($where)) { - return false; + return FALSE; } + $this->where($where); + $fields = array(); foreach ($data as $key => $val) { - $fields[$this->_protect_identifiers($key)] = $this->escape($val); + $fields[$this->protect_identifiers($key)] = $this->escape($val); } - if ( ! is_array($where)) + $sql = $this->_update($this->protect_identifiers($table, TRUE, NULL, FALSE), $fields); + $this->_reset_write(); + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Update statement + * + * Generates a platform-specific update string from the supplied data + * + * @param string the table name + * @param array the update data + * @return string + */ + protected function _update($table, $values) + { + foreach ($values as $key => $val) { - $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; - } + $valstr[] = $key.' = '.$val; } - return $this->_update($this->_protect_identifiers($table, TRUE, NULL, FALSE), $fields, $dest); + return 'UPDATE '.$table.' SET '.implode(', ', $valstr) + .$this->_compile_wh('qb_where') + .$this->_compile_order_by() + .($this->qb_limit ? ' LIMIT '.$this->qb_limit : ''); } // -------------------------------------------------------------------- @@ -971,19 +1535,50 @@ class CI_DB_driver { /** * Tests whether the string has an SQL operator * - * @access private * @param string * @return bool */ - function _has_operator($str) + protected function _has_operator($str) { - $str = trim($str); - if ( ! preg_match("/(\s|<|>|!|=|is null|is not null)/i", $str)) + return (bool) preg_match('/(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sIN\s*\(|\s)/i', trim($str)); + } + + // -------------------------------------------------------------------- + + /** + * Returns the SQL string operator + * + * @param string + * @return string + */ + protected function _get_operator($str) + { + static $_operators; + + if (empty($_operators)) { - return FALSE; + $_les = ($this->_like_escape_str !== '') + ? '\s+'.preg_quote(trim(sprintf($this->_like_escape_str, $this->_like_escape_chr)), '/') + : ''; + $_operators = array( + '\s*(?:<|>|!)?=\s*', // =, <=, >=, != + '\s*<>?\s*', // <, <> + '\s*>\s*', // > + '\s+IS NULL', // IS NULL + '\s+IS NOT NULL', // IS NOT NULL + '\s+EXISTS\s*\(.*\)', // EXISTS(sql) + '\s+NOT EXISTS\s*\(.*\)', // NOT EXISTS(sql) + '\s+BETWEEN\s+', // BETWEEN value AND value + '\s+IN\s*\(.*\)', // IN(list) + '\s+NOT IN\s*\(.*\)', // NOT IN (list) + '\s+LIKE\s+\S.*('.$_les.')?', // LIKE 'expr'[ ESCAPE '%s'] + '\s+NOT LIKE\s+\S.*('.$_les.')?' // NOT LIKE 'expr'[ ESCAPE '%s'] + ); + } - return TRUE; + return preg_match('/'.implode('|', $_operators).'/i', $str, $match) + ? $match[0] : FALSE; } // -------------------------------------------------------------------- @@ -991,14 +1586,12 @@ class CI_DB_driver { /** * 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 + * @param string $function Function name * @return mixed */ - function call_function($function) + public function call_function($function) { - $driver = ($this->dbdriver == 'postgre') ? 'pg_' : $this->dbdriver.'_'; + $driver = ($this->dbdriver === 'postgre') ? 'pg_' : $this->dbdriver.'_'; if (FALSE === strpos($driver, $function)) { @@ -1007,24 +1600,12 @@ class CI_DB_driver { 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); - } + return ($this->db_debug) ? $this->display_error('db_unsupported_function') : FALSE; } + + return (func_num_args() > 1) + ? call_user_func_array($function, array_slice(func_get_args(), 1)) + : call_user_func($function); } // -------------------------------------------------------------------- @@ -1032,11 +1613,10 @@ class CI_DB_driver { /** * Set Cache Directory Path * - * @access public * @param string the path to the cache directory * @return void */ - function cache_set_path($path = '') + public function cache_set_path($path = '') { $this->cachedir = $path; } @@ -1046,13 +1626,11 @@ class CI_DB_driver { /** * Enable Query Caching * - * @access public - * @return void + * @return bool cache_on value */ - function cache_on() + public function cache_on() { - $this->cache_on = TRUE; - return TRUE; + return $this->cache_on = TRUE; } // -------------------------------------------------------------------- @@ -1060,31 +1638,27 @@ class CI_DB_driver { /** * Disable Query Caching * - * @access public - * @return void + * @return bool cache_on value */ - function cache_off() + public function cache_off() { - $this->cache_on = FALSE; - return FALSE; + return $this->cache_on = FALSE; } - // -------------------------------------------------------------------- /** * Delete the cache files associated with a particular URI * - * @access public - * @return void + * @param string $segment_one = '' + * @param string $segment_two = '' + * @return bool */ - function cache_delete($segment_one = '', $segment_two = '') + public function cache_delete($segment_one = '', $segment_two = '') { - if ( ! $this->_cache_init()) - { - return FALSE; - } - return $this->CACHE->delete($segment_one, $segment_two); + return $this->_cache_init() + ? $this->CACHE->delete($segment_one, $segment_two) + : FALSE; } // -------------------------------------------------------------------- @@ -1092,17 +1666,13 @@ class CI_DB_driver { /** * Delete All cache files * - * @access public - * @return void + * @return bool */ - function cache_delete_all() + public function cache_delete_all() { - if ( ! $this->_cache_init()) - { - return FALSE; - } - - return $this->CACHE->delete_all(); + return $this->_cache_init() + ? $this->CACHE->delete_all() + : FALSE; } // -------------------------------------------------------------------- @@ -1110,24 +1680,19 @@ class CI_DB_driver { /** * Initialize the Cache Class * - * @access private - * @return void + * @return bool */ - function _cache_init() + protected function _cache_init() { - if (is_object($this->CACHE) AND class_exists('CI_DB_Cache')) + if ( ! class_exists('CI_DB_Cache', FALSE)) + { + require_once(BASEPATH.'database/DB_cache.php'); + } + elseif (is_object($this->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; } @@ -1137,15 +1702,28 @@ class CI_DB_driver { /** * Close DB Connection * - * @access public * @return void */ - function close() + public function close() { - if (is_resource($this->conn_id) OR is_object($this->conn_id)) + if ($this->conn_id) { - $this->_close($this->conn_id); + $this->_close(); + $this->conn_id = FALSE; } + } + + // -------------------------------------------------------------------- + + /** + * Close DB Connection + * + * This method would be overridden by most of the drivers. + * + * @return void + */ + protected function _close() + { $this->conn_id = FALSE; } @@ -1154,49 +1732,54 @@ class CI_DB_driver { /** * 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 + * @param bool whether to localize the message + * @return string sends the application/views/errors/error_db.php template */ - function display_error($error = '', $swap = '', $native = FALSE) + public function display_error($error = '', $swap = '', $native = FALSE) { $LANG =& load_class('Lang', 'core'); $LANG->load('db'); $heading = $LANG->line('db_error_heading'); - if ($native == TRUE) + if ($native === TRUE) { - $message = $error; + $message = (array) $error; } else { - $message = ( ! is_array($error)) ? array(str_replace('%s', $swap, $LANG->line($error))) : $error; + $message = is_array($error) ? $error : array(str_replace('%s', $swap, $LANG->line($error))); } // Find the most likely culprit of the error by going through // the backtrace until the source file is no longer in the // database folder. - $trace = debug_backtrace(); - foreach ($trace as $call) { - if (isset($call['file']) && strpos($call['file'], BASEPATH.'database') === FALSE) + if (isset($call['file'], $call['class'])) { - // Found it - use a relative path for safety - $message[] = 'Filename: '.str_replace(array(BASEPATH, APPPATH), '', $call['file']); - $message[] = 'Line Number: '.$call['line']; + // We'll need this on Windows, as APPPATH and BASEPATH will always use forward slashes + if (DIRECTORY_SEPARATOR !== '/') + { + $call['file'] = str_replace('\\', '/', $call['file']); + } - break; + if (strpos($call['file'], BASEPATH.'database') === FALSE && strpos($call['class'], 'Loader') === FALSE) + { + // Found it - use a relative path for safety + $message[] = 'Filename: '.str_replace(array(APPPATH, BASEPATH), '', $call['file']); + $message[] = 'Line Number: '.$call['line']; + break; + } } } $error =& load_class('Exceptions', 'core'); echo $error->show_error($heading, $message, 'error_db'); - exit; + exit(8); // EXIT_DATABASE } // -------------------------------------------------------------------- @@ -1204,29 +1787,13 @@ class CI_DB_driver { /** * 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 + * This function is used extensively by the Query Builder class, and by * a couple functions in this class. * It takes a column or table name (optionally with an alias) and inserts - * the table prefix onto it. Some logic is necessary in order to deal with - * column names that include the path. Consider a query like this: + * 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 + * SELECT hostname.database.table.column AS c FROM hostname.database.table * * Or a query with aliasing: * @@ -1237,14 +1804,13 @@ class CI_DB_driver { * 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) + public function protect_identifiers($item, $prefix_single = FALSE, $protect_identifiers = NULL, $field_exists = TRUE) { if ( ! is_bool($protect_identifiers)) { @@ -1254,50 +1820,64 @@ class CI_DB_driver { if (is_array($item)) { $escaped_array = array(); - foreach ($item as $k => $v) { - $escaped_array[$this->_protect_identifiers($k)] = $this->_protect_identifiers($v); + $escaped_array[$this->protect_identifiers($k)] = $this->protect_identifiers($v, $prefix_single, $protect_identifiers, $field_exists); } return $escaped_array; } + // This is basically a bug fix for queries that use MAX, MIN, etc. + // If a parenthesis is found we know that we do not need to + // escape the data or add a prefix. There's probably a more graceful + // way to deal with this, but I'm not thinking of it -- Rick + // + // Added exception for single quotes as well, we don't want to alter + // literal strings. -- Narf + if (strcspn($item, "()'") !== strlen($item)) + { + return $item; + } + // Convert tabs or multiple spaces into single spaces - $item = preg_replace('/[\t ]+/', ' ', $item); + $item = preg_replace('/\s+/', ' ', trim($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) + // Note: strripos() is used in order to support spaces in table names + if ($offset = strripos($item, ' AS ')) { - $alias = strstr($item, ' '); - $item = substr($item, 0, - strlen($alias)); + $alias = ($protect_identifiers) + ? substr($item, $offset, 4).$this->escape_identifiers(substr($item, $offset + 4)) + : substr($item, $offset); + $item = substr($item, 0, $offset); + } + elseif ($offset = strrpos($item, ' ')) + { + $alias = ($protect_identifiers) + ? ' '.$this->escape_identifiers(substr($item, $offset + 1)) + : substr($item, $offset); + $item = substr($item, 0, $offset); } else { $alias = ''; } - // 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); + $parts = explode('.', $item); // Does the first segment of the exploded item match - // one of the aliases previously identified? If so, + // 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)) + // + // NOTE: The ! empty() condition prevents this method + // from breaking when QB isn't enabled. + if ( ! empty($this->qb_aliased_tables) && in_array($parts[0], $this->qb_aliased_tables)) { if ($protect_identifiers === TRUE) { @@ -1305,17 +1885,18 @@ class CI_DB_driver { { if ( ! in_array($val, $this->_reserved_identifiers)) { - $parts[$key] = $this->_escape_identifiers($val); + $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 != '') + // 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)? @@ -1339,19 +1920,18 @@ class CI_DB_driver { // 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) + 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) + if ($this->swap_pre !== '' && strpos($parts[$i], $this->swap_pre) === 0) { - $parts[$i] = preg_replace("/^".$this->swap_pre."(\S+?)/", $this->dbprefix."\\1", $parts[$i]); + $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) + elseif (strpos($parts[$i], $this->dbprefix) !== 0) { $parts[$i] = $this->dbprefix.$parts[$i]; } @@ -1362,31 +1942,30 @@ class CI_DB_driver { if ($protect_identifiers === TRUE) { - $item = $this->_escape_identifiers($item); + $item = $this->escape_identifiers($item); } return $item.$alias; } - // Is there a table prefix? If not, no need to insert it - if ($this->dbprefix != '') + // 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) + if ($this->swap_pre !== '' && strpos($item, $this->swap_pre) === 0) { - $item = preg_replace("/^".$this->swap_pre."(\S+?)/", $this->dbprefix."\\1", $item); + $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) + elseif ($prefix_single === TRUE && strpos($item, $this->dbprefix) !== 0) { $item = $this->dbprefix.$item; } } - if ($protect_identifiers === TRUE AND ! in_array($item, $this->_reserved_identifiers)) + if ($protect_identifiers === TRUE && ! in_array($item, $this->_reserved_identifiers)) { - $item = $this->_escape_identifiers($item); + $item = $this->escape_identifiers($item); } return $item.$alias; @@ -1395,9 +1974,8 @@ class CI_DB_driver { // -------------------------------------------------------------------- /** - * Dummy method that allows Active Record class to be disabled - * - * This function is used extensively by every db driver. + * Dummy method that allows Query Builder class to be disabled + * and keep count_all() working. * * @return void */ @@ -1406,6 +1984,3 @@ class CI_DB_driver { } } - -/* End of file DB_driver.php */ -/* Location: ./system/database/DB_driver.php */ \ No newline at end of file diff --git a/src/system/database/DB_forge.php b/src/system/database/DB_forge.php index cf63ee1e..826aa1eb 100644 --- a/src/system/database/DB_forge.php +++ b/src/system/database/DB_forge.php @@ -1,47 +1,173 @@ -db - $CI =& get_instance(); - $this->db =& $CI->db; - log_message('debug', "Database Forge Class Initialized"); + $this->db =& $db; + log_message('info', 'Database Forge Class Initialized'); } // -------------------------------------------------------------------- @@ -49,20 +175,26 @@ class CI_DB_forge { /** * Create database * - * @access public - * @param string the database name + * @param string $db_name * @return bool */ - function create_database($db_name) + public function create_database($db_name) { - $sql = $this->_create_database($db_name); - - if (is_bool($sql)) + if ($this->_create_database === FALSE) { - return $sql; + return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE; + } + elseif ( ! $this->db->query(sprintf($this->_create_database, $db_name, $this->db->char_set, $this->db->dbcollat))) + { + return ($this->db->db_debug) ? $this->db->display_error('db_unable_to_drop') : FALSE; } - return $this->db->query($sql); + if ( ! empty($this->db->data_cache['db_names'])) + { + $this->db->data_cache['db_names'][] = $db_name; + } + + return TRUE; } // -------------------------------------------------------------------- @@ -70,20 +202,30 @@ class CI_DB_forge { /** * Drop database * - * @access public - * @param string the database name + * @param string $db_name * @return bool */ - function drop_database($db_name) + public function drop_database($db_name) { - $sql = $this->_drop_database($db_name); - - if (is_bool($sql)) + if ($this->_drop_database === FALSE) { - return $sql; + return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE; + } + elseif ( ! $this->db->query(sprintf($this->_drop_database, $db_name))) + { + return ($this->db->db_debug) ? $this->db->display_error('db_unable_to_drop') : FALSE; } - return $this->db->query($sql); + if ( ! empty($this->db->data_cache['db_names'])) + { + $key = array_search(strtolower($db_name), array_map('strtolower', $this->db->data_cache['db_names']), TRUE); + if ($key !== FALSE) + { + unset($this->db->data_cache['db_names'][$key]); + } + } + + return TRUE; } // -------------------------------------------------------------------- @@ -91,26 +233,26 @@ class CI_DB_forge { /** * Add Key * - * @access public - * @param string key - * @param string type - * @return void + * @param string $key + * @param bool $primary + * @return CI_DB_forge */ - function add_key($key = '', $primary = FALSE) + public function add_key($key, $primary = FALSE) { - if (is_array($key)) + // DO NOT change this! This condition is only applicable + // for PRIMARY keys because you can only have one such, + // and therefore all fields you add to it will be included + // in the same, composite PRIMARY KEY. + // + // It's not the same for regular indexes. + if ($primary === TRUE && is_array($key)) { foreach ($key as $one) { $this->add_key($one, $primary); } - return; - } - - if ($key == '') - { - show_error('Key information is required for that operation.'); + return $this; } if ($primary === TRUE) @@ -121,6 +263,8 @@ class CI_DB_forge { { $this->keys[] = $key; } + + return $this; } // -------------------------------------------------------------------- @@ -128,28 +272,22 @@ class CI_DB_forge { /** * Add Field * - * @access public - * @param string collation - * @return void + * @param array $field + * @return CI_DB_forge */ - function add_field($field = '') + public function add_field($field) { - if ($field == '') - { - show_error('Field information is required.'); - } - if (is_string($field)) { - if ($field == 'id') + if ($field === 'id') { $this->add_field(array( - 'id' => array( - 'type' => 'INT', - 'constraint' => 9, - 'auto_increment' => TRUE - ) - )); + 'id' => array( + 'type' => 'INT', + 'constraint' => 9, + 'auto_increment' => TRUE + ) + )); $this->add_key('id', TRUE); } else @@ -168,6 +306,7 @@ class CI_DB_forge { $this->fields = array_merge($this->fields, $field); } + return $this; } // -------------------------------------------------------------------- @@ -175,26 +314,133 @@ class CI_DB_forge { /** * Create Table * - * @access public - * @param string the table name + * @param string $table Table name + * @param bool $if_not_exists Whether to add IF NOT EXISTS condition + * @param array $attributes Associative array of table attributes * @return bool */ - function create_table($table = '', $if_not_exists = FALSE) + public function create_table($table, $if_not_exists = FALSE, array $attributes = array()) { - if ($table == '') + if ($table === '') { show_error('A table name is required for that operation.'); } + else + { + $table = $this->db->dbprefix.$table; + } - if (count($this->fields) == 0) + 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); + $sql = $this->_create_table($table, $if_not_exists, $attributes); + + if (is_bool($sql)) + { + $this->_reset(); + if ($sql === FALSE) + { + return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE; + } + } + + if (($result = $this->db->query($sql)) !== FALSE) + { + empty($this->db->data_cache['table_names']) OR $this->db->data_cache['table_names'][] = $table; + + // Most databases don't support creating indexes from within the CREATE TABLE statement + if ( ! empty($this->keys)) + { + for ($i = 0, $sqls = $this->_process_indexes($table), $c = count($sqls); $i < $c; $i++) + { + $this->db->query($sqls[$i]); + } + } + } $this->_reset(); - return $this->db->query($sql); + return $result; + } + + // -------------------------------------------------------------------- + + /** + * Create Table + * + * @param string $table Table name + * @param bool $if_not_exists Whether to add 'IF NOT EXISTS' condition + * @param array $attributes Associative array of table attributes + * @return mixed + */ + protected function _create_table($table, $if_not_exists, $attributes) + { + if ($if_not_exists === TRUE && $this->_create_table_if === FALSE) + { + if ($this->db->table_exists($table)) + { + return TRUE; + } + else + { + $if_not_exists = FALSE; + } + } + + $sql = ($if_not_exists) + ? sprintf($this->_create_table_if, $this->db->escape_identifiers($table)) + : 'CREATE TABLE'; + + $columns = $this->_process_fields(TRUE); + for ($i = 0, $c = count($columns); $i < $c; $i++) + { + $columns[$i] = ($columns[$i]['_literal'] !== FALSE) + ? "\n\t".$columns[$i]['_literal'] + : "\n\t".$this->_process_column($columns[$i]); + } + + $columns = implode(',', $columns) + .$this->_process_primary_keys($table); + + // Are indexes created from within the CREATE TABLE statement? (e.g. in MySQL) + if ($this->_create_table_keys === TRUE) + { + $columns .= $this->_process_indexes($table); + } + + // _create_table will usually have the following format: "%s %s (%s\n)" + $sql = sprintf($this->_create_table.'%s', + $sql, + $this->db->escape_identifiers($table), + $columns, + $this->_create_table_attr($attributes) + ); + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * CREATE TABLE attributes + * + * @param array $attributes Associative array of table attributes + * @return string + */ + protected function _create_table_attr($attributes) + { + $sql = ''; + + foreach (array_keys($attributes) as $key) + { + if (is_string($key)) + { + $sql .= ' '.strtoupper($key).' '.$attributes[$key]; + } + } + + return $sql; } // -------------------------------------------------------------------- @@ -202,20 +448,68 @@ class CI_DB_forge { /** * Drop Table * - * @access public - * @param string the table name + * @param string $table_name Table name + * @param bool $if_exists Whether to add an IF EXISTS condition * @return bool */ - function drop_table($table_name) + public function drop_table($table_name, $if_exists = FALSE) { - $sql = $this->_drop_table($this->db->dbprefix.$table_name); - - if (is_bool($sql)) + if ($table_name === '') { - return $sql; + return ($this->db->db_debug) ? $this->db->display_error('db_table_name_required') : FALSE; } - return $this->db->query($sql); + if (($query = $this->_drop_table($this->db->dbprefix.$table_name, $if_exists)) === TRUE) + { + return TRUE; + } + + $query = $this->db->query($query); + + // Update table list cache + if ($query && ! empty($this->db->data_cache['table_names'])) + { + $key = array_search(strtolower($this->db->dbprefix.$table_name), array_map('strtolower', $this->db->data_cache['table_names']), TRUE); + if ($key !== FALSE) + { + unset($this->db->data_cache['table_names'][$key]); + } + } + + return $query; + } + + // -------------------------------------------------------------------- + + /** + * Drop Table + * + * Generates a platform-specific DROP TABLE string + * + * @param string $table Table name + * @param bool $if_exists Whether to add an IF EXISTS condition + * @return string + */ + protected function _drop_table($table, $if_exists) + { + $sql = 'DROP TABLE'; + + if ($if_exists) + { + if ($this->_drop_table_if === FALSE) + { + if ( ! $this->db->table_exists($table)) + { + return TRUE; + } + } + else + { + $sql = sprintf($this->_drop_table_if, $this->db->escape_identifiers($table)); + } + } + + return $sql.' '.$this->db->escape_identifiers($table); } // -------------------------------------------------------------------- @@ -223,20 +517,37 @@ class CI_DB_forge { /** * Rename Table * - * @access public - * @param string the old table name - * @param string the new table name + * @param string $table_name Old table name + * @param string $new_table_name New table name * @return bool */ - function rename_table($table_name, $new_table_name) + public function rename_table($table_name, $new_table_name) { - if ($table_name == '' OR $new_table_name == '') + if ($table_name === '' OR $new_table_name === '') { show_error('A table name is required for that operation.'); + return FALSE; + } + elseif ($this->_rename_table === FALSE) + { + return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE; } - $sql = $this->_rename_table($this->db->dbprefix.$table_name, $this->db->dbprefix.$new_table_name); - return $this->db->query($sql); + $result = $this->db->query(sprintf($this->_rename_table, + $this->db->escape_identifiers($this->db->dbprefix.$table_name), + $this->db->escape_identifiers($this->db->dbprefix.$new_table_name)) + ); + + if ($result && ! empty($this->db->data_cache['table_names'])) + { + $key = array_search(strtolower($this->db->dbprefix.$table_name), array_map('strtolower', $this->db->data_cache['table_names']), TRUE); + if ($key !== FALSE) + { + $this->db->data_cache['table_names'][$key] = $this->db->dbprefix.$new_table_name; + } + } + + return $result; } // -------------------------------------------------------------------- @@ -244,43 +555,44 @@ class CI_DB_forge { /** * Column Add * - * @access public - * @param string the table name - * @param string the column name - * @param string the column definition + * @todo Remove deprecated $_after option in 3.1+ + * @param string $table Table name + * @param array $field Column definition + * @param string $_after Column for AFTER clause (deprecated) * @return bool */ - function add_column($table = '', $field = array(), $after_field = '') + public function add_column($table, $field, $_after = NULL) { - if ($table == '') + // Work-around for literal column definitions + is_array($field) OR $field = array($field); + + foreach (array_keys($field) as $k) { - 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) + // Backwards-compatibility work-around for MySQL/CUBRID AFTER clause (remove in 3.1+) + if ($_after !== NULL && is_array($field[$k]) && ! isset($field[$k]['after'])) { - show_error('Field information is required.'); + $field[$k]['after'] = $_after; } - $sql = $this->_alter_table('ADD', $this->db->dbprefix.$table, $this->fields, $after_field); + $this->add_field(array($k => $field[$k])); + } - $this->_reset(); + $sqls = $this->_alter_table('ADD', $this->db->dbprefix.$table, $this->_process_fields()); + $this->_reset(); + if ($sqls === FALSE) + { + return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE; + } - if ($this->db->query($sql) === FALSE) + for ($i = 0, $c = count($sqls); $i < $c; $i++) + { + if ($this->db->query($sqls[$i]) === FALSE) { return FALSE; } } return TRUE; - } // -------------------------------------------------------------------- @@ -288,25 +600,17 @@ class CI_DB_forge { /** * Column Drop * - * @access public - * @param string the table name - * @param string the column name + * @param string $table Table name + * @param string $column_name Column name * @return bool */ - function drop_column($table = '', $column_name = '') + public 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); + if ($sql === FALSE) + { + return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE; + } return $this->db->query($sql); } @@ -316,42 +620,35 @@ class CI_DB_forge { /** * Column Modify * - * @access public - * @param string the table name - * @param string the column name - * @param string the column definition + * @param string $table Table name + * @param string $field Column definition * @return bool */ - function modify_column($table = '', $field = array()) + public function modify_column($table, $field) { - if ($table == '') + // Work-around for literal column definitions + is_array($field) OR $field = array($field); + + foreach (array_keys($field) as $k) { - show_error('A table name is required for that operation.'); + $this->add_field(array($k => $field[$k])); } - // 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 (count($this->fields) === 0) { - // If no name provided, use the current name - if ( ! isset($field[$k]['name'])) - { - $field[$k]['name'] = $k; - } + show_error('Field information is required.'); + } - $this->add_field(array($k => $field[$k])); + $sqls = $this->_alter_table('CHANGE', $this->db->dbprefix.$table, $this->_process_fields()); + $this->_reset(); + if ($sqls === FALSE) + { + return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE; + } - 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) + for ($i = 0, $c = count($sqls); $i < $c; $i++) + { + if ($this->db->query($sqls[$i]) === FALSE) { return FALSE; } @@ -362,22 +659,374 @@ class CI_DB_forge { // -------------------------------------------------------------------- + /** + * ALTER TABLE + * + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * @return string|string[] + */ + protected function _alter_table($alter_type, $table, $field) + { + $sql = 'ALTER TABLE '.$this->db->escape_identifiers($table).' '; + + // DROP has everything it needs now. + if ($alter_type === 'DROP') + { + return $sql.'DROP COLUMN '.$this->db->escape_identifiers($field); + } + + $sql .= ($alter_type === 'ADD') + ? 'ADD ' + : $alter_type.' COLUMN '; + + $sqls = array(); + for ($i = 0, $c = count($field); $i < $c; $i++) + { + $sqls[] = $sql + .($field[$i]['_literal'] !== FALSE ? $field[$i]['_literal'] : $this->_process_column($field[$i])); + } + + return $sqls; + } + + // -------------------------------------------------------------------- + + /** + * Process fields + * + * @param bool $create_table + * @return array + */ + protected function _process_fields($create_table = FALSE) + { + $fields = array(); + + foreach ($this->fields as $key => $attributes) + { + if (is_int($key) && ! is_array($attributes)) + { + $fields[] = array('_literal' => $attributes); + continue; + } + + $attributes = array_change_key_case($attributes, CASE_UPPER); + + if ($create_table === TRUE && empty($attributes['TYPE'])) + { + continue; + } + + isset($attributes['TYPE']) && $this->_attr_type($attributes); + + $field = array( + 'name' => $key, + 'new_name' => isset($attributes['NAME']) ? $attributes['NAME'] : NULL, + 'type' => isset($attributes['TYPE']) ? $attributes['TYPE'] : NULL, + 'length' => '', + 'unsigned' => '', + 'null' => '', + 'unique' => '', + 'default' => '', + 'auto_increment' => '', + '_literal' => FALSE + ); + + isset($attributes['TYPE']) && $this->_attr_unsigned($attributes, $field); + + if ($create_table === FALSE) + { + if (isset($attributes['AFTER'])) + { + $field['after'] = $attributes['AFTER']; + } + elseif (isset($attributes['FIRST'])) + { + $field['first'] = (bool) $attributes['FIRST']; + } + } + + $this->_attr_default($attributes, $field); + + if (isset($attributes['NULL'])) + { + if ($attributes['NULL'] === TRUE) + { + $field['null'] = empty($this->_null) ? '' : ' '.$this->_null; + } + else + { + $field['null'] = ' NOT NULL'; + } + } + elseif ($create_table === TRUE) + { + $field['null'] = ' NOT NULL'; + } + + $this->_attr_auto_increment($attributes, $field); + $this->_attr_unique($attributes, $field); + + if (isset($attributes['COMMENT'])) + { + $field['comment'] = $this->db->escape($attributes['COMMENT']); + } + + if (isset($attributes['TYPE']) && ! empty($attributes['CONSTRAINT'])) + { + switch (strtoupper($attributes['TYPE'])) + { + case 'ENUM': + case 'SET': + $attributes['CONSTRAINT'] = $this->db->escape($attributes['CONSTRAINT']); + default: + $field['length'] = is_array($attributes['CONSTRAINT']) + ? '('.implode(',', $attributes['CONSTRAINT']).')' + : '('.$attributes['CONSTRAINT'].')'; + break; + } + } + + $fields[] = $field; + } + + return $fields; + } + + // -------------------------------------------------------------------- + + /** + * Process column + * + * @param array $field + * @return string + */ + protected function _process_column($field) + { + return $this->db->escape_identifiers($field['name']) + .' '.$field['type'].$field['length'] + .$field['unsigned'] + .$field['default'] + .$field['null'] + .$field['auto_increment'] + .$field['unique']; + } + + // -------------------------------------------------------------------- + + /** + * Field attribute TYPE + * + * Performs a data type mapping between different databases. + * + * @param array &$attributes + * @return void + */ + protected function _attr_type(&$attributes) + { + // Usually overridden by drivers + } + + // -------------------------------------------------------------------- + + /** + * Field attribute UNSIGNED + * + * Depending on the _unsigned property value: + * + * - TRUE will always set $field['unsigned'] to 'UNSIGNED' + * - FALSE will always set $field['unsigned'] to '' + * - array(TYPE) will set $field['unsigned'] to 'UNSIGNED', + * if $attributes['TYPE'] is found in the array + * - array(TYPE => UTYPE) will change $field['type'], + * from TYPE to UTYPE in case of a match + * + * @param array &$attributes + * @param array &$field + * @return void + */ + protected function _attr_unsigned(&$attributes, &$field) + { + if (empty($attributes['UNSIGNED']) OR $attributes['UNSIGNED'] !== TRUE) + { + return; + } + + // Reset the attribute in order to avoid issues if we do type conversion + $attributes['UNSIGNED'] = FALSE; + + if (is_array($this->_unsigned)) + { + foreach (array_keys($this->_unsigned) as $key) + { + if (is_int($key) && strcasecmp($attributes['TYPE'], $this->_unsigned[$key]) === 0) + { + $field['unsigned'] = ' UNSIGNED'; + return; + } + elseif (is_string($key) && strcasecmp($attributes['TYPE'], $key) === 0) + { + $field['type'] = $key; + return; + } + } + + return; + } + + $field['unsigned'] = ($this->_unsigned === TRUE) ? ' UNSIGNED' : ''; + } + + // -------------------------------------------------------------------- + + /** + * Field attribute DEFAULT + * + * @param array &$attributes + * @param array &$field + * @return void + */ + protected function _attr_default(&$attributes, &$field) + { + if ($this->_default === FALSE) + { + return; + } + + if (array_key_exists('DEFAULT', $attributes)) + { + if ($attributes['DEFAULT'] === NULL) + { + $field['default'] = empty($this->_null) ? '' : $this->_default.$this->_null; + + // Override the NULL attribute if that's our default + $attributes['NULL'] = TRUE; + $field['null'] = empty($this->_null) ? '' : ' '.$this->_null; + } + else + { + $field['default'] = $this->_default.$this->db->escape($attributes['DEFAULT']); + } + } + } + + // -------------------------------------------------------------------- + + /** + * Field attribute UNIQUE + * + * @param array &$attributes + * @param array &$field + * @return void + */ + protected function _attr_unique(&$attributes, &$field) + { + if ( ! empty($attributes['UNIQUE']) && $attributes['UNIQUE'] === TRUE) + { + $field['unique'] = ' UNIQUE'; + } + } + + // -------------------------------------------------------------------- + + /** + * Field attribute AUTO_INCREMENT + * + * @param array &$attributes + * @param array &$field + * @return void + */ + protected function _attr_auto_increment(&$attributes, &$field) + { + if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE && stripos($field['type'], 'int') !== FALSE) + { + $field['auto_increment'] = ' AUTO_INCREMENT'; + } + } + + // -------------------------------------------------------------------- + + /** + * Process primary keys + * + * @param string $table Table name + * @return string + */ + protected function _process_primary_keys($table) + { + $sql = ''; + + for ($i = 0, $c = count($this->primary_keys); $i < $c; $i++) + { + if ( ! isset($this->fields[$this->primary_keys[$i]])) + { + unset($this->primary_keys[$i]); + } + } + + if (count($this->primary_keys) > 0) + { + $sql .= ",\n\tCONSTRAINT ".$this->db->escape_identifiers('pk_'.$table) + .' PRIMARY KEY('.implode(', ', $this->db->escape_identifiers($this->primary_keys)).')'; + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Process indexes + * + * @param string $table + * @return string + */ + protected function _process_indexes($table) + { + $sqls = array(); + + for ($i = 0, $c = count($this->keys); $i < $c; $i++) + { + if (is_array($this->keys[$i])) + { + for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++) + { + if ( ! isset($this->fields[$this->keys[$i][$i2]])) + { + unset($this->keys[$i][$i2]); + continue; + } + } + } + elseif ( ! isset($this->fields[$this->keys[$i]])) + { + unset($this->keys[$i]); + continue; + } + + is_array($this->keys[$i]) OR $this->keys[$i] = array($this->keys[$i]); + + $sqls[] = 'CREATE INDEX '.$this->db->escape_identifiers($table.'_'.implode('_', $this->keys[$i])) + .' ON '.$this->db->escape_identifiers($table) + .' ('.implode(', ', $this->db->escape_identifiers($this->keys[$i])).');'; + } + + return $sqls; + } + + // -------------------------------------------------------------------- + /** * Reset * * Resets table creation vars * - * @access private * @return void */ - function _reset() + protected function _reset() { - $this->fields = array(); - $this->keys = array(); - $this->primary_keys = array(); + $this->fields = $this->keys = $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/src/system/database/DB_query_builder.php b/src/system/database/DB_query_builder.php new file mode 100644 index 00000000..c862d937 --- /dev/null +++ b/src/system/database/DB_query_builder.php @@ -0,0 +1,2782 @@ +_protect_identifiers; + + foreach ($select as $val) + { + $val = trim($val); + + if ($val !== '') + { + $this->qb_select[] = $val; + $this->qb_no_escape[] = $escape; + + if ($this->qb_caching === TRUE) + { + $this->qb_cache_select[] = $val; + $this->qb_cache_exists[] = 'select'; + $this->qb_cache_no_escape[] = $escape; + } + } + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Select Max + * + * Generates a SELECT MAX(field) portion of a query + * + * @param string the field + * @param string an alias + * @return CI_DB_query_builder + */ + public function select_max($select = '', $alias = '') + { + return $this->_max_min_avg_sum($select, $alias, 'MAX'); + } + + // -------------------------------------------------------------------- + + /** + * Select Min + * + * Generates a SELECT MIN(field) portion of a query + * + * @param string the field + * @param string an alias + * @return CI_DB_query_builder + */ + public function select_min($select = '', $alias = '') + { + return $this->_max_min_avg_sum($select, $alias, 'MIN'); + } + + // -------------------------------------------------------------------- + + /** + * Select Average + * + * Generates a SELECT AVG(field) portion of a query + * + * @param string the field + * @param string an alias + * @return CI_DB_query_builder + */ + public function select_avg($select = '', $alias = '') + { + return $this->_max_min_avg_sum($select, $alias, 'AVG'); + } + + // -------------------------------------------------------------------- + + /** + * Select Sum + * + * Generates a SELECT SUM(field) portion of a query + * + * @param string the field + * @param string an alias + * @return CI_DB_query_builder + */ + public function select_sum($select = '', $alias = '') + { + return $this->_max_min_avg_sum($select, $alias, 'SUM'); + } + + // -------------------------------------------------------------------- + + /** + * SELECT [MAX|MIN|AVG|SUM]() + * + * @used-by select_max() + * @used-by select_min() + * @used-by select_avg() + * @used-by select_sum() + * + * @param string $select Field name + * @param string $alias + * @param string $type + * @return CI_DB_query_builder + */ + protected function _max_min_avg_sum($select = '', $alias = '', $type = 'MAX') + { + if ( ! is_string($select) OR $select === '') + { + $this->display_error('db_invalid_query'); + } + + $type = strtoupper($type); + + if ( ! in_array($type, array('MAX', 'MIN', 'AVG', 'SUM'))) + { + show_error('Invalid function type: '.$type); + } + + if ($alias === '') + { + $alias = $this->_create_alias_from_table(trim($select)); + } + + $sql = $type.'('.$this->protect_identifiers(trim($select)).') AS '.$this->escape_identifiers(trim($alias)); + + $this->qb_select[] = $sql; + $this->qb_no_escape[] = NULL; + + if ($this->qb_caching === TRUE) + { + $this->qb_cache_select[] = $sql; + $this->qb_cache_exists[] = 'select'; + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Determines the alias name based on the table + * + * @param string $item + * @return string + */ + protected function _create_alias_from_table($item) + { + if (strpos($item, '.') !== FALSE) + { + $item = explode('.', $item); + return end($item); + } + + return $item; + } + + // -------------------------------------------------------------------- + + /** + * DISTINCT + * + * Sets a flag which tells the query string compiler to add DISTINCT + * + * @param bool $val + * @return CI_DB_query_builder + */ + public function distinct($val = TRUE) + { + $this->qb_distinct = is_bool($val) ? $val : TRUE; + return $this; + } + + // -------------------------------------------------------------------- + + /** + * From + * + * Generates the FROM portion of the query + * + * @param mixed $from can be a string or array + * @return CI_DB_query_builder + */ + public function from($from) + { + foreach ((array) $from as $val) + { + if (strpos($val, ',') !== FALSE) + { + foreach (explode(',', $val) as $v) + { + $v = trim($v); + $this->_track_aliases($v); + + $this->qb_from[] = $v = $this->protect_identifiers($v, TRUE, NULL, FALSE); + + if ($this->qb_caching === TRUE) + { + $this->qb_cache_from[] = $v; + $this->qb_cache_exists[] = 'from'; + } + } + } + else + { + $val = trim($val); + + // Extract any aliases that might exist. We use this information + // in the protect_identifiers to know whether to add a table prefix + $this->_track_aliases($val); + + $this->qb_from[] = $val = $this->protect_identifiers($val, TRUE, NULL, FALSE); + + if ($this->qb_caching === TRUE) + { + $this->qb_cache_from[] = $val; + $this->qb_cache_exists[] = 'from'; + } + } + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * JOIN + * + * Generates the JOIN portion of the query + * + * @param string + * @param string the join condition + * @param string the type of join + * @param string whether not to try to escape identifiers + * @return CI_DB_query_builder + */ + public function join($table, $cond, $type = '', $escape = NULL) + { + if ($type !== '') + { + $type = strtoupper(trim($type)); + + if ( ! in_array($type, array('LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER'), TRUE)) + { + $type = ''; + } + else + { + $type .= ' '; + } + } + + // Extract any aliases that might exist. We use this information + // in the protect_identifiers to know whether to add a table prefix + $this->_track_aliases($table); + + is_bool($escape) OR $escape = $this->_protect_identifiers; + + if ( ! $this->_has_operator($cond)) + { + $cond = ' USING ('.($escape ? $this->escape_identifiers($cond) : $cond).')'; + } + elseif ($escape === FALSE) + { + $cond = ' ON '.$cond; + } + else + { + // Split multiple conditions + if (preg_match_all('/\sAND\s|\sOR\s/i', $cond, $joints, PREG_OFFSET_CAPTURE)) + { + $conditions = array(); + $joints = $joints[0]; + array_unshift($joints, array('', 0)); + + for ($i = count($joints) - 1, $pos = strlen($cond); $i >= 0; $i--) + { + $joints[$i][1] += strlen($joints[$i][0]); // offset + $conditions[$i] = substr($cond, $joints[$i][1], $pos - $joints[$i][1]); + $pos = $joints[$i][1] - strlen($joints[$i][0]); + $joints[$i] = $joints[$i][0]; + } + } + else + { + $conditions = array($cond); + $joints = array(''); + } + + $cond = ' ON '; + for ($i = 0, $c = count($conditions); $i < $c; $i++) + { + $operator = $this->_get_operator($conditions[$i]); + $cond .= $joints[$i]; + $cond .= preg_match("/(\(*)?([\[\]\w\.'-]+)".preg_quote($operator)."(.*)/i", $conditions[$i], $match) + ? $match[1].$this->protect_identifiers($match[2]).$operator.$this->protect_identifiers($match[3]) + : $conditions[$i]; + } + } + + // Do we want to escape the table name? + if ($escape === TRUE) + { + $table = $this->protect_identifiers($table, TRUE, NULL, FALSE); + } + + // Assemble the JOIN statement + $this->qb_join[] = $join = $type.'JOIN '.$table.$cond; + + if ($this->qb_caching === TRUE) + { + $this->qb_cache_join[] = $join; + $this->qb_cache_exists[] = 'join'; + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * WHERE + * + * Generates the WHERE portion of the query. + * Separates multiple calls with 'AND'. + * + * @param mixed + * @param mixed + * @param bool + * @return CI_DB_query_builder + */ + public function where($key, $value = NULL, $escape = NULL) + { + return $this->_wh('qb_where', $key, $value, 'AND ', $escape); + } + + // -------------------------------------------------------------------- + + /** + * OR WHERE + * + * Generates the WHERE portion of the query. + * Separates multiple calls with 'OR'. + * + * @param mixed + * @param mixed + * @param bool + * @return CI_DB_query_builder + */ + public function or_where($key, $value = NULL, $escape = NULL) + { + return $this->_wh('qb_where', $key, $value, 'OR ', $escape); + } + + // -------------------------------------------------------------------- + + /** + * WHERE, HAVING + * + * @used-by where() + * @used-by or_where() + * @used-by having() + * @used-by or_having() + * + * @param string $qb_key 'qb_where' or 'qb_having' + * @param mixed $key + * @param mixed $value + * @param string $type + * @param bool $escape + * @return CI_DB_query_builder + */ + protected function _wh($qb_key, $key, $value = NULL, $type = 'AND ', $escape = NULL) + { + $qb_cache_key = ($qb_key === 'qb_having') ? 'qb_cache_having' : 'qb_cache_where'; + + if ( ! is_array($key)) + { + $key = array($key => $value); + } + + // If the escape value was not set will base it on the global setting + is_bool($escape) OR $escape = $this->_protect_identifiers; + + foreach ($key as $k => $v) + { + $prefix = (count($this->$qb_key) === 0 && count($this->$qb_cache_key) === 0) + ? $this->_group_get_type('') + : $this->_group_get_type($type); + + if ($v !== NULL) + { + if ($escape === TRUE) + { + $v = ' '.$this->escape($v); + } + + if ( ! $this->_has_operator($k)) + { + $k .= ' = '; + } + } + elseif ( ! $this->_has_operator($k)) + { + // value appears not to have been set, assign the test to IS NULL + $k .= ' IS NULL'; + } + elseif (preg_match('/\s*(!?=|<>|IS(?:\s+NOT)?)\s*$/i', $k, $match, PREG_OFFSET_CAPTURE)) + { + $k = substr($k, 0, $match[0][1]).($match[1][0] === '=' ? ' IS NULL' : ' IS NOT NULL'); + } + + $this->{$qb_key}[] = array('condition' => $prefix.$k.$v, 'escape' => $escape); + if ($this->qb_caching === TRUE) + { + $this->{$qb_cache_key}[] = array('condition' => $prefix.$k.$v, 'escape' => $escape); + $this->qb_cache_exists[] = substr($qb_key, 3); + } + + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * WHERE IN + * + * Generates a WHERE field IN('item', 'item') SQL query, + * joined with 'AND' if appropriate. + * + * @param string $key The field to search + * @param array $values The values searched on + * @param bool $escape + * @return CI_DB_query_builder + */ + public function where_in($key = NULL, $values = NULL, $escape = NULL) + { + return $this->_where_in($key, $values, FALSE, 'AND ', $escape); + } + + // -------------------------------------------------------------------- + + /** + * OR WHERE IN + * + * Generates a WHERE field IN('item', 'item') SQL query, + * joined with 'OR' if appropriate. + * + * @param string $key The field to search + * @param array $values The values searched on + * @param bool $escape + * @return CI_DB_query_builder + */ + public function or_where_in($key = NULL, $values = NULL, $escape = NULL) + { + return $this->_where_in($key, $values, FALSE, 'OR ', $escape); + } + + // -------------------------------------------------------------------- + + /** + * WHERE NOT IN + * + * Generates a WHERE field NOT IN('item', 'item') SQL query, + * joined with 'AND' if appropriate. + * + * @param string $key The field to search + * @param array $values The values searched on + * @param bool $escape + * @return CI_DB_query_builder + */ + public function where_not_in($key = NULL, $values = NULL, $escape = NULL) + { + return $this->_where_in($key, $values, TRUE, 'AND ', $escape); + } + + // -------------------------------------------------------------------- + + /** + * OR WHERE NOT IN + * + * Generates a WHERE field NOT IN('item', 'item') SQL query, + * joined with 'OR' if appropriate. + * + * @param string $key The field to search + * @param array $values The values searched on + * @param bool $escape + * @return CI_DB_query_builder + */ + public function or_where_not_in($key = NULL, $values = NULL, $escape = NULL) + { + return $this->_where_in($key, $values, TRUE, 'OR ', $escape); + } + + // -------------------------------------------------------------------- + + /** + * Internal WHERE IN + * + * @used-by where_in() + * @used-by or_where_in() + * @used-by where_not_in() + * @used-by or_where_not_in() + * + * @param string $key The field to search + * @param array $values The values searched on + * @param bool $not If the statement would be IN or NOT IN + * @param string $type + * @param bool $escape + * @return CI_DB_query_builder + */ + protected function _where_in($key = NULL, $values = NULL, $not = FALSE, $type = 'AND ', $escape = NULL) + { + if ($key === NULL OR $values === NULL) + { + return $this; + } + + if ( ! is_array($values)) + { + $values = array($values); + } + + is_bool($escape) OR $escape = $this->_protect_identifiers; + + $not = ($not) ? ' NOT' : ''; + + if ($escape === TRUE) + { + $where_in = array(); + foreach ($values as $value) + { + $where_in[] = $this->escape($value); + } + } + else + { + $where_in = array_values($values); + } + + $prefix = (count($this->qb_where) === 0 && count($this->qb_cache_where) === 0) + ? $this->_group_get_type('') + : $this->_group_get_type($type); + + $where_in = array( + 'condition' => $prefix.$key.$not.' IN('.implode(', ', $where_in).')', + 'escape' => $escape + ); + + $this->qb_where[] = $where_in; + if ($this->qb_caching === TRUE) + { + $this->qb_cache_where[] = $where_in; + $this->qb_cache_exists[] = 'where'; + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * LIKE + * + * Generates a %LIKE% portion of the query. + * Separates multiple calls with 'AND'. + * + * @param mixed $field + * @param string $match + * @param string $side + * @param bool $escape + * @return CI_DB_query_builder + */ + public function like($field, $match = '', $side = 'both', $escape = NULL) + { + return $this->_like($field, $match, 'AND ', $side, '', $escape); + } + + // -------------------------------------------------------------------- + + /** + * NOT LIKE + * + * Generates a NOT LIKE portion of the query. + * Separates multiple calls with 'AND'. + * + * @param mixed $field + * @param string $match + * @param string $side + * @param bool $escape + * @return CI_DB_query_builder + */ + public function not_like($field, $match = '', $side = 'both', $escape = NULL) + { + return $this->_like($field, $match, 'AND ', $side, 'NOT', $escape); + } + + // -------------------------------------------------------------------- + + /** + * OR LIKE + * + * Generates a %LIKE% portion of the query. + * Separates multiple calls with 'OR'. + * + * @param mixed $field + * @param string $match + * @param string $side + * @param bool $escape + * @return CI_DB_query_builder + */ + public function or_like($field, $match = '', $side = 'both', $escape = NULL) + { + return $this->_like($field, $match, 'OR ', $side, '', $escape); + } + + // -------------------------------------------------------------------- + + /** + * OR NOT LIKE + * + * Generates a NOT LIKE portion of the query. + * Separates multiple calls with 'OR'. + * + * @param mixed $field + * @param string $match + * @param string $side + * @param bool $escape + * @return CI_DB_query_builder + */ + public function or_not_like($field, $match = '', $side = 'both', $escape = NULL) + { + return $this->_like($field, $match, 'OR ', $side, 'NOT', $escape); + } + + // -------------------------------------------------------------------- + + /** + * Internal LIKE + * + * @used-by like() + * @used-by or_like() + * @used-by not_like() + * @used-by or_not_like() + * + * @param mixed $field + * @param string $match + * @param string $type + * @param string $side + * @param string $not + * @param bool $escape + * @return CI_DB_query_builder + */ + protected function _like($field, $match = '', $type = 'AND ', $side = 'both', $not = '', $escape = NULL) + { + if ( ! is_array($field)) + { + $field = array($field => $match); + } + + is_bool($escape) OR $escape = $this->_protect_identifiers; + // lowercase $side in case somebody writes e.g. 'BEFORE' instead of 'before' (doh) + $side = strtolower($side); + + foreach ($field as $k => $v) + { + $prefix = (count($this->qb_where) === 0 && count($this->qb_cache_where) === 0) + ? $this->_group_get_type('') : $this->_group_get_type($type); + + if ($escape === TRUE) + { + $v = $this->escape_like_str($v); + } + + if ($side === 'none') + { + $like_statement = "{$prefix} {$k} {$not} LIKE '{$v}'"; + } + elseif ($side === 'before') + { + $like_statement = "{$prefix} {$k} {$not} LIKE '%{$v}'"; + } + elseif ($side === 'after') + { + $like_statement = "{$prefix} {$k} {$not} LIKE '{$v}%'"; + } + else + { + $like_statement = "{$prefix} {$k} {$not} LIKE '%{$v}%'"; + } + + // some platforms require an escape sequence definition for LIKE wildcards + if ($escape === TRUE && $this->_like_escape_str !== '') + { + $like_statement .= sprintf($this->_like_escape_str, $this->_like_escape_chr); + } + + $this->qb_where[] = array('condition' => $like_statement, 'escape' => $escape); + if ($this->qb_caching === TRUE) + { + $this->qb_cache_where[] = array('condition' => $like_statement, 'escape' => $escape); + $this->qb_cache_exists[] = 'where'; + } + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Starts a query group. + * + * @param string $not (Internal use only) + * @param string $type (Internal use only) + * @return CI_DB_query_builder + */ + public function group_start($not = '', $type = 'AND ') + { + $type = $this->_group_get_type($type); + + $this->qb_where_group_started = TRUE; + $prefix = (count($this->qb_where) === 0 && count($this->qb_cache_where) === 0) ? '' : $type; + $where = array( + 'condition' => $prefix.$not.str_repeat(' ', ++$this->qb_where_group_count).' (', + 'escape' => FALSE + ); + + $this->qb_where[] = $where; + if ($this->qb_caching) + { + $this->qb_cache_where[] = $where; + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Starts a query group, but ORs the group + * + * @return CI_DB_query_builder + */ + public function or_group_start() + { + return $this->group_start('', 'OR '); + } + + // -------------------------------------------------------------------- + + /** + * Starts a query group, but NOTs the group + * + * @return CI_DB_query_builder + */ + public function not_group_start() + { + return $this->group_start('NOT ', 'AND '); + } + + // -------------------------------------------------------------------- + + /** + * Starts a query group, but OR NOTs the group + * + * @return CI_DB_query_builder + */ + public function or_not_group_start() + { + return $this->group_start('NOT ', 'OR '); + } + + // -------------------------------------------------------------------- + + /** + * Ends a query group + * + * @return CI_DB_query_builder + */ + public function group_end() + { + $this->qb_where_group_started = FALSE; + $where = array( + 'condition' => str_repeat(' ', $this->qb_where_group_count--).')', + 'escape' => FALSE + ); + + $this->qb_where[] = $where; + if ($this->qb_caching) + { + $this->qb_cache_where[] = $where; + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Group_get_type + * + * @used-by group_start() + * @used-by _like() + * @used-by _wh() + * @used-by _where_in() + * + * @param string $type + * @return string + */ + protected function _group_get_type($type) + { + if ($this->qb_where_group_started) + { + $type = ''; + $this->qb_where_group_started = FALSE; + } + + return $type; + } + + // -------------------------------------------------------------------- + + /** + * GROUP BY + * + * @param string $by + * @param bool $escape + * @return CI_DB_query_builder + */ + public function group_by($by, $escape = NULL) + { + is_bool($escape) OR $escape = $this->_protect_identifiers; + + if (is_string($by)) + { + $by = ($escape === TRUE) + ? explode(',', $by) + : array($by); + } + + foreach ($by as $val) + { + $val = trim($val); + + if ($val !== '') + { + $val = array('field' => $val, 'escape' => $escape); + + $this->qb_groupby[] = $val; + if ($this->qb_caching === TRUE) + { + $this->qb_cache_groupby[] = $val; + $this->qb_cache_exists[] = 'groupby'; + } + } + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * HAVING + * + * Separates multiple calls with 'AND'. + * + * @param string $key + * @param string $value + * @param bool $escape + * @return CI_DB_query_builder + */ + public function having($key, $value = NULL, $escape = NULL) + { + return $this->_wh('qb_having', $key, $value, 'AND ', $escape); + } + + // -------------------------------------------------------------------- + + /** + * OR HAVING + * + * Separates multiple calls with 'OR'. + * + * @param string $key + * @param string $value + * @param bool $escape + * @return CI_DB_query_builder + */ + public function or_having($key, $value = NULL, $escape = NULL) + { + return $this->_wh('qb_having', $key, $value, 'OR ', $escape); + } + + // -------------------------------------------------------------------- + + /** + * ORDER BY + * + * @param string $orderby + * @param string $direction ASC, DESC or RANDOM + * @param bool $escape + * @return CI_DB_query_builder + */ + public function order_by($orderby, $direction = '', $escape = NULL) + { + $direction = strtoupper(trim($direction)); + + if ($direction === 'RANDOM') + { + $direction = ''; + + // Do we have a seed value? + $orderby = ctype_digit((string) $orderby) + ? sprintf($this->_random_keyword[1], $orderby) + : $this->_random_keyword[0]; + } + elseif (empty($orderby)) + { + return $this; + } + elseif ($direction !== '') + { + $direction = in_array($direction, array('ASC', 'DESC'), TRUE) ? ' '.$direction : ''; + } + + is_bool($escape) OR $escape = $this->_protect_identifiers; + + if ($escape === FALSE) + { + $qb_orderby[] = array('field' => $orderby, 'direction' => $direction, 'escape' => FALSE); + } + else + { + $qb_orderby = array(); + foreach (explode(',', $orderby) as $field) + { + $qb_orderby[] = ($direction === '' && preg_match('/\s+(ASC|DESC)$/i', rtrim($field), $match, PREG_OFFSET_CAPTURE)) + ? array('field' => ltrim(substr($field, 0, $match[0][1])), 'direction' => ' '.$match[1][0], 'escape' => TRUE) + : array('field' => trim($field), 'direction' => $direction, 'escape' => TRUE); + } + } + + $this->qb_orderby = array_merge($this->qb_orderby, $qb_orderby); + if ($this->qb_caching === TRUE) + { + $this->qb_cache_orderby = array_merge($this->qb_cache_orderby, $qb_orderby); + $this->qb_cache_exists[] = 'orderby'; + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * LIMIT + * + * @param int $value LIMIT value + * @param int $offset OFFSET value + * @return CI_DB_query_builder + */ + public function limit($value, $offset = 0) + { + is_null($value) OR $this->qb_limit = (int) $value; + empty($offset) OR $this->qb_offset = (int) $offset; + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Sets the OFFSET value + * + * @param int $offset OFFSET value + * @return CI_DB_query_builder + */ + public function offset($offset) + { + empty($offset) OR $this->qb_offset = (int) $offset; + return $this; + } + + // -------------------------------------------------------------------- + + /** + * LIMIT string + * + * Generates a platform-specific LIMIT clause. + * + * @param string $sql SQL Query + * @return string + */ + protected function _limit($sql) + { + return $sql.' LIMIT '.($this->qb_offset ? $this->qb_offset.', ' : '').$this->qb_limit; + } + + // -------------------------------------------------------------------- + + /** + * The "set" function. + * + * Allows key/value pairs to be set for inserting or updating + * + * @param mixed + * @param string + * @param bool + * @return CI_DB_query_builder + */ + public function set($key, $value = '', $escape = NULL) + { + $key = $this->_object_to_array($key); + + if ( ! is_array($key)) + { + $key = array($key => $value); + } + + is_bool($escape) OR $escape = $this->_protect_identifiers; + + foreach ($key as $k => $v) + { + $this->qb_set[$this->protect_identifiers($k, FALSE, $escape)] = ($escape) + ? $this->escape($v) : $v; + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Get SELECT query string + * + * Compiles a SELECT query string and returns the sql. + * + * @param string the table name to select from (optional) + * @param bool TRUE: resets QB values; FALSE: leave QB values alone + * @return string + */ + public function get_compiled_select($table = '', $reset = TRUE) + { + if ($table !== '') + { + $this->_track_aliases($table); + $this->from($table); + } + + $select = $this->_compile_select(); + + if ($reset === TRUE) + { + $this->_reset_select(); + } + + return $select; + } + + // -------------------------------------------------------------------- + + /** + * Get + * + * Compiles the select statement based on the other functions called + * and runs the query + * + * @param string the table + * @param string the limit clause + * @param string the offset clause + * @return CI_DB_result + */ + public function get($table = '', $limit = NULL, $offset = NULL) + { + if ($table !== '') + { + $this->_track_aliases($table); + $this->from($table); + } + + if ( ! empty($limit)) + { + $this->limit($limit, $offset); + } + + $result = $this->query($this->_compile_select()); + $this->_reset_select(); + return $result; + } + + // -------------------------------------------------------------------- + + /** + * "Count All Results" query + * + * Generates a platform-specific query string that counts all records + * returned by an Query Builder query. + * + * @param string + * @param bool the reset clause + * @return int + */ + public function count_all_results($table = '', $reset = TRUE) + { + if ($table !== '') + { + $this->_track_aliases($table); + $this->from($table); + } + + // ORDER BY usage is often problematic here (most notably + // on Microsoft SQL Server) and ultimately unnecessary + // for selecting COUNT(*) ... + if ( ! empty($this->qb_orderby)) + { + $orderby = $this->qb_orderby; + $this->qb_orderby = NULL; + } + + $result = ($this->qb_distinct === TRUE) + ? $this->query($this->_count_string.$this->protect_identifiers('numrows')."\nFROM (\n".$this->_compile_select()."\n) CI_count_all_results") + : $this->query($this->_compile_select($this->_count_string.$this->protect_identifiers('numrows'))); + + if ($reset === TRUE) + { + $this->_reset_select(); + } + // If we've previously reset the qb_orderby values, get them back + elseif ( ! isset($this->qb_orderby)) + { + $this->qb_orderby = $orderby; + } + + if ($result->num_rows() === 0) + { + return 0; + } + + $row = $result->row(); + return (int) $row->numrows; + } + + // -------------------------------------------------------------------- + + /** + * Get_Where + * + * Allows the where clause, limit and offset to be added directly + * + * @param string $table + * @param string $where + * @param int $limit + * @param int $offset + * @return CI_DB_result + */ + public function get_where($table = '', $where = NULL, $limit = NULL, $offset = NULL) + { + if ($table !== '') + { + $this->from($table); + } + + if ($where !== NULL) + { + $this->where($where); + } + + if ( ! empty($limit)) + { + $this->limit($limit, $offset); + } + + $result = $this->query($this->_compile_select()); + $this->_reset_select(); + return $result; + } + + // -------------------------------------------------------------------- + + /** + * Insert_Batch + * + * Compiles batch insert strings and runs the queries + * + * @param string $table Table to insert into + * @param array $set An associative array of insert values + * @param bool $escape Whether to escape values and identifiers + * @return int Number of rows inserted or FALSE on failure + */ + public function insert_batch($table, $set = NULL, $escape = NULL, $batch_size = 100) + { + if ($set === NULL) + { + if (empty($this->qb_set)) + { + return ($this->db_debug) ? $this->display_error('db_must_use_set') : FALSE; + } + } + else + { + if (empty($set)) + { + return ($this->db_debug) ? $this->display_error('insert_batch() called with no data') : FALSE; + } + + $this->set_insert_batch($set, '', $escape); + } + + if (strlen($table) === 0) + { + if ( ! isset($this->qb_from[0])) + { + return ($this->db_debug) ? $this->display_error('db_must_set_table') : FALSE; + } + + $table = $this->qb_from[0]; + } + + // Batch this baby + $affected_rows = 0; + for ($i = 0, $total = count($this->qb_set); $i < $total; $i += $batch_size) + { + $this->query($this->_insert_batch($this->protect_identifiers($table, TRUE, $escape, FALSE), $this->qb_keys, array_slice($this->qb_set, $i, $batch_size))); + $affected_rows += $this->affected_rows(); + } + + $this->_reset_write(); + return $affected_rows; + } + + // -------------------------------------------------------------------- + + /** + * Insert batch statement + * + * Generates a platform-specific insert string from the supplied data. + * + * @param string $table Table name + * @param array $keys INSERT keys + * @param array $values INSERT values + * @return string + */ + protected function _insert_batch($table, $keys, $values) + { + return 'INSERT INTO '.$table.' ('.implode(', ', $keys).') VALUES '.implode(', ', $values); + } + + // -------------------------------------------------------------------- + + /** + * The "set_insert_batch" function. Allows key/value pairs to be set for batch inserts + * + * @param mixed + * @param string + * @param bool + * @return CI_DB_query_builder + */ + public function set_insert_batch($key, $value = '', $escape = NULL) + { + $key = $this->_object_to_array_batch($key); + + if ( ! is_array($key)) + { + $key = array($key => $value); + } + + is_bool($escape) OR $escape = $this->_protect_identifiers; + + $keys = array_keys($this->_object_to_array(current($key))); + sort($keys); + + foreach ($key as $row) + { + $row = $this->_object_to_array($row); + if (count(array_diff($keys, array_keys($row))) > 0 OR count(array_diff(array_keys($row), $keys)) > 0) + { + // batch function above returns an error on an empty array + $this->qb_set[] = array(); + return; + } + + ksort($row); // puts $row in the same order as our keys + + if ($escape !== FALSE) + { + $clean = array(); + foreach ($row as $value) + { + $clean[] = $this->escape($value); + } + + $row = $clean; + } + + $this->qb_set[] = '('.implode(',', $row).')'; + } + + foreach ($keys as $k) + { + $this->qb_keys[] = $this->protect_identifiers($k, FALSE, $escape); + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Get INSERT query string + * + * Compiles an insert query and returns the sql + * + * @param string the table to insert into + * @param bool TRUE: reset QB values; FALSE: leave QB values alone + * @return string + */ + public function get_compiled_insert($table = '', $reset = TRUE) + { + if ($this->_validate_insert($table) === FALSE) + { + return FALSE; + } + + $sql = $this->_insert( + $this->protect_identifiers( + $this->qb_from[0], TRUE, NULL, FALSE + ), + array_keys($this->qb_set), + array_values($this->qb_set) + ); + + if ($reset === TRUE) + { + $this->_reset_write(); + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Insert + * + * Compiles an insert string and runs the query + * + * @param string the table to insert data into + * @param array an associative array of insert values + * @param bool $escape Whether to escape values and identifiers + * @return bool TRUE on success, FALSE on failure + */ + public function insert($table = '', $set = NULL, $escape = NULL) + { + if ($set !== NULL) + { + $this->set($set, '', $escape); + } + + if ($this->_validate_insert($table) === FALSE) + { + return FALSE; + } + + $sql = $this->_insert( + $this->protect_identifiers( + $this->qb_from[0], TRUE, $escape, FALSE + ), + array_keys($this->qb_set), + array_values($this->qb_set) + ); + + $this->_reset_write(); + return $this->query($sql); + } + + // -------------------------------------------------------------------- + + /** + * Validate Insert + * + * This method is used by both insert() and get_compiled_insert() to + * validate that the there data is actually being set and that table + * has been chosen to be inserted into. + * + * @param string the table to insert data into + * @return string + */ + protected function _validate_insert($table = '') + { + if (count($this->qb_set) === 0) + { + return ($this->db_debug) ? $this->display_error('db_must_use_set') : FALSE; + } + + if ($table !== '') + { + $this->qb_from[0] = $table; + } + elseif ( ! isset($this->qb_from[0])) + { + return ($this->db_debug) ? $this->display_error('db_must_set_table') : FALSE; + } + + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Replace + * + * Compiles an replace into string and runs the query + * + * @param string the table to replace data into + * @param array an associative array of insert values + * @return bool TRUE on success, FALSE on failure + */ + public function replace($table = '', $set = NULL) + { + if ($set !== NULL) + { + $this->set($set); + } + + if (count($this->qb_set) === 0) + { + return ($this->db_debug) ? $this->display_error('db_must_use_set') : FALSE; + } + + if ($table === '') + { + if ( ! isset($this->qb_from[0])) + { + return ($this->db_debug) ? $this->display_error('db_must_set_table') : FALSE; + } + + $table = $this->qb_from[0]; + } + + $sql = $this->_replace($this->protect_identifiers($table, TRUE, NULL, FALSE), array_keys($this->qb_set), array_values($this->qb_set)); + + $this->_reset_write(); + return $this->query($sql); + } + + // -------------------------------------------------------------------- + + /** + * Replace statement + * + * Generates a platform-specific replace string from the supplied data + * + * @param string the table name + * @param array the insert keys + * @param array the insert values + * @return string + */ + protected function _replace($table, $keys, $values) + { + return 'REPLACE INTO '.$table.' ('.implode(', ', $keys).') VALUES ('.implode(', ', $values).')'; + } + + // -------------------------------------------------------------------- + + /** + * FROM tables + * + * Groups tables in FROM clauses if needed, so there is no confusion + * about operator precedence. + * + * Note: This is only used (and overridden) by MySQL and CUBRID. + * + * @return string + */ + protected function _from_tables() + { + return implode(', ', $this->qb_from); + } + + // -------------------------------------------------------------------- + + /** + * Get UPDATE query string + * + * Compiles an update query and returns the sql + * + * @param string the table to update + * @param bool TRUE: reset QB values; FALSE: leave QB values alone + * @return string + */ + public function get_compiled_update($table = '', $reset = TRUE) + { + // Combine any cached components with the current statements + $this->_merge_cache(); + + if ($this->_validate_update($table) === FALSE) + { + return FALSE; + } + + $sql = $this->_update($this->qb_from[0], $this->qb_set); + + if ($reset === TRUE) + { + $this->_reset_write(); + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * UPDATE + * + * Compiles an update string and runs the query. + * + * @param string $table + * @param array $set An associative array of update values + * @param mixed $where + * @param int $limit + * @return bool TRUE on success, FALSE on failure + */ + public function update($table = '', $set = NULL, $where = NULL, $limit = NULL) + { + // Combine any cached components with the current statements + $this->_merge_cache(); + + if ($set !== NULL) + { + $this->set($set); + } + + if ($this->_validate_update($table) === FALSE) + { + return FALSE; + } + + if ($where !== NULL) + { + $this->where($where); + } + + if ( ! empty($limit)) + { + $this->limit($limit); + } + + $sql = $this->_update($this->qb_from[0], $this->qb_set); + $this->_reset_write(); + return $this->query($sql); + } + + // -------------------------------------------------------------------- + + /** + * Validate Update + * + * This method is used by both update() and get_compiled_update() to + * validate that data is actually being set and that a table has been + * chosen to be update. + * + * @param string the table to update data on + * @return bool + */ + protected function _validate_update($table) + { + if (count($this->qb_set) === 0) + { + return ($this->db_debug) ? $this->display_error('db_must_use_set') : FALSE; + } + + if ($table !== '') + { + $this->qb_from = array($this->protect_identifiers($table, TRUE, NULL, FALSE)); + } + elseif ( ! isset($this->qb_from[0])) + { + return ($this->db_debug) ? $this->display_error('db_must_set_table') : FALSE; + } + + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Update_Batch + * + * Compiles an update string and runs the query + * + * @param string the table to retrieve the results from + * @param array an associative array of update values + * @param string the where key + * @return int number of rows affected or FALSE on failure + */ + public function update_batch($table, $set = NULL, $index = NULL, $batch_size = 100) + { + // Combine any cached components with the current statements + $this->_merge_cache(); + + if ($index === NULL) + { + return ($this->db_debug) ? $this->display_error('db_must_use_index') : FALSE; + } + + if ($set === NULL) + { + if (empty($this->qb_set)) + { + return ($this->db_debug) ? $this->display_error('db_must_use_set') : FALSE; + } + } + else + { + if (empty($set)) + { + return ($this->db_debug) ? $this->display_error('update_batch() called with no data') : FALSE; + } + + $this->set_update_batch($set, $index); + } + + if (strlen($table) === 0) + { + if ( ! isset($this->qb_from[0])) + { + return ($this->db_debug) ? $this->display_error('db_must_set_table') : FALSE; + } + + $table = $this->qb_from[0]; + } + + // Batch this baby + $affected_rows = 0; + for ($i = 0, $total = count($this->qb_set); $i < $total; $i += $batch_size) + { + $this->query($this->_update_batch($this->protect_identifiers($table, TRUE, NULL, FALSE), array_slice($this->qb_set, $i, $batch_size), $this->protect_identifiers($index))); + $affected_rows += $this->affected_rows(); + $this->qb_where = array(); + } + + $this->_reset_write(); + return $affected_rows; + } + + // -------------------------------------------------------------------- + + /** + * Update_Batch statement + * + * Generates a platform-specific batch update string from the supplied data + * + * @param string $table Table name + * @param array $values Update data + * @param string $index WHERE key + * @return string + */ + protected function _update_batch($table, $values, $index) + { + $ids = array(); + foreach ($values as $key => $val) + { + $ids[] = $val[$index]; + + foreach (array_keys($val) as $field) + { + if ($field !== $index) + { + $final[$field][] = 'WHEN '.$index.' = '.$val[$index].' THEN '.$val[$field]; + } + } + } + + $cases = ''; + foreach ($final as $k => $v) + { + $cases .= $k." = CASE \n" + .implode("\n", $v)."\n" + .'ELSE '.$k.' END, '; + } + + $this->where($index.' IN('.implode(',', $ids).')', NULL, FALSE); + + return 'UPDATE '.$table.' SET '.substr($cases, 0, -2).$this->_compile_wh('qb_where'); + } + + // -------------------------------------------------------------------- + + /** + * The "set_update_batch" function. Allows key/value pairs to be set for batch updating + * + * @param array + * @param string + * @param bool + * @return CI_DB_query_builder + */ + public function set_update_batch($key, $index = '', $escape = NULL) + { + $key = $this->_object_to_array_batch($key); + + if ( ! is_array($key)) + { + // @todo error + } + + is_bool($escape) OR $escape = $this->_protect_identifiers; + + foreach ($key as $k => $v) + { + $index_set = FALSE; + $clean = array(); + foreach ($v as $k2 => $v2) + { + if ($k2 === $index) + { + $index_set = TRUE; + } + + $clean[$this->protect_identifiers($k2, FALSE, $escape)] = ($escape === FALSE) ? $v2 : $this->escape($v2); + } + + if ($index_set === FALSE) + { + return $this->display_error('db_batch_missing_index'); + } + + $this->qb_set[] = $clean; + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Empty Table + * + * Compiles a delete string and runs "DELETE FROM table" + * + * @param string the table to empty + * @return bool TRUE on success, FALSE on failure + */ + public function empty_table($table = '') + { + if ($table === '') + { + if ( ! isset($this->qb_from[0])) + { + return ($this->db_debug) ? $this->display_error('db_must_set_table') : FALSE; + } + + $table = $this->qb_from[0]; + } + else + { + $table = $this->protect_identifiers($table, TRUE, NULL, FALSE); + } + + $sql = $this->_delete($table); + $this->_reset_write(); + return $this->query($sql); + } + + // -------------------------------------------------------------------- + + /** + * Truncate + * + * Compiles a truncate string and runs the query + * If the database does not support the truncate() command + * This function maps to "DELETE FROM table" + * + * @param string the table to truncate + * @return bool TRUE on success, FALSE on failure + */ + public function truncate($table = '') + { + if ($table === '') + { + if ( ! isset($this->qb_from[0])) + { + return ($this->db_debug) ? $this->display_error('db_must_set_table') : FALSE; + } + + $table = $this->qb_from[0]; + } + else + { + $table = $this->protect_identifiers($table, TRUE, NULL, FALSE); + } + + $sql = $this->_truncate($table); + $this->_reset_write(); + return $this->query($sql); + } + + // -------------------------------------------------------------------- + + /** + * Truncate statement + * + * Generates a platform-specific truncate string from the supplied data + * + * If the database does not support the truncate() command, + * then this method maps to 'DELETE FROM table' + * + * @param string the table name + * @return string + */ + protected function _truncate($table) + { + return 'TRUNCATE '.$table; + } + + // -------------------------------------------------------------------- + + /** + * Get DELETE query string + * + * Compiles a delete query string and returns the sql + * + * @param string the table to delete from + * @param bool TRUE: reset QB values; FALSE: leave QB values alone + * @return string + */ + public function get_compiled_delete($table = '', $reset = TRUE) + { + $this->return_delete_sql = TRUE; + $sql = $this->delete($table, '', NULL, $reset); + $this->return_delete_sql = FALSE; + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Delete + * + * Compiles a delete string and runs the query + * + * @param mixed the table(s) to delete from. String or array + * @param mixed the where clause + * @param mixed the limit clause + * @param bool + * @return mixed + */ + public function delete($table = '', $where = '', $limit = NULL, $reset_data = TRUE) + { + // Combine any cached components with the current statements + $this->_merge_cache(); + + if ($table === '') + { + if ( ! isset($this->qb_from[0])) + { + return ($this->db_debug) ? $this->display_error('db_must_set_table') : FALSE; + } + + $table = $this->qb_from[0]; + } + elseif (is_array($table)) + { + empty($where) && $reset_data = FALSE; + + foreach ($table as $single_table) + { + $this->delete($single_table, $where, $limit, $reset_data); + } + + return; + } + else + { + $table = $this->protect_identifiers($table, TRUE, NULL, FALSE); + } + + if ($where !== '') + { + $this->where($where); + } + + if ( ! empty($limit)) + { + $this->limit($limit); + } + + if (count($this->qb_where) === 0) + { + return ($this->db_debug) ? $this->display_error('db_del_must_use_where') : FALSE; + } + + $sql = $this->_delete($table); + if ($reset_data) + { + $this->_reset_write(); + } + + return ($this->return_delete_sql === TRUE) ? $sql : $this->query($sql); + } + + // -------------------------------------------------------------------- + + /** + * Delete statement + * + * Generates a platform-specific delete string from the supplied data + * + * @param string the table name + * @return string + */ + protected function _delete($table) + { + return 'DELETE FROM '.$table.$this->_compile_wh('qb_where') + .($this->qb_limit ? ' LIMIT '.$this->qb_limit : ''); + } + + // -------------------------------------------------------------------- + + /** + * DB Prefix + * + * Prepends a database prefix if one exists in configuration + * + * @param string the table + * @return string + */ + public function dbprefix($table = '') + { + if ($table === '') + { + $this->display_error('db_table_name_required'); + } + + return $this->dbprefix.$table; + } + + // -------------------------------------------------------------------- + + /** + * Set DB Prefix + * + * Set's the DB Prefix to something new without needing to reconnect + * + * @param string the prefix + * @return string + */ + public function set_dbprefix($prefix = '') + { + return $this->dbprefix = $prefix; + } + + // -------------------------------------------------------------------- + + /** + * Track Aliases + * + * Used to track SQL statements written with aliased tables. + * + * @param string The table to inspect + * @return string + */ + protected function _track_aliases($table) + { + if (is_array($table)) + { + foreach ($table as $t) + { + $this->_track_aliases($t); + } + return; + } + + // Does the string contain a comma? If so, we need to separate + // the string into discreet statements + if (strpos($table, ',') !== FALSE) + { + return $this->_track_aliases(explode(',', $table)); + } + + // if a table alias is used we can recognize it by a space + if (strpos($table, ' ') !== FALSE) + { + // if the alias is written with the AS keyword, remove it + $table = preg_replace('/\s+AS\s+/i', ' ', $table); + + // Grab the alias + $table = trim(strrchr($table, ' ')); + + // Store the alias, if it doesn't already exist + if ( ! in_array($table, $this->qb_aliased_tables)) + { + $this->qb_aliased_tables[] = $table; + } + } + } + + // -------------------------------------------------------------------- + + /** + * Compile the SELECT statement + * + * Generates a query string based on which functions were used. + * Should not be called directly. + * + * @param bool $select_override + * @return string + */ + protected function _compile_select($select_override = FALSE) + { + // Combine any cached components with the current statements + $this->_merge_cache(); + + // Write the "select" portion of the query + if ($select_override !== FALSE) + { + $sql = $select_override; + } + else + { + $sql = ( ! $this->qb_distinct) ? 'SELECT ' : 'SELECT DISTINCT '; + + if (count($this->qb_select) === 0) + { + $sql .= '*'; + } + else + { + // Cycle through the "select" portion of the query and prep each column name. + // The reason we protect identifiers here rather than in the select() function + // is because until the user calls the from() function we don't know if there are aliases + foreach ($this->qb_select as $key => $val) + { + $no_escape = isset($this->qb_no_escape[$key]) ? $this->qb_no_escape[$key] : NULL; + $this->qb_select[$key] = $this->protect_identifiers($val, FALSE, $no_escape); + } + + $sql .= implode(', ', $this->qb_select); + } + } + + // Write the "FROM" portion of the query + if (count($this->qb_from) > 0) + { + $sql .= "\nFROM ".$this->_from_tables(); + } + + // Write the "JOIN" portion of the query + if (count($this->qb_join) > 0) + { + $sql .= "\n".implode("\n", $this->qb_join); + } + + $sql .= $this->_compile_wh('qb_where') + .$this->_compile_group_by() + .$this->_compile_wh('qb_having') + .$this->_compile_order_by(); // ORDER BY + + // LIMIT + if ($this->qb_limit) + { + return $this->_limit($sql."\n"); + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Compile WHERE, HAVING statements + * + * Escapes identifiers in WHERE and HAVING statements at execution time. + * + * Required so that aliases are tracked properly, regardless of whether + * where(), or_where(), having(), or_having are called prior to from(), + * join() and dbprefix is added only if needed. + * + * @param string $qb_key 'qb_where' or 'qb_having' + * @return string SQL statement + */ + protected function _compile_wh($qb_key) + { + if (count($this->$qb_key) > 0) + { + for ($i = 0, $c = count($this->$qb_key); $i < $c; $i++) + { + // Is this condition already compiled? + if (is_string($this->{$qb_key}[$i])) + { + continue; + } + elseif ($this->{$qb_key}[$i]['escape'] === FALSE) + { + $this->{$qb_key}[$i] = $this->{$qb_key}[$i]['condition']; + continue; + } + + // Split multiple conditions + $conditions = preg_split( + '/((?:^|\s+)AND\s+|(?:^|\s+)OR\s+)/i', + $this->{$qb_key}[$i]['condition'], + -1, + PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY + ); + + for ($ci = 0, $cc = count($conditions); $ci < $cc; $ci++) + { + if (($op = $this->_get_operator($conditions[$ci])) === FALSE + OR ! preg_match('/^(\(?)(.*)('.preg_quote($op, '/').')\s*(.*(? '(test <= foo)', /* the whole thing */ + // 1 => '(', /* optional */ + // 2 => 'test', /* the field name */ + // 3 => ' <= ', /* $op */ + // 4 => 'foo', /* optional, if $op is e.g. 'IS NULL' */ + // 5 => ')' /* optional */ + // ); + + if ( ! empty($matches[4])) + { + $this->_is_literal($matches[4]) OR $matches[4] = $this->protect_identifiers(trim($matches[4])); + $matches[4] = ' '.$matches[4]; + } + + $conditions[$ci] = $matches[1].$this->protect_identifiers(trim($matches[2])) + .' '.trim($matches[3]).$matches[4].$matches[5]; + } + + $this->{$qb_key}[$i] = implode('', $conditions); + } + + return ($qb_key === 'qb_having' ? "\nHAVING " : "\nWHERE ") + .implode("\n", $this->$qb_key); + } + + return ''; + } + + // -------------------------------------------------------------------- + + /** + * Compile GROUP BY + * + * Escapes identifiers in GROUP BY statements at execution time. + * + * Required so that aliases are tracked properly, regardless of wether + * group_by() is called prior to from(), join() and dbprefix is added + * only if needed. + * + * @return string SQL statement + */ + protected function _compile_group_by() + { + if (count($this->qb_groupby) > 0) + { + for ($i = 0, $c = count($this->qb_groupby); $i < $c; $i++) + { + // Is it already compiled? + if (is_string($this->qb_groupby[$i])) + { + continue; + } + + $this->qb_groupby[$i] = ($this->qb_groupby[$i]['escape'] === FALSE OR $this->_is_literal($this->qb_groupby[$i]['field'])) + ? $this->qb_groupby[$i]['field'] + : $this->protect_identifiers($this->qb_groupby[$i]['field']); + } + + return "\nGROUP BY ".implode(', ', $this->qb_groupby); + } + + return ''; + } + + // -------------------------------------------------------------------- + + /** + * Compile ORDER BY + * + * Escapes identifiers in ORDER BY statements at execution time. + * + * Required so that aliases are tracked properly, regardless of wether + * order_by() is called prior to from(), join() and dbprefix is added + * only if needed. + * + * @return string SQL statement + */ + protected function _compile_order_by() + { + if (is_array($this->qb_orderby) && count($this->qb_orderby) > 0) + { + for ($i = 0, $c = count($this->qb_orderby); $i < $c; $i++) + { + if ($this->qb_orderby[$i]['escape'] !== FALSE && ! $this->_is_literal($this->qb_orderby[$i]['field'])) + { + $this->qb_orderby[$i]['field'] = $this->protect_identifiers($this->qb_orderby[$i]['field']); + } + + $this->qb_orderby[$i] = $this->qb_orderby[$i]['field'].$this->qb_orderby[$i]['direction']; + } + + return $this->qb_orderby = "\nORDER BY ".implode(', ', $this->qb_orderby); + } + elseif (is_string($this->qb_orderby)) + { + return $this->qb_orderby; + } + + return ''; + } + + // -------------------------------------------------------------------- + + /** + * Object to Array + * + * Takes an object as input and converts the class variables to array key/vals + * + * @param object + * @return array + */ + protected function _object_to_array($object) + { + if ( ! is_object($object)) + { + return $object; + } + + $array = array(); + foreach (get_object_vars($object) as $key => $val) + { + // There are some built in keys we need to ignore for this conversion + if ( ! is_object($val) && ! is_array($val) && $key !== '_parent_name') + { + $array[$key] = $val; + } + } + + return $array; + } + + // -------------------------------------------------------------------- + + /** + * Object to Array + * + * Takes an object as input and converts the class variables to array key/vals + * + * @param object + * @return array + */ + protected function _object_to_array_batch($object) + { + if ( ! is_object($object)) + { + return $object; + } + + $array = array(); + $out = get_object_vars($object); + $fields = array_keys($out); + + foreach ($fields as $val) + { + // There are some built in keys we need to ignore for this conversion + if ($val !== '_parent_name') + { + $i = 0; + foreach ($out[$val] as $data) + { + $array[$i++][$val] = $data; + } + } + } + + return $array; + } + + // -------------------------------------------------------------------- + + /** + * Start Cache + * + * Starts QB caching + * + * @return CI_DB_query_builder + */ + public function start_cache() + { + $this->qb_caching = TRUE; + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Stop Cache + * + * Stops QB caching + * + * @return CI_DB_query_builder + */ + public function stop_cache() + { + $this->qb_caching = FALSE; + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Flush Cache + * + * Empties the QB cache + * + * @return CI_DB_query_builder + */ + public function flush_cache() + { + $this->_reset_run(array( + 'qb_cache_select' => array(), + 'qb_cache_from' => array(), + 'qb_cache_join' => array(), + 'qb_cache_where' => array(), + 'qb_cache_groupby' => array(), + 'qb_cache_having' => array(), + 'qb_cache_orderby' => array(), + 'qb_cache_set' => array(), + 'qb_cache_exists' => array(), + 'qb_cache_no_escape' => array() + )); + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Merge Cache + * + * When called, this function merges any cached QB arrays with + * locally called ones. + * + * @return void + */ + protected function _merge_cache() + { + if (count($this->qb_cache_exists) === 0) + { + return; + } + elseif (in_array('select', $this->qb_cache_exists, TRUE)) + { + $qb_no_escape = $this->qb_cache_no_escape; + } + + foreach (array_unique($this->qb_cache_exists) as $val) // select, from, etc. + { + $qb_variable = 'qb_'.$val; + $qb_cache_var = 'qb_cache_'.$val; + $qb_new = $this->$qb_cache_var; + + for ($i = 0, $c = count($this->$qb_variable); $i < $c; $i++) + { + if ( ! in_array($this->{$qb_variable}[$i], $qb_new, TRUE)) + { + $qb_new[] = $this->{$qb_variable}[$i]; + if ($val === 'select') + { + $qb_no_escape[] = $this->qb_no_escape[$i]; + } + } + } + + $this->$qb_variable = $qb_new; + if ($val === 'select') + { + $this->qb_no_escape = $qb_no_escape; + } + } + + // 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 && count($this->qb_cache_from) > 0) + { + $this->_track_aliases($this->qb_from); + } + } + + // -------------------------------------------------------------------- + + /** + * Is literal + * + * Determines if a string represents a literal value or a field name + * + * @param string $str + * @return bool + */ + protected function _is_literal($str) + { + $str = trim($str); + + if (empty($str) OR ctype_digit($str) OR (string) (float) $str === $str OR in_array(strtoupper($str), array('TRUE', 'FALSE'), TRUE)) + { + return TRUE; + } + + static $_str; + + if (empty($_str)) + { + $_str = ($this->_escape_char !== '"') + ? array('"', "'") : array("'"); + } + + return in_array($str[0], $_str, TRUE); + } + + // -------------------------------------------------------------------- + + /** + * Reset Query Builder values. + * + * Publicly-visible method to reset the QB values. + * + * @return CI_DB_query_builder + */ + public function reset_query() + { + $this->_reset_select(); + $this->_reset_write(); + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Resets the query builder values. Called by the get() function + * + * @param array An array of fields to reset + * @return void + */ + protected function _reset_run($qb_reset_items) + { + foreach ($qb_reset_items as $item => $default_value) + { + $this->$item = $default_value; + } + } + + // -------------------------------------------------------------------- + + /** + * Resets the query builder values. Called by the get() function + * + * @return void + */ + protected function _reset_select() + { + $this->_reset_run(array( + 'qb_select' => array(), + 'qb_from' => array(), + 'qb_join' => array(), + 'qb_where' => array(), + 'qb_groupby' => array(), + 'qb_having' => array(), + 'qb_orderby' => array(), + 'qb_aliased_tables' => array(), + 'qb_no_escape' => array(), + 'qb_distinct' => FALSE, + 'qb_limit' => FALSE, + 'qb_offset' => FALSE + )); + } + + // -------------------------------------------------------------------- + + /** + * Resets the query builder "write" values. + * + * Called by the insert() update() insert_batch() update_batch() and delete() functions + * + * @return void + */ + protected function _reset_write() + { + $this->_reset_run(array( + 'qb_set' => array(), + 'qb_from' => array(), + 'qb_join' => array(), + 'qb_where' => array(), + 'qb_orderby' => array(), + 'qb_keys' => array(), + 'qb_limit' => FALSE + )); + } + +} diff --git a/src/system/database/DB_result.php b/src/system/database/DB_result.php index d397fb44..d9d1fccc 100644 --- a/src/system/database/DB_result.php +++ b/src/system/database/DB_result.php @@ -1,20 +1,41 @@ -conn_id = $driver_object->conn_id; + $this->result_id = $driver_object->result_id; + } + + // -------------------------------------------------------------------- + + /** + * Number of rows in the result set + * + * @return int + */ + public function num_rows() + { + if (is_int($this->num_rows)) + { + return $this->num_rows; + } + elseif (count($this->result_array) > 0) + { + return $this->num_rows = count($this->result_array); + } + elseif (count($this->result_object) > 0) + { + return $this->num_rows = count($this->result_object); + } + + return $this->num_rows = count($this->result_array()); + } + + // -------------------------------------------------------------------- + + /** + * Query result. Acts as a wrapper function for the following functions. + * + * @param string $type 'object', 'array' or a custom class name + * @return array */ public function result($type = 'object') { - if ($type == 'array') return $this->result_array(); - else if ($type == 'object') return $this->result_object(); - else return $this->custom_result_object($type); + if ($type === 'array') + { + return $this->result_array(); + } + elseif ($type === 'object') + { + return $this->result_object(); + } + else + { + return $this->custom_result_object($type); + } } // -------------------------------------------------------------------- @@ -58,48 +174,63 @@ class CI_DB_result { /** * Custom query result. * - * @param class_name A string that represents the type of object you want back - * @return array of objects + * @param string $class_name + * @return array */ public function custom_result_object($class_name) { - if (array_key_exists($class_name, $this->custom_result_object)) + if (isset($this->custom_result_object[$class_name])) { return $this->custom_result_object[$class_name]; } - - if ($this->result_id === FALSE OR $this->num_rows() == 0) + elseif ( ! $this->result_id 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()) + // Don't fetch the result set again if we already have it + $_data = NULL; + if (($c = count($this->result_array)) > 0) { - $object = new $class_name(); - - foreach ($row as $key => $value) - { - $object->$key = $value; - } - - $result_object[] = $object; + $_data = 'result_array'; + } + elseif (($c = count($this->result_object)) > 0) + { + $_data = 'result_object'; } - // return the array - return $this->custom_result_object[$class_name] = $result_object; + if ($_data !== NULL) + { + for ($i = 0; $i < $c; $i++) + { + $this->custom_result_object[$class_name][$i] = new $class_name(); + + foreach ($this->{$_data}[$i] as $key => $value) + { + $this->custom_result_object[$class_name][$i]->$key = $value; + } + } + + return $this->custom_result_object[$class_name]; + } + + is_null($this->row_data) OR $this->data_seek(0); + $this->custom_result_object[$class_name] = array(); + + while ($row = $this->_fetch_object($class_name)) + { + $this->custom_result_object[$class_name][] = $row; + } + + return $this->custom_result_object[$class_name]; } // -------------------------------------------------------------------- /** - * Query result. "object" version. + * Query result. "object" version. * - * @access public - * @return object + * @return array */ public function result_object() { @@ -108,15 +239,25 @@ class CI_DB_result { 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) + // In the event that query caching is on, the result_id variable + // will not be a valid resource so we'll simply return an empty + // array. + if ( ! $this->result_id OR $this->num_rows === 0) { return array(); } - $this->_data_seek(0); + if (($c = count($this->result_array)) > 0) + { + for ($i = 0; $i < $c; $i++) + { + $this->result_object[$i] = (object) $this->result_array[$i]; + } + + return $this->result_object; + } + + is_null($this->row_data) OR $this->data_seek(0); while ($row = $this->_fetch_object()) { $this->result_object[] = $row; @@ -128,9 +269,8 @@ class CI_DB_result { // -------------------------------------------------------------------- /** - * Query result. "array" version. + * Query result. "array" version. * - * @access public * @return array */ public function result_array() @@ -140,15 +280,25 @@ class CI_DB_result { 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) + // In the event that query caching is on, the result_id variable + // will not be a valid resource so we'll simply return an empty + // array. + if ( ! $this->result_id OR $this->num_rows === 0) { return array(); } - $this->_data_seek(0); + if (($c = count($this->result_object)) > 0) + { + for ($i = 0; $i < $c; $i++) + { + $this->result_array[$i] = (array) $this->result_object[$i]; + } + + return $this->result_array; + } + + is_null($this->row_data) OR $this->data_seek(0); while ($row = $this->_fetch_assoc()) { $this->result_array[] = $row; @@ -160,34 +310,32 @@ class CI_DB_result { // -------------------------------------------------------------------- /** - * Query result. Acts as a wrapper function for the following functions. + * Row * - * @access public - * @param string - * @param string can be "object" or "array" - * @return mixed either a result object or array + * A wrapper method. + * + * @param mixed $n + * @param string $type 'object' or 'array' + * @return mixed */ public function row($n = 0, $type = 'object') { if ( ! is_numeric($n)) { // We cache the row data for subsequent uses - if ( ! is_array($this->row_data)) + is_array($this->row_data) OR $this->row_data = $this->row_array(0); + + // array_key_exists() instead of isset() to allow for NULL values + if (empty($this->row_data) OR ! array_key_exists($n, $this->row_data)) { - $this->row_data = $this->row_array(0); + return NULL; } - // 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; + return $this->row_data[$n]; } - if ($type == 'object') return $this->row_object($n); - else if ($type == 'array') return $this->row_array($n); + if ($type === 'object') return $this->row_object($n); + elseif ($type === 'array') return $this->row_array($n); else return $this->custom_row_object($n, $type); } @@ -196,8 +344,9 @@ class CI_DB_result { /** * Assigns an item into a particular column slot * - * @access public - * @return object + * @param mixed $key + * @param mixed $value + * @return void */ public function set_row($key, $value = NULL) { @@ -213,11 +362,10 @@ class CI_DB_result { { $this->row_data[$k] = $v; } - return; } - if ($key != '' AND ! is_null($value)) + if ($key !== '' && $value !== NULL) { $this->row_data[$key] = $value; } @@ -228,42 +376,44 @@ class CI_DB_result { /** * Returns a single result row - custom object version * - * @access public + * @param int $n + * @param string $type * @return object */ public function custom_row_object($n, $type) { - $result = $this->custom_result_object($type); + isset($this->custom_result_object[$type]) OR $this->custom_result_object($type); - if (count($result) == 0) + if (count($this->custom_result_object[$type]) === 0) { - return $result; + return NULL; } - if ($n != $this->current_row AND isset($result[$n])) + if ($n !== $this->current_row && isset($this->custom_result_object[$type][$n])) { $this->current_row = $n; } - return $result[$this->current_row]; + return $this->custom_result_object[$type][$this->current_row]; } + // -------------------------------------------------------------------- + /** * Returns a single result row - object version * - * @access public + * @param int $n * @return object */ public function row_object($n = 0) { $result = $this->result_object(); - - if (count($result) == 0) + if (count($result) === 0) { - return $result; + return NULL; } - if ($n != $this->current_row AND isset($result[$n])) + if ($n !== $this->current_row && isset($result[$n])) { $this->current_row = $n; } @@ -276,19 +426,18 @@ class CI_DB_result { /** * Returns a single result row - array version * - * @access public + * @param int $n * @return array */ public function row_array($n = 0) { $result = $this->result_array(); - - if (count($result) == 0) + if (count($result) === 0) { - return $result; + return NULL; } - if ($n != $this->current_row AND isset($result[$n])) + if ($n !== $this->current_row && isset($result[$n])) { $this->current_row = $n; } @@ -296,24 +445,18 @@ class CI_DB_result { return $result[$this->current_row]; } - // -------------------------------------------------------------------- /** * Returns the "first" row * - * @access public - * @return object + * @param string $type + * @return mixed */ public function first_row($type = 'object') { $result = $this->result($type); - - if (count($result) == 0) - { - return $result; - } - return $result[0]; + return (count($result) === 0) ? NULL : $result[0]; } // -------------------------------------------------------------------- @@ -321,18 +464,13 @@ class CI_DB_result { /** * Returns the "last" row * - * @access public - * @return object + * @param string $type + * @return mixed */ public function last_row($type = 'object') { $result = $this->result($type); - - if (count($result) == 0) - { - return $result; - } - return $result[count($result) -1]; + return (count($result) === 0) ? NULL : $result[count($result) - 1]; } // -------------------------------------------------------------------- @@ -340,24 +478,20 @@ class CI_DB_result { /** * Returns the "next" row * - * @access public - * @return object + * @param string $type + * @return mixed */ public function next_row($type = 'object') { $result = $this->result($type); - - if (count($result) == 0) + if (count($result) === 0) { - return $result; + return NULL; } - if (isset($result[$this->current_row + 1])) - { - ++$this->current_row; - } - - return $result[$this->current_row]; + return isset($result[$this->current_row + 1]) + ? $result[++$this->current_row] + : NULL; } // -------------------------------------------------------------------- @@ -365,16 +499,15 @@ class CI_DB_result { /** * Returns the "previous" row * - * @access public - * @return object + * @param string $type + * @return mixed */ public function previous_row($type = 'object') { $result = $this->result($type); - - if (count($result) == 0) + if (count($result) === 0) { - return $result; + return NULL; } if (isset($result[$this->current_row - 1])) @@ -387,25 +520,147 @@ class CI_DB_result { // -------------------------------------------------------------------- /** - * The following functions are normally overloaded by the identically named + * Returns an unbuffered row and move pointer to next row + * + * @param string $type 'array', 'object' or a custom class name + * @return mixed + */ + public function unbuffered_row($type = 'object') + { + if ($type === 'array') + { + return $this->_fetch_assoc(); + } + elseif ($type === 'object') + { + return $this->_fetch_object(); + } + + return $this->_fetch_object($type); + } + + // -------------------------------------------------------------------- + + /** + * The following methods are normally overloaded by the identically named * methods in the platform-specific driver -- except when query caching - * is used. When caching is enabled we do not load the other driver. + * 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 + * 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(); } + + // -------------------------------------------------------------------- + + /** + * Number of fields in the result set + * + * Overridden by driver result classes. + * + * @return int + */ + public function num_fields() + { + return 0; + } + + // -------------------------------------------------------------------- + + /** + * Fetch Field Names + * + * Generates an array of column names. + * + * Overridden by driver result classes. + * + * @return array + */ + public function list_fields() + { + return array(); + } + + // -------------------------------------------------------------------- + + /** + * Field data + * + * Generates an array of objects containing field meta-data. + * + * Overridden by driver result classes. + * + * @return array + */ + public function field_data() + { + return array(); + } + + // -------------------------------------------------------------------- + + /** + * Free the result + * + * Overridden by driver result classes. + * + * @return void + */ + public function free_result() + { + $this->result_id = FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Data Seek + * + * Moves the internal pointer to the desired offset. We call + * this internally before fetching results to make sure the + * result set starts at zero. + * + * Overridden by driver result classes. + * + * @param int $n + * @return bool + */ + public function data_seek($n = 0) + { + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Result - associative array + * + * Returns the result set as an array. + * + * Overridden by driver result classes. + * + * @return array + */ + protected function _fetch_assoc() + { + return array(); + } + + // -------------------------------------------------------------------- + + /** + * Result - object + * + * Returns the result set as an object. + * + * Overridden by driver result classes. + * + * @param string $class_name + * @return object + */ + protected function _fetch_object($class_name = 'stdClass') + { + return array(); + } } -// END DB_result class - -/* End of file DB_result.php */ -/* Location: ./system/database/DB_result.php */ diff --git a/src/system/database/DB_utility.php b/src/system/database/DB_utility.php index 3032d9ee..70528286 100644 --- a/src/system/database/DB_utility.php +++ b/src/system/database/DB_utility.php @@ -1,46 +1,93 @@ -db - $CI =& get_instance(); - $this->db =& $CI->db; + protected $db; - log_message('debug', "Database Utility Class Initialized"); + // -------------------------------------------------------------------- + + /** + * List databases statement + * + * @var string + */ + protected $_list_databases = FALSE; + + /** + * OPTIMIZE TABLE statement + * + * @var string + */ + protected $_optimize_table = FALSE; + + /** + * REPAIR TABLE statement + * + * @var string + */ + protected $_repair_table = FALSE; + + // -------------------------------------------------------------------- + + /** + * Class constructor + * + * @param object &$db Database object + * @return void + */ + public function __construct(&$db) + { + $this->db =& $db; + log_message('info', 'Database Utility Class Initialized'); } // -------------------------------------------------------------------- @@ -48,29 +95,34 @@ class CI_DB_utility extends CI_DB_forge { /** * List databases * - * @access public - * @return bool + * @return array */ - function list_databases() + public function list_databases() { // Is there a cached result? - if (isset($this->data_cache['db_names'])) + if (isset($this->db->data_cache['db_names'])) { - return $this->data_cache['db_names']; + return $this->db->data_cache['db_names']; + } + elseif ($this->_list_databases === FALSE) + { + return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE; } - $query = $this->db->query($this->_list_databases()); - $dbs = array(); - if ($query->num_rows() > 0) + $this->db->data_cache['db_names'] = array(); + + $query = $this->db->query($this->_list_databases); + if ($query === FALSE) { - foreach ($query->result_array() as $row) - { - $dbs[] = current($row); - } + return $this->db->data_cache['db_names']; } - $this->data_cache['db_names'] = $dbs; - return $this->data_cache['db_names']; + for ($i = 0, $query = $query->result_array(), $c = count($query); $i < $c; $i++) + { + $this->db->data_cache['db_names'][] = current($query[$i]); + } + + return $this->db->data_cache['db_names']; } // -------------------------------------------------------------------- @@ -78,50 +130,37 @@ class CI_DB_utility extends CI_DB_forge { /** * Determine if a particular database exists * - * @access public - * @param string - * @return boolean + * @param string $database_name + * @return bool */ - function database_exists($database_name) + public 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; - } + return in_array($database_name, $this->list_databases()); } - // -------------------------------------------------------------------- /** * Optimize Table * - * @access public - * @param string the table name - * @return bool + * @param string $table_name + * @return mixed */ - function optimize_table($table_name) + public function optimize_table($table_name) { - $sql = $this->_optimize_table($table_name); - - if (is_bool($sql)) + if ($this->_optimize_table === FALSE) { - show_error('db_must_use_set'); + return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE; } - $query = $this->db->query($sql); - $res = $query->result_array(); + $query = $this->db->query(sprintf($this->_optimize_table, $this->db->escape_identifiers($table_name))); + if ($query !== FALSE) + { + $query = $query->result_array(); + return current($query); + } - // 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); + return FALSE; } // -------------------------------------------------------------------- @@ -129,27 +168,26 @@ class CI_DB_utility extends CI_DB_forge { /** * Optimize Database * - * @access public - * @return array + * @return mixed */ - function optimize_database() + public function optimize_database() { + if ($this->_optimize_table === FALSE) + { + return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE; + } + $result = array(); foreach ($this->db->list_tables() as $table_name) { - $sql = $this->_optimize_table($table_name); - - if (is_bool($sql)) + $res = $this->db->query(sprintf($this->_optimize_table, $this->db->escape_identifiers($table_name))); + if (is_bool($res)) { - return $sql; + return $res; } - $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 = $res->result_array(); $res = current($res); $key = str_replace($this->db->database.'.', '', current($res)); $keys = array_keys($res); @@ -166,25 +204,24 @@ class CI_DB_utility extends CI_DB_forge { /** * Repair Table * - * @access public - * @param string the table name - * @return bool + * @param string $table_name + * @return mixed */ - function repair_table($table_name) + public function repair_table($table_name) { - $sql = $this->_repair_table($table_name); - - if (is_bool($sql)) + if ($this->_repair_table === FALSE) { - return $sql; + return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE; } - $query = $this->db->query($sql); + $query = $this->db->query(sprintf($this->_repair_table, $this->db->escape_identifiers($table_name))); + if (is_bool($query)) + { + return $query; + } - // 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); + $query = $query->result_array(); + return current($query); } // -------------------------------------------------------------------- @@ -192,14 +229,13 @@ class CI_DB_utility extends CI_DB_forge { /** * 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 + * @param object $query Query result object + * @param string $delim Delimiter (default: ,) + * @param string $newline Newline character (default: \n) + * @param string $enclosure Enclosure (default: ") * @return string */ - function csv_from_result($query, $delim = ",", $newline = "\n", $enclosure = '"') + public function csv_from_result($query, $delim = ',', $newline = "\n", $enclosure = '"') { if ( ! is_object($query) OR ! method_exists($query, 'list_fields')) { @@ -207,25 +243,23 @@ class CI_DB_utility extends CI_DB_forge { } $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; + $out = substr($out, 0, -strlen($delim)).$newline; // Next blast through the result array and build out the rows - foreach ($query->result_array() as $row) + while ($row = $query->unbuffered_row('array')) { + $line = array(); foreach ($row as $item) { - $out .= $enclosure.str_replace($enclosure, $enclosure.$enclosure, $item).$enclosure.$delim; + $line[] = $enclosure.str_replace($enclosure, $enclosure.$enclosure, $item).$enclosure; } - $out = rtrim($out); - $out .= $newline; + $out .= implode($delim, $line).$newline; } return $out; @@ -236,12 +270,11 @@ class CI_DB_utility extends CI_DB_forge { /** * Generate XML data from a query result object * - * @access public - * @param object The query result object - * @param array Any preferences + * @param object $query Query result object + * @param array $params Any preferences * @return string */ - function xml_from_result($query, $params = array()) + public function xml_from_result($query, $params = array()) { if ( ! is_object($query) OR ! method_exists($query, 'list_fields')) { @@ -261,24 +294,21 @@ class CI_DB_utility extends CI_DB_forge { extract($params); // Load the xml helper - $CI =& get_instance(); - $CI->load->helper('xml'); + get_instance()->load->helper('xml'); // Generate the result - $xml = "<{$root}>".$newline; - foreach ($query->result_array() as $row) + $xml = '<'.$root.'>'.$newline; + while ($row = $query->unbuffered_row()) { - $xml .= $tab."<{$element}>".$newline; - + $xml .= $tab.'<'.$element.'>'.$newline; foreach ($row as $key => $val) { - $xml .= $tab.$tab."<{$key}>".xml_convert($val)."".$newline; + $xml .= $tab.$tab.'<'.$key.'>'.xml_convert($val).''.$newline; } - $xml .= $tab."".$newline; + $xml .= $tab.''.$newline; } - $xml .= "".$newline; - return $xml; + return $xml.''.$newline; } // -------------------------------------------------------------------- @@ -286,10 +316,10 @@ class CI_DB_utility extends CI_DB_forge { /** * Database Backup * - * @access public - * @return void + * @param array $params + * @return string */ - function backup($params = array()) + public function backup($params = array()) { // If the parameters have not been submitted as an // array then we know that it is simply the table @@ -299,18 +329,17 @@ class CI_DB_utility extends CI_DB_forge { $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" - ); + 'tables' => array(), + 'ignore' => array(), + 'filename' => '', + 'format' => 'gzip', // gzip, zip, txt + 'add_drop' => TRUE, + 'add_insert' => TRUE, + 'newline' => "\n", + 'foreign_key_checks' => TRUE + ); // Did the user submit any preferences? If so set them.... if (count($params) > 0) @@ -324,92 +353,72 @@ class CI_DB_utility extends CI_DB_forge { } } - // ------------------------------------------------------ - // 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) + 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 + // 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 (($prefs['format'] === 'gzip' && ! function_exists('gzencode')) + OR ($prefs['format'] === 'zip' && ! function_exists('gzcompress'))) { if ($this->db->db_debug) { - return $this->db->display_error('db_unsuported_compression'); + return $this->db->display_error('db_unsupported_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 ($prefs['format'] === 'zip') { - // If they included the .zip file extension we'll remove it - if (preg_match("|.+?\.zip$|", $prefs['filename'])) + // Set the filename if not provided (only needed with Zip files) + if ($prefs['filename'] === '') { - $prefs['filename'] = str_replace('.zip', '', $prefs['filename']); + $prefs['filename'] = (count($prefs['tables']) === 1 ? $prefs['tables'] : $this->db->database) + .date('Y-m-d_H-i', time()).'.sql'; } - - // Tack on the ".sql" file extension if needed - if ( ! preg_match("|.+?\.sql$|", $prefs['filename'])) + else { - $prefs['filename'] .= '.sql'; + // If they included the .zip file extension we'll remove it + if (preg_match('|.+?\.zip$|', $prefs['filename'])) + { + $prefs['filename'] = str_replace('.zip', '', $prefs['filename']); + } + + // Tack on the ".sql" file extension if needed + if ( ! preg_match('|.+?\.sql$|', $prefs['filename'])) + { + $prefs['filename'] .= '.sql'; + } } // Load the Zip class and output it - $CI =& get_instance(); $CI->load->library('zip'); $CI->zip->add_data($prefs['filename'], $this->_backup($prefs)); return $CI->zip->get_zip(); } + elseif ($prefs['format'] === 'txt') // Was a text file requested? + { + return $this->_backup($prefs); + } + elseif ($prefs['format'] === 'gzip') // Was a Gzip file requested? + { + return gzencode($this->_backup($prefs)); + } + return; } } - - -/* End of file DB_utility.php */ -/* Location: ./system/database/DB_utility.php */ \ No newline at end of file diff --git a/src/system/database/drivers/cubrid/cubrid_driver.php b/src/system/database/drivers/cubrid/cubrid_driver.php index 08aa429e..77f591ce 100644 --- a/src/system/database/drivers/cubrid/cubrid_driver.php +++ b/src/system/database/drivers/cubrid/cubrid_driver.php @@ -1,109 +1,135 @@ -port == '') - { - $this->port = self::DEFAULT_PORT; - } + parent::__construct($params); - $conn = cubrid_connect($this->hostname, $this->port, $this->database, $this->username, $this->password); - - if ($conn) + if (preg_match('/^CUBRID:[^:]+(:[0-9][1-9]{0,4})?:[^:]+:[^:]*:[^:]*:(\?.+)?$/', $this->dsn, $matches)) { - // 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) + if (stripos($matches[2], 'autocommit=off') !== FALSE) { - cubrid_set_autocommit($conn, CUBRID_AUTOCOMMIT_FALSE); - } - else - { - cubrid_set_autocommit($conn, CUBRID_AUTOCOMMIT_TRUE); - $this->auto_commit = TRUE; + $this->auto_commit = FALSE; } } - - return $conn; + else + { + // If no port is defined by the user, use the default value + empty($this->port) OR $this->port = 33000; + } } // -------------------------------------------------------------------- /** - * 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. + * Non-persistent database connection * - * @access private called by the base class + * @param bool $persistent * @return resource */ - function db_pconnect() + public function db_connect($persistent = FALSE) { - return $this->db_connect(); + if (preg_match('/^CUBRID:[^:]+(:[0-9][1-9]{0,4})?:[^:]+:([^:]*):([^:]*):(\?.+)?$/', $this->dsn, $matches)) + { + $func = ($persistent !== TRUE) ? 'cubrid_connect_with_url' : 'cubrid_pconnect_with_url'; + return ($matches[2] === '' && $matches[3] === '' && $this->username !== '' && $this->password !== '') + ? $func($this->dsn, $this->username, $this->password) + : $func($this->dsn); + } + + $func = ($persistent !== TRUE) ? 'cubrid_connect' : 'cubrid_pconnect'; + return ($this->username !== '') + ? $func($this->hostname, $this->port, $this->database, $this->username, $this->password) + : $func($this->hostname, $this->port, $this->database); } // -------------------------------------------------------------------- @@ -114,10 +140,9 @@ class CI_DB_cubrid_driver extends CI_DB { * 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() + public function reconnect() { if (cubrid_ping($this->conn_id) === FALSE) { @@ -128,54 +153,20 @@ class CI_DB_cubrid_driver extends CI_DB { // -------------------------------------------------------------------- /** - * Select the database + * Database version number * - * @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() + public 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); + if (isset($this->data_cache['version'])) + { + return $this->data_cache['version']; + } + + return ( ! $this->conn_id OR ($version = cubrid_get_server_info($this->conn_id)) === FALSE) + ? FALSE + : $this->data_cache['version'] = $version; } // -------------------------------------------------------------------- @@ -183,31 +174,12 @@ class CI_DB_cubrid_driver extends CI_DB { /** * Execute the query * - * @access private called by the base class - * @param string an SQL query + * @param string $sql an SQL query * @return resource */ - function _execute($sql) + protected 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; + return cubrid_query($sql, $this->conn_id); } // -------------------------------------------------------------------- @@ -215,30 +187,17 @@ class CI_DB_cubrid_driver extends CI_DB { /** * Begin Transaction * - * @access public * @return bool */ - function trans_begin($test_mode = FALSE) + protected function _trans_begin() { - if ( ! $this->trans_enabled) + if (($autocommit = cubrid_get_autocommit($this->conn_id)) === NULL) { - return TRUE; + return FALSE; } - - // When transactions are nested we only begin/commit/rollback the outermost ones - if ($this->_trans_depth > 0) + elseif ($autocommit === TRUE) { - 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 cubrid_set_autocommit($this->conn_id, CUBRID_AUTOCOMMIT_FALSE); } return TRUE; @@ -249,27 +208,18 @@ class CI_DB_cubrid_driver extends CI_DB { /** * Commit Transaction * - * @access public * @return bool */ - function trans_commit() + protected function _trans_commit() { - if ( ! $this->trans_enabled) + if ( ! cubrid_commit($this->conn_id)) { - return TRUE; + return FALSE; } - // 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 cubrid_set_autocommit($this->conn_id, CUBRID_AUTOCOMMIT_TRUE); } return TRUE; @@ -280,24 +230,15 @@ class CI_DB_cubrid_driver extends CI_DB { /** * Rollback Transaction * - * @access public * @return bool */ - function trans_rollback() + protected function _trans_rollback() { - if ( ! $this->trans_enabled) + if ( ! cubrid_rollback($this->conn_id)) { - return TRUE; + return FALSE; } - // 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); @@ -309,41 +250,14 @@ class CI_DB_cubrid_driver extends CI_DB { // -------------------------------------------------------------------- /** - * Escape String + * Platform-dependant string escape * - * @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) + protected function _escape_str($str) { - 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; + return cubrid_real_escape_string($str, $this->conn_id); } // -------------------------------------------------------------------- @@ -351,12 +265,11 @@ class CI_DB_cubrid_driver extends CI_DB { /** * Affected Rows * - * @access public - * @return integer + * @return int */ - function affected_rows() + public function affected_rows() { - return @cubrid_affected_rows($this->conn_id); + return cubrid_affected_rows(); } // -------------------------------------------------------------------- @@ -364,43 +277,11 @@ class CI_DB_cubrid_driver extends CI_DB { /** * Insert ID * - * @access public - * @return integer + * @return int */ - function insert_id() + public 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; + return cubrid_insert_id($this->conn_id); } // -------------------------------------------------------------------- @@ -410,17 +291,16 @@ class CI_DB_cubrid_driver extends CI_DB { * * Generates a platform-specific query string so that the table names can be fetched * - * @access private - * @param boolean + * @param bool $prefix_limit * @return string */ - function _list_tables($prefix_limit = FALSE) + protected function _list_tables($prefix_limit = FALSE) { - $sql = "SHOW TABLES"; + $sql = 'SHOW TABLES'; - if ($prefix_limit !== FALSE AND $this->dbprefix != '') + if ($prefix_limit !== FALSE && $this->dbprefix !== '') { - $sql .= " LIKE '".$this->escape_like_str($this->dbprefix)."%'"; + return $sql." LIKE '".$this->escape_like_str($this->dbprefix)."%'"; } return $sql; @@ -433,343 +313,81 @@ class CI_DB_cubrid_driver extends CI_DB { * * Generates a platform-specific query string so that the column names can be fetched * - * @access public - * @param string the table name + * @param string $table * @return string */ - function _list_columns($table = '') + protected function _list_columns($table = '') { - return "SHOW COLUMNS FROM ".$this->_protect_identifiers($table, TRUE, NULL, FALSE); + return 'SHOW COLUMNS FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE); } // -------------------------------------------------------------------- /** - * Field data query + * Returns an object with field data * - * Generates a platform-specific query so that the column data can be retrieved - * - * @access public - * @param string the table name - * @return object + * @param string $table + * @return array */ - function _field_data($table) + public function field_data($table) { - return "SELECT * FROM ".$table." LIMIT 1"; + if (($query = $this->query('SHOW COLUMNS FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE))) === FALSE) + { + return FALSE; + } + $query = $query->result_object(); + + $retval = array(); + for ($i = 0, $c = count($query); $i < $c; $i++) + { + $retval[$i] = new stdClass(); + $retval[$i]->name = $query[$i]->Field; + + sscanf($query[$i]->Type, '%[a-z](%d)', + $retval[$i]->type, + $retval[$i]->max_length + ); + + $retval[$i]->default = $query[$i]->Default; + $retval[$i]->primary_key = (int) ($query[$i]->Key === 'PRI'); + } + + return $retval; } // -------------------------------------------------------------------- /** - * The error message string + * Error + * + * Returns an array containing code and message of the last + * database error that has occured. + * + * @return array + */ + public function error() + { + return array('code' => cubrid_errno($this->conn_id), 'message' => cubrid_error($this->conn_id)); + } + + // -------------------------------------------------------------------- + + /** + * FROM tables + * + * Groups tables in FROM clauses if needed, so there is no confusion + * about operator precedence. * - * @access private * @return string */ - function _error_message() + protected function _from_tables() { - 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 == '') + if ( ! empty($this->qb_join) && count($this->qb_from) > 1) { - return $item; + return '('.implode(', ', $this->qb_from).')'; } - 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; + return implode(', ', $this->qb_from); } // -------------------------------------------------------------------- @@ -777,17 +395,11 @@ class CI_DB_cubrid_driver extends CI_DB { /** * Close DB Connection * - * @access public - * @param resource * @return void */ - function _close($conn_id) + protected function _close() { - @cubrid_close($conn_id); + cubrid_close($this->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/src/system/database/drivers/cubrid/cubrid_forge.php b/src/system/database/drivers/cubrid/cubrid_forge.php index 73bfafe2..8262beb6 100644 --- a/src/system/database/drivers/cubrid/cubrid_forge.php +++ b/src/system/database/drivers/cubrid/cubrid_forge.php @@ -1,289 +1,227 @@ - 'INTEGER', + 'SMALLINT' => 'INTEGER', + 'INT' => 'BIGINT', + 'INTEGER' => 'BIGINT', + 'BIGINT' => 'NUMERIC', + 'FLOAT' => 'DOUBLE', + 'REAL' => 'DOUBLE' + ); // -------------------------------------------------------------------- /** - * Drop database + * ALTER TABLE * - * @access private - * @param string the database name - * @return bool + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * @return string|string[] */ - function _drop_database($name) + protected function _alter_table($alter_type, $table, $field) { - // CUBRID does not allow to drop a database in SQL. The GUI tools - // have to be used for this purpose. - return FALSE; - } - - // -------------------------------------------------------------------- - - /** - * Process Fields - * - * @access private - * @param mixed the fields - * @return string - */ - function _process_fields($fields) - { - $current_field_count = 0; - $sql = ''; - - foreach ($fields as $field=>$attributes) + if (in_array($alter_type, array('DROP', 'ADD'), TRUE)) { - // 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)) + return parent::_alter_table($alter_type, $table, $field); + } + + $sql = 'ALTER TABLE '.$this->db->escape_identifiers($table); + $sqls = array(); + for ($i = 0, $c = count($field); $i < $c; $i++) + { + if ($field[$i]['_literal'] !== FALSE) { - $sql .= "\n\t$attributes"; + $sqls[] = $sql.' CHANGE '.$field[$i]['_literal']; } else { - $attributes = array_change_key_case($attributes, CASE_UPPER); + $alter_type = empty($field[$i]['new_name']) ? ' MODIFY ' : ' CHANGE '; + $sqls[] = $sql.$alter_type.$this->_process_column($field[$i]); + } + } - $sql .= "\n\t\"" . $this->db->_protect_identifiers($field) . "\""; + return $sqls; + } - if (array_key_exists('NAME', $attributes)) + // -------------------------------------------------------------------- + + /** + * Process column + * + * @param array $field + * @return string + */ + protected function _process_column($field) + { + $extra_clause = isset($field['after']) + ? ' AFTER '.$this->db->escape_identifiers($field['after']) : ''; + + if (empty($extra_clause) && isset($field['first']) && $field['first'] === TRUE) + { + $extra_clause = ' FIRST'; + } + + return $this->db->escape_identifiers($field['name']) + .(empty($field['new_name']) ? '' : ' '.$this->db->escape_identifiers($field['new_name'])) + .' '.$field['type'].$field['length'] + .$field['unsigned'] + .$field['null'] + .$field['default'] + .$field['auto_increment'] + .$field['unique'] + .$extra_clause; + } + + // -------------------------------------------------------------------- + + /** + * Field attribute TYPE + * + * Performs a data type mapping between different databases. + * + * @param array &$attributes + * @return void + */ + protected function _attr_type(&$attributes) + { + switch (strtoupper($attributes['TYPE'])) + { + case 'TINYINT': + $attributes['TYPE'] = 'SMALLINT'; + $attributes['UNSIGNED'] = FALSE; + return; + case 'MEDIUMINT': + $attributes['TYPE'] = 'INTEGER'; + $attributes['UNSIGNED'] = FALSE; + return; + default: return; + } + } + + // -------------------------------------------------------------------- + + /** + * Process indexes + * + * @param string $table (ignored) + * @return string + */ + protected function _process_indexes($table) + { + $sql = ''; + + for ($i = 0, $c = count($this->keys); $i < $c; $i++) + { + if (is_array($this->keys[$i])) + { + for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++) { - $sql .= ' '.$this->db->_protect_identifiers($attributes['NAME']).' '; - } - - if (array_key_exists('TYPE', $attributes)) - { - $sql .= ' '.$attributes['TYPE']; - - if (array_key_exists('CONSTRAINT', $attributes)) + if ( ! isset($this->fields[$this->keys[$i][$i2]])) { - 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'].')'; - } + unset($this->keys[$i][$i2]); + continue; } } - - 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)) + elseif ( ! isset($this->fields[$this->keys[$i]])) { - $sql .= ','; + unset($this->keys[$i]); + continue; } + + is_array($this->keys[$i]) OR $this->keys[$i] = array($this->keys[$i]); + + $sql .= ",\n\tKEY ".$this->db->escape_identifiers(implode('_', $this->keys[$i])) + .' ('.implode(', ', $this->db->escape_identifiers($this->keys[$i])).')'; } - return $sql; - } + $this->keys = array(); - // -------------------------------------------------------------------- - - /** - * 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/src/system/database/drivers/cubrid/cubrid_result.php b/src/system/database/drivers/cubrid/cubrid_result.php index 244e6b46..9cccb257 100644 --- a/src/system/database/drivers/cubrid/cubrid_result.php +++ b/src/system/database/drivers/cubrid/cubrid_result.php @@ -1,20 +1,41 @@ -result_id); + return is_int($this->num_rows) + ? $this->num_rows + : $this->num_rows = cubrid_num_rows($this->result_id); } // -------------------------------------------------------------------- @@ -43,12 +65,11 @@ class CI_DB_cubrid_result extends CI_DB_result { /** * Number of fields in the result set * - * @access public - * @return integer + * @return int */ - function num_fields() + public function num_fields() { - return @cubrid_num_fields($this->result_id); + return cubrid_num_fields($this->result_id); } // -------------------------------------------------------------------- @@ -58,10 +79,9 @@ class CI_DB_cubrid_result extends CI_DB_result { * * Generates an array of column names * - * @access public * @return array */ - function list_fields() + public function list_fields() { return cubrid_column_names($this->result_id); } @@ -73,59 +93,19 @@ class CI_DB_cubrid_result extends CI_DB_result { * * Generates an array of objects containing field meta-data * - * @access public * @return array */ - function field_data() + public function field_data() { $retval = array(); - $tablePrimaryKeys = array(); - - while ($field = cubrid_fetch_field($this->result_id)) + for ($i = 0, $c = $this->num_fields(); $i < $c; $i++) { - $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; + $retval[$i] = new stdClass(); + $retval[$i]->name = cubrid_field_name($this->result_id, $i); + $retval[$i]->type = cubrid_field_type($this->result_id, $i); + $retval[$i]->max_length = cubrid_field_len($this->result_id, $i); + $retval[$i]->primary_key = (int) (strpos(cubrid_field_flags($this->result_id, $i), 'primary_key') !== FALSE); } return $retval; @@ -136,13 +116,12 @@ class CI_DB_cubrid_result extends CI_DB_result { /** * Free the result * - * @return null + * @return void */ - function free_result() + public function free_result() { - if(is_resource($this->result_id) || - get_resource_type($this->result_id) == "Unknown" && - preg_match('/Resource id #/', strval($this->result_id))) + if (is_resource($this->result_id) OR + (get_resource_type($this->result_id) === 'Unknown' && preg_match('/Resource id #/', strval($this->result_id)))) { cubrid_close_request($this->result_id); $this->result_id = FALSE; @@ -156,12 +135,12 @@ class CI_DB_cubrid_result extends CI_DB_result { * * Moves the internal pointer to the desired offset. We call * this internally before fetching results to make sure the - * result set starts at zero + * result set starts at zero. * - * @access private - * @return array + * @param int $n + * @return bool */ - function _data_seek($n = 0) + public function data_seek($n = 0) { return cubrid_data_seek($this->result_id, $n); } @@ -173,10 +152,9 @@ class CI_DB_cubrid_result extends CI_DB_result { * * Returns the result set as an array * - * @access private * @return array */ - function _fetch_assoc() + protected function _fetch_assoc() { return cubrid_fetch_assoc($this->result_id); } @@ -188,16 +166,12 @@ class CI_DB_cubrid_result extends CI_DB_result { * * Returns the result set as an object * - * @access private + * @param string $class_name * @return object */ - function _fetch_object() + protected function _fetch_object($class_name = 'stdClass') { - return cubrid_fetch_object($this->result_id); + return cubrid_fetch_object($this->result_id, $class_name); } } - - -/* End of file cubrid_result.php */ -/* Location: ./system/database/drivers/cubrid/cubrid_result.php */ \ No newline at end of file diff --git a/src/system/database/drivers/cubrid/cubrid_utility.php b/src/system/database/drivers/cubrid/cubrid_utility.php index 4cd7bb58..942fa3b4 100644 --- a/src/system/database/drivers/cubrid/cubrid_utility.php +++ b/src/system/database/drivers/cubrid/cubrid_utility.php @@ -1,109 +1,79 @@ -conn_id) + if (isset($this->db->data_cache['db_names'])) { - return "SELECT '" . $this->database . "'"; - } - else - { - return FALSE; + return $this->db->data_cache['db_names']; } + + return $this->db->data_cache['db_names'] = cubrid_list_dbs($this->db->conn_id); } // -------------------------------------------------------------------- - /** - * 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()) + protected function _backup($params = array()) { // No SQL based support in CUBRID as of version 8.4.0. Database or // table backup can be performed using CUBRID Manager // database administration tool. - return $this->db->display_error('db_unsuported_feature'); + return $this->db->display_error('db_unsupported_feature'); } } - -/* End of file cubrid_utility.php */ -/* Location: ./system/database/drivers/cubrid/cubrid_utility.php */ \ No newline at end of file diff --git a/src/system/database/drivers/cubrid/index.html b/src/system/database/drivers/cubrid/index.html index c942a79c..b702fbc3 100644 --- a/src/system/database/drivers/cubrid/index.html +++ b/src/system/database/drivers/cubrid/index.html @@ -1,3 +1,4 @@ + 403 Forbidden @@ -7,4 +8,4 @@

Directory access is forbidden.

- \ No newline at end of file + diff --git a/src/system/database/drivers/ibase/ibase_driver.php b/src/system/database/drivers/ibase/ibase_driver.php new file mode 100644 index 00000000..c1055c1e --- /dev/null +++ b/src/system/database/drivers/ibase/ibase_driver.php @@ -0,0 +1,396 @@ +hostname.':'.$this->database, $this->username, $this->password, $this->char_set) + : ibase_connect($this->hostname.':'.$this->database, $this->username, $this->password, $this->char_set); + } + + // -------------------------------------------------------------------- + + /** + * Database version number + * + * @return string + */ + public function version() + { + if (isset($this->data_cache['version'])) + { + return $this->data_cache['version']; + } + + if (($service = ibase_service_attach($this->hostname, $this->username, $this->password))) + { + $this->data_cache['version'] = ibase_server_info($service, IBASE_SVC_SERVER_VERSION); + + // Don't keep the service open + ibase_service_detach($service); + return $this->data_cache['version']; + } + + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Execute the query + * + * @param string $sql an SQL query + * @return resource + */ + protected function _execute($sql) + { + return ibase_query(isset($this->_ibase_trans) ? $this->_ibase_trans : $this->conn_id, $sql); + } + + // -------------------------------------------------------------------- + + /** + * Begin Transaction + * + * @return bool + */ + protected function _trans_begin() + { + if (($trans_handle = ibase_trans($this->conn_id)) === FALSE) + { + return FALSE; + } + + $this->_ibase_trans = $trans_handle; + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Commit Transaction + * + * @return bool + */ + protected function _trans_commit() + { + if (ibase_commit($this->_ibase_trans)) + { + $this->_ibase_trans = NULL; + return TRUE; + } + + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Rollback Transaction + * + * @return bool + */ + protected function _trans_rollback() + { + if (ibase_rollback($this->_ibase_trans)) + { + $this->_ibase_trans = NULL; + return TRUE; + } + + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Affected Rows + * + * @return int + */ + public function affected_rows() + { + return ibase_affected_rows($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * Insert ID + * + * @param string $generator_name + * @param int $inc_by + * @return int + */ + public function insert_id($generator_name, $inc_by = 0) + { + //If a generator hasn't been used before it will return 0 + return ibase_gen_id('"'.$generator_name.'"', $inc_by); + } + + // -------------------------------------------------------------------- + + /** + * List table query + * + * Generates a platform-specific query string so that the table names can be fetched + * + * @param bool $prefix_limit + * @return string + */ + protected function _list_tables($prefix_limit = FALSE) + { + $sql = 'SELECT TRIM("RDB$RELATION_NAME") AS TABLE_NAME FROM "RDB$RELATIONS" WHERE "RDB$RELATION_NAME" NOT LIKE \'RDB$%\' AND "RDB$RELATION_NAME" NOT LIKE \'MON$%\''; + + if ($prefix_limit !== FALSE && $this->dbprefix !== '') + { + return $sql.' AND TRIM("RDB$RELATION_NAME") AS TABLE_NAME LIKE \''.$this->escape_like_str($this->dbprefix)."%' " + .sprintf($this->_like_escape_str, $this->_like_escape_chr); + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Show column query + * + * Generates a platform-specific query string so that the column names can be fetched + * + * @param string $table + * @return string + */ + protected function _list_columns($table = '') + { + return 'SELECT TRIM("RDB$FIELD_NAME") AS COLUMN_NAME FROM "RDB$RELATION_FIELDS" WHERE "RDB$RELATION_NAME" = '.$this->escape($table); + } + + // -------------------------------------------------------------------- + + /** + * Returns an object with field data + * + * @param string $table + * @return array + */ + public function field_data($table) + { + $sql = 'SELECT "rfields"."RDB$FIELD_NAME" AS "name", + CASE "fields"."RDB$FIELD_TYPE" + WHEN 7 THEN \'SMALLINT\' + WHEN 8 THEN \'INTEGER\' + WHEN 9 THEN \'QUAD\' + WHEN 10 THEN \'FLOAT\' + WHEN 11 THEN \'DFLOAT\' + WHEN 12 THEN \'DATE\' + WHEN 13 THEN \'TIME\' + WHEN 14 THEN \'CHAR\' + WHEN 16 THEN \'INT64\' + WHEN 27 THEN \'DOUBLE\' + WHEN 35 THEN \'TIMESTAMP\' + WHEN 37 THEN \'VARCHAR\' + WHEN 40 THEN \'CSTRING\' + WHEN 261 THEN \'BLOB\' + ELSE NULL + END AS "type", + "fields"."RDB$FIELD_LENGTH" AS "max_length", + "rfields"."RDB$DEFAULT_VALUE" AS "default" + FROM "RDB$RELATION_FIELDS" "rfields" + JOIN "RDB$FIELDS" "fields" ON "rfields"."RDB$FIELD_SOURCE" = "fields"."RDB$FIELD_NAME" + WHERE "rfields"."RDB$RELATION_NAME" = '.$this->escape($table).' + ORDER BY "rfields"."RDB$FIELD_POSITION"'; + + return (($query = $this->query($sql)) !== FALSE) + ? $query->result_object() + : FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Error + * + * Returns an array containing code and message of the last + * database error that has occured. + * + * @return array + */ + public function error() + { + return array('code' => ibase_errcode(), 'message' => ibase_errmsg()); + } + + // -------------------------------------------------------------------- + + /** + * Update statement + * + * Generates a platform-specific update string from the supplied data + * + * @param string $table + * @param array $values + * @return string + */ + protected function _update($table, $values) + { + $this->qb_limit = FALSE; + return parent::_update($table, $values); + } + + // -------------------------------------------------------------------- + + /** + * Truncate statement + * + * Generates a platform-specific truncate string from the supplied data + * + * If the database does not support the TRUNCATE statement, + * then this method maps to 'DELETE FROM table' + * + * @param string $table + * @return string + */ + protected function _truncate($table) + { + return 'DELETE FROM '.$table; + } + + // -------------------------------------------------------------------- + + /** + * Delete statement + * + * Generates a platform-specific delete string from the supplied data + * + * @param string $table + * @return string + */ + protected function _delete($table) + { + $this->qb_limit = FALSE; + return parent::_delete($table); + } + + // -------------------------------------------------------------------- + + /** + * LIMIT + * + * Generates a platform-specific LIMIT clause + * + * @param string $sql SQL Query + * @return string + */ + protected function _limit($sql) + { + // Limit clause depends on if Interbase or Firebird + if (stripos($this->version(), 'firebird') !== FALSE) + { + $select = 'FIRST '.$this->qb_limit + .($this->qb_offset ? ' SKIP '.$this->qb_offset : ''); + } + else + { + $select = 'ROWS ' + .($this->qb_offset ? $this->qb_offset.' TO '.($this->qb_limit + $this->qb_offset) : $this->qb_limit); + } + + return preg_replace('`SELECT`i', 'SELECT '.$select, $sql, 1); + } + + // -------------------------------------------------------------------- + + /** + * Close DB Connection + * + * @return void + */ + protected function _close() + { + ibase_close($this->conn_id); + } + +} diff --git a/src/system/database/drivers/ibase/ibase_forge.php b/src/system/database/drivers/ibase/ibase_forge.php new file mode 100644 index 00000000..9c358c36 --- /dev/null +++ b/src/system/database/drivers/ibase/ibase_forge.php @@ -0,0 +1,251 @@ + 'INTEGER', + 'INTEGER' => 'INT64', + 'FLOAT' => 'DOUBLE PRECISION' + ); + + /** + * NULL value representation in CREATE/ALTER TABLE statements + * + * @var string + */ + protected $_null = 'NULL'; + + // -------------------------------------------------------------------- + + /** + * Create database + * + * @param string $db_name + * @return string + */ + public function create_database($db_name) + { + // Firebird databases are flat files, so a path is required + + // Hostname is needed for remote access + empty($this->db->hostname) OR $db_name = $this->hostname.':'.$db_name; + + return parent::create_database('"'.$db_name.'"'); + } + + // -------------------------------------------------------------------- + + /** + * Drop database + * + * @param string $db_name (ignored) + * @return bool + */ + public function drop_database($db_name = '') + { + if ( ! ibase_drop_db($this->conn_id)) + { + return ($this->db->db_debug) ? $this->db->display_error('db_unable_to_drop') : FALSE; + } + elseif ( ! empty($this->db->data_cache['db_names'])) + { + $key = array_search(strtolower($this->db->database), array_map('strtolower', $this->db->data_cache['db_names']), TRUE); + if ($key !== FALSE) + { + unset($this->db->data_cache['db_names'][$key]); + } + } + + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * ALTER TABLE + * + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * @return string|string[] + */ + protected function _alter_table($alter_type, $table, $field) + { + if (in_array($alter_type, array('DROP', 'ADD'), TRUE)) + { + return parent::_alter_table($alter_type, $table, $field); + } + + $sql = 'ALTER TABLE '.$this->db->escape_identifiers($table); + $sqls = array(); + for ($i = 0, $c = count($field); $i < $c; $i++) + { + if ($field[$i]['_literal'] !== FALSE) + { + return FALSE; + } + + if (isset($field[$i]['type'])) + { + $sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identififers($field[$i]['name']) + .' TYPE '.$field[$i]['type'].$field[$i]['length']; + } + + if ( ! empty($field[$i]['default'])) + { + $sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identifiers($field[$i]['name']) + .' SET DEFAULT '.$field[$i]['default']; + } + + if (isset($field[$i]['null'])) + { + $sqls[] = 'UPDATE "RDB$RELATION_FIELDS" SET "RDB$NULL_FLAG" = ' + .($field[$i]['null'] === TRUE ? 'NULL' : '1') + .' WHERE "RDB$FIELD_NAME" = '.$this->db->escape($field[$i]['name']) + .' AND "RDB$RELATION_NAME" = '.$this->db->escape($table); + } + + if ( ! empty($field[$i]['new_name'])) + { + $sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identifiers($field[$i]['name']) + .' TO '.$this->db->escape_identifiers($field[$i]['new_name']); + } + } + + return $sqls; + } + + // -------------------------------------------------------------------- + + /** + * Process column + * + * @param array $field + * @return string + */ + protected function _process_column($field) + { + return $this->db->escape_identifiers($field['name']) + .' '.$field['type'].$field['length'] + .$field['null'] + .$field['unique'] + .$field['default']; + } + + // -------------------------------------------------------------------- + + /** + * Field attribute TYPE + * + * Performs a data type mapping between different databases. + * + * @param array &$attributes + * @return void + */ + protected function _attr_type(&$attributes) + { + switch (strtoupper($attributes['TYPE'])) + { + case 'TINYINT': + $attributes['TYPE'] = 'SMALLINT'; + $attributes['UNSIGNED'] = FALSE; + return; + case 'MEDIUMINT': + $attributes['TYPE'] = 'INTEGER'; + $attributes['UNSIGNED'] = FALSE; + return; + case 'INT': + $attributes['TYPE'] = 'INTEGER'; + return; + case 'BIGINT': + $attributes['TYPE'] = 'INT64'; + return; + default: return; + } + } + + // -------------------------------------------------------------------- + + /** + * Field attribute AUTO_INCREMENT + * + * @param array &$attributes + * @param array &$field + * @return void + */ + protected function _attr_auto_increment(&$attributes, &$field) + { + // Not supported + } + +} diff --git a/src/system/database/drivers/ibase/ibase_result.php b/src/system/database/drivers/ibase/ibase_result.php new file mode 100644 index 00000000..f3c21fce --- /dev/null +++ b/src/system/database/drivers/ibase/ibase_result.php @@ -0,0 +1,161 @@ +result_id); + } + + // -------------------------------------------------------------------- + + /** + * Fetch Field Names + * + * Generates an array of column names + * + * @return array + */ + public function list_fields() + { + $field_names = array(); + for ($i = 0, $num_fields = $this->num_fields(); $i < $num_fields; $i++) + { + $info = ibase_field_info($this->result_id, $i); + $field_names[] = $info['name']; + } + + return $field_names; + } + + // -------------------------------------------------------------------- + + /** + * Field data + * + * Generates an array of objects containing field meta-data + * + * @return array + */ + public function field_data() + { + $retval = array(); + for ($i = 0, $c = $this->num_fields(); $i < $c; $i++) + { + $info = ibase_field_info($this->result_id, $i); + + $retval[$i] = new stdClass(); + $retval[$i]->name = $info['name']; + $retval[$i]->type = $info['type']; + $retval[$i]->max_length = $info['length']; + } + + return $retval; + } + + // -------------------------------------------------------------------- + + /** + * Free the result + * + * @return void + */ + public function free_result() + { + ibase_free_result($this->result_id); + } + + // -------------------------------------------------------------------- + + /** + * Result - associative array + * + * Returns the result set as an array + * + * @return array + */ + protected function _fetch_assoc() + { + return ibase_fetch_assoc($this->result_id, IBASE_FETCH_BLOBS); + } + + // -------------------------------------------------------------------- + + /** + * Result - object + * + * Returns the result set as an object + * + * @param string $class_name + * @return object + */ + protected function _fetch_object($class_name = 'stdClass') + { + $row = ibase_fetch_object($this->result_id, IBASE_FETCH_BLOBS); + + if ($class_name === 'stdClass' OR ! $row) + { + return $row; + } + + $class_name = new $class_name(); + foreach ($row as $key => $value) + { + $class_name->$key = $value; + } + + return $class_name; + } + +} diff --git a/src/system/database/drivers/ibase/ibase_utility.php b/src/system/database/drivers/ibase/ibase_utility.php new file mode 100644 index 00000000..619ebad0 --- /dev/null +++ b/src/system/database/drivers/ibase/ibase_utility.php @@ -0,0 +1,69 @@ +db->hostname, $this->db->username, $this->db->password)) + { + $res = ibase_backup($service, $this->db->database, $filename.'.fbk'); + + // Close the service connection + ibase_service_detach($service); + return $res; + } + + return FALSE; + } + +} diff --git a/src/system/database/drivers/ibase/index.html b/src/system/database/drivers/ibase/index.html new file mode 100644 index 00000000..b702fbc3 --- /dev/null +++ b/src/system/database/drivers/ibase/index.html @@ -0,0 +1,11 @@ + + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + diff --git a/src/system/database/drivers/index.html b/src/system/database/drivers/index.html index c942a79c..b702fbc3 100644 --- a/src/system/database/drivers/index.html +++ b/src/system/database/drivers/index.html @@ -1,3 +1,4 @@ + 403 Forbidden @@ -7,4 +8,4 @@

Directory access is forbidden.

- \ No newline at end of file + diff --git a/src/system/database/drivers/mssql/index.html b/src/system/database/drivers/mssql/index.html index c942a79c..b702fbc3 100644 --- a/src/system/database/drivers/mssql/index.html +++ b/src/system/database/drivers/mssql/index.html @@ -1,3 +1,4 @@ + 403 Forbidden @@ -7,4 +8,4 @@

Directory access is forbidden.

- \ No newline at end of file + diff --git a/src/system/database/drivers/mssql/mssql_driver.php b/src/system/database/drivers/mssql/mssql_driver.php index f3d633ca..d40d67a0 100644 --- a/src/system/database/drivers/mssql/mssql_driver.php +++ b/src/system/database/drivers/mssql/mssql_driver.php @@ -1,101 +1,141 @@ -port)) + { + $this->hostname .= (DIRECTORY_SEPARATOR === '\\' ? ',' : ':').$this->port; + } + } + + // -------------------------------------------------------------------- /** * Non-persistent database connection * - * @access private called by the base class + * @param bool $persistent * @return resource */ - function db_connect() + public function db_connect($persistent = FALSE) { - if ($this->port != '') + $this->conn_id = ($persistent) + ? mssql_pconnect($this->hostname, $this->username, $this->password) + : mssql_connect($this->hostname, $this->username, $this->password); + + if ( ! $this->conn_id) { - $this->hostname .= ','.$this->port; + return FALSE; } - 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 != '') + // Select the DB... assuming a database name is specified in the config file + if ($this->database !== '' && ! $this->db_select()) { - $this->hostname .= ','.$this->port; + log_message('error', 'Unable to select database: '.$this->database); + + return ($this->db_debug === TRUE) + ? $this->display_error('db_unable_to_select', $this->database) + : FALSE; } - return @mssql_pconnect($this->hostname, $this->username, $this->password); - } + // Determine how identifiers are escaped + $query = $this->query('SELECT CASE WHEN (@@OPTIONS | 256) = @@OPTIONS THEN 1 ELSE 0 END AS qi'); + $query = $query->row_array(); + $this->_quoted_identifier = empty($query) ? FALSE : (bool) $query['qi']; + $this->_escape_char = ($this->_quoted_identifier) ? '"' : array('[', ']'); - // -------------------------------------------------------------------- - - /** - * 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 + return $this->conn_id; } // -------------------------------------------------------------------- @@ -103,30 +143,25 @@ class CI_DB_mssql_driver extends CI_DB { /** * Select the database * - * @access private called by the base class - * @return resource + * @param string $database + * @return bool */ - function db_select() + public function db_select($database = '') { - // Note: The brackets are required in the event that the DB name - // contains reserved characters - return @mssql_select_db('['.$this->database.']', $this->conn_id); - } + if ($database === '') + { + $database = $this->database; + } - // -------------------------------------------------------------------- + // Note: Escaping is required in the event that the DB name + // contains reserved characters. + if (mssql_select_db('['.$database.']', $this->conn_id)) + { + $this->database = $database; + 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; + return FALSE; } // -------------------------------------------------------------------- @@ -134,30 +169,12 @@ class CI_DB_mssql_driver extends CI_DB { /** * Execute the query * - * @access private called by the base class - * @param string an SQL query - * @return resource + * @param string $sql an SQL query + * @return mixed resource if rows are returned, bool otherwise */ - function _execute($sql) + protected 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; + return mssql_query($sql, $this->conn_id); } // -------------------------------------------------------------------- @@ -165,29 +182,11 @@ class CI_DB_mssql_driver extends CI_DB { /** * Begin Transaction * - * @access public * @return bool */ - function trans_begin($test_mode = FALSE) + protected function _trans_begin() { - 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; + return $this->simple_query('BEGIN TRAN'); } // -------------------------------------------------------------------- @@ -195,24 +194,11 @@ class CI_DB_mssql_driver extends CI_DB { /** * Commit Transaction * - * @access public * @return bool */ - function trans_commit() + protected 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; + return $this->simple_query('COMMIT TRAN'); } // -------------------------------------------------------------------- @@ -220,62 +206,11 @@ class CI_DB_mssql_driver extends CI_DB { /** * Rollback Transaction * - * @access public * @return bool */ - function trans_rollback() + protected 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; + return $this->simple_query('ROLLBACK TRAN'); } // -------------------------------------------------------------------- @@ -283,93 +218,56 @@ class CI_DB_mssql_driver extends CI_DB { /** * Affected Rows * - * @access public - * @return integer + * @return int */ - function affected_rows() + public function affected_rows() { - return @mssql_rows_affected($this->conn_id); + 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 + * Insert ID * - * Generates a platform-specific query string that counts all records in - * the specified database + * Returns the last id created in the Identity column. * - * @access public - * @param string * @return string */ - function count_all($table = '') + public function insert_id() { - if ($table == '') - { - return 0; - } + $query = version_compare($this->version(), '8', '>=') + ? 'SELECT SCOPE_IDENTITY() AS last_id' + : 'SELECT @@IDENTITY AS last_id'; - $query = $this->query($this->_count_string . $this->_protect_identifiers('numrows') . " FROM " . $this->_protect_identifiers($table, TRUE, NULL, FALSE)); + $query = $this->query($query); + $query = $query->row(); + return $query->last_id; + } - if ($query->num_rows() == 0) - { - return 0; - } + // -------------------------------------------------------------------- - $row = $query->row(); - $this->_reset_select(); - return (int) $row->numrows; + /** + * Set client character set + * + * @param string $charset + * @return bool + */ + protected function _db_set_charset($charset) + { + return (ini_set('mssql.charset', $charset) !== FALSE); + } + + // -------------------------------------------------------------------- + + /** + * Version number query string + * + * @return string + */ + protected function _version() + { + return "SELECT SERVERPROPERTY('ProductVersion') AS ver"; } // -------------------------------------------------------------------- @@ -379,22 +277,22 @@ class CI_DB_mssql_driver extends CI_DB { * * Generates a platform-specific query string so that the table names can be fetched * - * @access private - * @param boolean + * @param bool $prefix_limit * @return string */ - function _list_tables($prefix_limit = FALSE) + protected function _list_tables($prefix_limit = FALSE) { - $sql = "SELECT name FROM sysobjects WHERE type = 'U' ORDER BY name"; + $sql = 'SELECT '.$this->escape_identifiers('name') + .' FROM '.$this->escape_identifiers('sysobjects') + .' WHERE '.$this->escape_identifiers('type')." = 'U'"; - // for future compatibility - if ($prefix_limit !== FALSE AND $this->dbprefix != '') + if ($prefix_limit !== FALSE && $this->dbprefix !== '') { - //$sql .= " LIKE '".$this->escape_like_str($this->dbprefix)."%' ".sprintf($this->_like_escape_str, $this->_like_escape_chr); - return FALSE; // not currently supported + $sql .= ' AND '.$this->escape_identifiers('name')." LIKE '".$this->escape_like_str($this->dbprefix)."%' " + .sprintf($this->_like_escape_str, $this->_like_escape_chr); } - return $sql; + return $sql.' ORDER BY '.$this->escape_identifiers('name'); } // -------------------------------------------------------------------- @@ -404,138 +302,74 @@ class CI_DB_mssql_driver extends CI_DB { * * Generates a platform-specific query string so that the column names can be fetched * - * @access private - * @param string the table name + * @param string $table * @return string */ - function _list_columns($table = '') + protected function _list_columns($table = '') { - return "SELECT * FROM INFORMATION_SCHEMA.Columns WHERE TABLE_NAME = '".$table."'"; + return 'SELECT COLUMN_NAME + FROM INFORMATION_SCHEMA.Columns + WHERE UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table)); } // -------------------------------------------------------------------- /** - * Field data query + * Returns an object with field data * - * Generates a platform-specific query so that the column data can be retrieved - * - * @access public - * @param string the table name - * @return object + * @param string $table + * @return array */ - function _field_data($table) + public function field_data($table) { - return "SELECT TOP 1 * FROM ".$table; - } + $sql = 'SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, COLUMN_DEFAULT + FROM INFORMATION_SCHEMA.Columns + WHERE UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table)); - // -------------------------------------------------------------------- - - /** - * 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 == '') + if (($query = $this->query($sql)) === FALSE) { - return $item; + return FALSE; + } + $query = $query->result_object(); + + $retval = array(); + for ($i = 0, $c = count($query); $i < $c; $i++) + { + $retval[$i] = new stdClass(); + $retval[$i]->name = $query[$i]->COLUMN_NAME; + $retval[$i]->type = $query[$i]->DATA_TYPE; + $retval[$i]->max_length = ($query[$i]->CHARACTER_MAXIMUM_LENGTH > 0) ? $query[$i]->CHARACTER_MAXIMUM_LENGTH : $query[$i]->NUMERIC_PRECISION; + $retval[$i]->default = $query[$i]->COLUMN_DEFAULT; } - 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); + return $retval; } // -------------------------------------------------------------------- /** - * From Tables + * Error * - * This function implicitly groups FROM tables so there is no confusion - * about operator precedence in harmony with SQL standards + * Returns an array containing code and message of the last + * database error that has occured. * - * @access public - * @param type - * @return type + * @return array */ - function _from_tables($tables) + public function error() { - if ( ! is_array($tables)) + // We need this because the error info is discarded by the + // server the first time you request it, and query() already + // calls error() once for logging purposes when a query fails. + static $error = array('code' => 0, 'message' => NULL); + + $message = mssql_get_last_message(); + if ( ! empty($message)) { - $tables = array($tables); + $error['code'] = $this->query('SELECT @@ERROR AS code')->row()->code; + $error['message'] = $message; } - 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).")"; + return $error; } // -------------------------------------------------------------------- @@ -545,51 +379,33 @@ class CI_DB_mssql_driver extends CI_DB { * * 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 + * @param string $table + * @param array $values * @return string */ - function _update($table, $values, $where, $orderby = array(), $limit = FALSE) + protected function _update($table, $values) { - 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; + $this->qb_limit = FALSE; + $this->qb_orderby = array(); + return parent::_update($table, $values); } - // -------------------------------------------------------------------- /** * Truncate statement * * Generates a platform-specific truncate string from the supplied data - * If the database does not support the truncate() command - * This function maps to "DELETE FROM table" * - * @access public - * @param string the table name + * If the database does not support the TRUNCATE statement, + * then this method maps to 'DELETE FROM table' + * + * @param string $table * @return string */ - function _truncate($table) + protected function _truncate($table) { - return "TRUNCATE ".$table; + return 'TRUNCATE TABLE '.$table; } // -------------------------------------------------------------------- @@ -599,51 +415,91 @@ class CI_DB_mssql_driver extends CI_DB { * * 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 + * @param string $table * @return string */ - function _delete($table, $where = array(), $like = array(), $limit = FALSE) + protected function _delete($table) { - $conditions = ''; - - if (count($where) > 0 OR count($like) > 0) + if ($this->qb_limit) { - $conditions = "\nWHERE "; - $conditions .= implode("\n", $this->ar_where); - - if (count($where) > 0 && count($like) > 0) - { - $conditions .= " AND "; - } - $conditions .= implode("\n", $like); + return 'WITH ci_delete AS (SELECT TOP '.$this->qb_limit.' * FROM '.$table.$this->_compile_wh('qb_where').') DELETE FROM ci_delete'; } - $limit = ( ! $limit) ? '' : ' LIMIT '.$limit; - - return "DELETE FROM ".$table.$conditions.$limit; + return parent::_delete($table); } // -------------------------------------------------------------------- /** - * Limit string + * LIMIT * * 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 + * @param string $sql SQL Query * @return string */ - function _limit($sql, $limit, $offset) + protected function _limit($sql) { - $i = $limit + $offset; + $limit = $this->qb_offset + $this->qb_limit; - return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.$i.' ', $sql); + // As of SQL Server 2005 (9.0.*) ROW_NUMBER() is supported, + // however an ORDER BY clause is required for it to work + if (version_compare($this->version(), '9', '>=') && $this->qb_offset && ! empty($this->qb_orderby)) + { + $orderby = $this->_compile_order_by(); + + // We have to strip the ORDER BY clause + $sql = trim(substr($sql, 0, strrpos($sql, $orderby))); + + // Get the fields to select from our subquery, so that we can avoid CI_rownum appearing in the actual results + if (count($this->qb_select) === 0) + { + $select = '*'; // Inevitable + } + else + { + // Use only field names and their aliases, everything else is out of our scope. + $select = array(); + $field_regexp = ($this->_quoted_identifier) + ? '("[^\"]+")' : '(\[[^\]]+\])'; + for ($i = 0, $c = count($this->qb_select); $i < $c; $i++) + { + $select[] = preg_match('/(?:\s|\.)'.$field_regexp.'$/i', $this->qb_select[$i], $m) + ? $m[1] : $this->qb_select[$i]; + } + $select = implode(', ', $select); + } + + return 'SELECT '.$select." FROM (\n\n" + .preg_replace('/^(SELECT( DISTINCT)?)/i', '\\1 ROW_NUMBER() OVER('.trim($orderby).') AS '.$this->escape_identifiers('CI_rownum').', ', $sql) + ."\n\n) ".$this->escape_identifiers('CI_subquery') + ."\nWHERE ".$this->escape_identifiers('CI_rownum').' BETWEEN '.($this->qb_offset + 1).' AND '.$limit; + } + + return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.$limit.' ', $sql); + } + + // -------------------------------------------------------------------- + + /** + * Insert batch statement + * + * Generates a platform-specific insert string from the supplied data. + * + * @param string $table Table name + * @param array $keys INSERT keys + * @param array $values INSERT values + * @return string|bool + */ + protected function _insert_batch($table, $keys, $values) + { + // Multiple-value inserts are only supported as of SQL Server 2008 + if (version_compare($this->version(), '10', '>=')) + { + return parent::_insert_batch($table, $keys, $values); + } + + return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE; } // -------------------------------------------------------------------- @@ -651,18 +507,11 @@ class CI_DB_mssql_driver extends CI_DB { /** * Close DB Connection * - * @access public - * @param resource * @return void */ - function _close($conn_id) + protected function _close() { - @mssql_close($conn_id); + mssql_close($this->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/src/system/database/drivers/mssql/mssql_forge.php b/src/system/database/drivers/mssql/mssql_forge.php index 579a8eef..24d1fc20 100644 --- a/src/system/database/drivers/mssql/mssql_forge.php +++ b/src/system/database/drivers/mssql/mssql_forge.php @@ -1,249 +1,146 @@ - 'SMALLINT', + 'SMALLINT' => 'INT', + 'INT' => 'BIGINT', + 'REAL' => 'FLOAT' + ); + + // -------------------------------------------------------------------- + + /** + * ALTER TABLE + * + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * @return string|string[] + */ + protected function _alter_table($alter_type, $table, $field) { - return "CREATE DATABASE ".$name; + if (in_array($alter_type, array('ADD', 'DROP'), TRUE)) + { + return parent::_alter_table($alter_type, $table, $field); + } + + $sql = 'ALTER TABLE '.$this->db->escape_identifiers($table).' ALTER COLUMN '; + $sqls = array(); + for ($i = 0, $c = count($field); $i < $c; $i++) + { + $sqls[] = $sql.$this->_process_column($field[$i]); + } + + return $sqls; } // -------------------------------------------------------------------- /** - * Drop database + * Field attribute TYPE * - * @access private - * @param string the database name - * @return bool + * Performs a data type mapping between different databases. + * + * @param array &$attributes + * @return void */ - function _drop_database($name) + protected function _attr_type(&$attributes) { - return "DROP DATABASE ".$name; + switch (strtoupper($attributes['TYPE'])) + { + case 'MEDIUMINT': + $attributes['TYPE'] = 'INTEGER'; + $attributes['UNSIGNED'] = FALSE; + return; + case 'INTEGER': + $attributes['TYPE'] = 'INT'; + return; + default: return; + } } // -------------------------------------------------------------------- /** - * Drop Table + * Field attribute AUTO_INCREMENT * - * @access private - * @return bool + * @param array &$attributes + * @param array &$field + * @return void */ - function _drop_table($table) + protected function _attr_auto_increment(&$attributes, &$field) { - return "DROP TABLE ".$this->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) + if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE && stripos($field['type'], 'int') !== FALSE) { - $sql .= 'IF NOT EXISTS '; + $field['auto_increment'] = ' IDENTITY(1,1)'; } - - $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/src/system/database/drivers/mssql/mssql_result.php b/src/system/database/drivers/mssql/mssql_result.php index 6ad24e4d..b62bf75c 100644 --- a/src/system/database/drivers/mssql/mssql_result.php +++ b/src/system/database/drivers/mssql/mssql_result.php @@ -1,41 +1,65 @@ -result_id); + return is_int($this->num_rows) + ? $this->num_rows + : $this->num_rows = mssql_num_rows($this->result_id); } // -------------------------------------------------------------------- @@ -43,12 +67,11 @@ class CI_DB_mssql_result extends CI_DB_result { /** * Number of fields in the result set * - * @access public - * @return integer + * @return int */ - function num_fields() + public function num_fields() { - return @mssql_num_fields($this->result_id); + return mssql_num_fields($this->result_id); } // -------------------------------------------------------------------- @@ -58,12 +81,12 @@ class CI_DB_mssql_result extends CI_DB_result { * * Generates an array of column names * - * @access public * @return array */ - function list_fields() + public function list_fields() { $field_names = array(); + mssql_field_seek($this->result_id, 0); while ($field = mssql_fetch_field($this->result_id)) { $field_names[] = $field->name; @@ -79,22 +102,19 @@ class CI_DB_mssql_result extends CI_DB_result { * * Generates an array of objects containing field meta-data * - * @access public * @return array */ - function field_data() + public function field_data() { $retval = array(); - while ($field = mssql_fetch_field($this->result_id)) + for ($i = 0, $c = $this->num_fields(); $i < $c; $i++) { - $F = new stdClass(); - $F->name = $field->name; - $F->type = $field->type; - $F->max_length = $field->max_length; - $F->primary_key = 0; - $F->default = ''; + $field = mssql_fetch_field($this->result_id, $i); - $retval[] = $F; + $retval[$i] = new stdClass(); + $retval[$i]->name = $field->name; + $retval[$i]->type = $field->type; + $retval[$i]->max_length = $field->max_length; } return $retval; @@ -105,9 +125,9 @@ class CI_DB_mssql_result extends CI_DB_result { /** * Free the result * - * @return null + * @return void */ - function free_result() + public function free_result() { if (is_resource($this->result_id)) { @@ -121,14 +141,14 @@ class CI_DB_mssql_result extends CI_DB_result { /** * Data Seek * - * Moves the internal pointer to the desired offset. We call + * Moves the internal pointer to the desired offset. We call * this internally before fetching results to make sure the - * result set starts at zero + * result set starts at zero. * - * @access private - * @return array + * @param int $n + * @return bool */ - function _data_seek($n = 0) + public function data_seek($n = 0) { return mssql_data_seek($this->result_id, $n); } @@ -140,10 +160,9 @@ class CI_DB_mssql_result extends CI_DB_result { * * Returns the result set as an array * - * @access private * @return array */ - function _fetch_assoc() + protected function _fetch_assoc() { return mssql_fetch_assoc($this->result_id); } @@ -155,16 +174,25 @@ class CI_DB_mssql_result extends CI_DB_result { * * Returns the result set as an object * - * @access private + * @param string $class_name * @return object */ - function _fetch_object() + protected function _fetch_object($class_name = 'stdClass') { - return mssql_fetch_object($this->result_id); + $row = mssql_fetch_object($this->result_id); + + if ($class_name === 'stdClass' OR ! $row) + { + return $row; + } + + $class_name = new $class_name(); + foreach ($row as $key => $value) + { + $class_name->$key = $value; + } + + return $class_name; } } - - -/* End of file mssql_result.php */ -/* Location: ./system/database/drivers/mssql/mssql_result.php */ \ No newline at end of file diff --git a/src/system/database/drivers/mssql/mssql_utility.php b/src/system/database/drivers/mssql/mssql_utility.php index 96014b90..cd23be82 100644 --- a/src/system/database/drivers/mssql/mssql_utility.php +++ b/src/system/database/drivers/mssql/mssql_utility.php @@ -1,89 +1,77 @@ -db->display_error('db_unsuported_feature'); + return $this->db->display_error('db_unsupported_feature'); } } - -/* End of file mssql_utility.php */ -/* Location: ./system/database/drivers/mssql/mssql_utility.php */ \ No newline at end of file diff --git a/src/system/database/drivers/mysql/index.html b/src/system/database/drivers/mysql/index.html index c942a79c..b702fbc3 100644 --- a/src/system/database/drivers/mysql/index.html +++ b/src/system/database/drivers/mysql/index.html @@ -1,3 +1,4 @@ + 403 Forbidden @@ -7,4 +8,4 @@

Directory access is forbidden.

- \ No newline at end of file + diff --git a/src/system/database/drivers/mysql/mysql_driver.php b/src/system/database/drivers/mysql/mysql_driver.php index 3f2a9a00..78b1d703 100644 --- a/src/system/database/drivers/mysql/mysql_driver.php +++ b/src/system/database/drivers/mysql/mysql_driver.php @@ -1,95 +1,175 @@ -port != '') + parent::__construct($params); + + if ( ! empty($this->port)) { $this->hostname .= ':'.$this->port; } - - return @mysql_connect($this->hostname, $this->username, $this->password, TRUE); } // -------------------------------------------------------------------- /** - * Persistent database connection + * Non-persistent database connection * - * @access private called by the base class + * @param bool $persistent * @return resource */ - function db_pconnect() + public function db_connect($persistent = FALSE) { - if ($this->port != '') + $client_flags = ($this->compress === FALSE) ? 0 : MYSQL_CLIENT_COMPRESS; + + if ($this->encrypt === TRUE) { - $this->hostname .= ':'.$this->port; + $client_flags = $client_flags | MYSQL_CLIENT_SSL; } - return @mysql_pconnect($this->hostname, $this->username, $this->password); + // Error suppression is necessary mostly due to PHP 5.5+ issuing E_DEPRECATED messages + $this->conn_id = ($persistent === TRUE) + ? mysql_pconnect($this->hostname, $this->username, $this->password, $client_flags) + : mysql_connect($this->hostname, $this->username, $this->password, TRUE, $client_flags); + + // ---------------------------------------------------------------- + + // Select the DB... assuming a database name is specified in the config file + if ($this->database !== '' && ! $this->db_select()) + { + log_message('error', 'Unable to select database: '.$this->database); + + return ($this->db_debug === TRUE) + ? $this->display_error('db_unable_to_select', $this->database) + : FALSE; + } + + if (isset($this->stricton) && is_resource($this->conn_id)) + { + if ($this->stricton) + { + $this->simple_query('SET SESSION sql_mode = CONCAT(@@sql_mode, ",", "STRICT_ALL_TABLES")'); + } + else + { + $this->simple_query( + 'SET SESSION sql_mode = + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + @@sql_mode, + "STRICT_ALL_TABLES,", ""), + ",STRICT_ALL_TABLES", ""), + "STRICT_ALL_TABLES", ""), + "STRICT_TRANS_TABLES,", ""), + ",STRICT_TRANS_TABLES", ""), + "STRICT_TRANS_TABLES", "")' + ); + } + } + + return $this->conn_id; } // -------------------------------------------------------------------- @@ -100,10 +180,9 @@ class CI_DB_mysql_driver extends CI_DB { * 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() + public function reconnect() { if (mysql_ping($this->conn_id) === FALSE) { @@ -116,12 +195,23 @@ class CI_DB_mysql_driver extends CI_DB { /** * Select the database * - * @access private called by the base class - * @return resource + * @param string $database + * @return bool */ - function db_select() + public function db_select($database = '') { - return @mysql_select_db($this->database, $this->conn_id); + if ($database === '') + { + $database = $this->database; + } + + if (mysql_select_db($database, $this->conn_id)) + { + $this->database = $database; + return TRUE; + } + + return FALSE; } // -------------------------------------------------------------------- @@ -129,40 +219,34 @@ class CI_DB_mysql_driver extends CI_DB { /** * Set client character set * - * @access public - * @param string - * @param string - * @return resource + * @param string $charset + * @return bool */ - function db_set_charset($charset, $collation) + protected function _db_set_charset($charset) { - 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); - } + return mysql_set_charset($charset, $this->conn_id); } // -------------------------------------------------------------------- /** - * Version number query string + * Database version number * - * @access public * @return string */ - function _version() + public function version() { - return "SELECT version() AS ver"; + if (isset($this->data_cache['version'])) + { + return $this->data_cache['version']; + } + + if ( ! $this->conn_id OR ($version = mysql_get_server_info($this->conn_id)) === FALSE) + { + return FALSE; + } + + return $this->data_cache['version'] = $version; } // -------------------------------------------------------------------- @@ -170,14 +254,12 @@ class CI_DB_mysql_driver extends CI_DB { /** * Execute the query * - * @access private called by the base class - * @param string an SQL query - * @return resource + * @param string $sql an SQL query + * @return mixed */ - function _execute($sql) + protected function _execute($sql) { - $sql = $this->_prep_query($sql); - return @mysql_query($sql, $this->conn_id); + return mysql_query($this->_prep_query($sql), $this->conn_id); } // -------------------------------------------------------------------- @@ -187,20 +269,16 @@ class CI_DB_mysql_driver extends CI_DB { * * If needed, each database adapter can prep the query string * - * @access private called by execute() - * @param string an SQL query + * @param string $sql an SQL query * @return string */ - function _prep_query($sql) + protected 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) + // mysql_affected_rows() returns 0 for "DELETE FROM TABLE" queries. This hack + // modifies the query so that it a proper number of affected rows is returned. + if ($this->delete_hack === TRUE && preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $sql)) { - 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 trim($sql).' WHERE 1=1'; } return $sql; @@ -211,30 +289,12 @@ class CI_DB_mysql_driver extends CI_DB { /** * Begin Transaction * - * @access public * @return bool */ - function trans_begin($test_mode = FALSE) + protected function _trans_begin() { - 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; + return $this->simple_query('START TRANSACTION'); // can also be BEGIN or BEGIN WORK } // -------------------------------------------------------------------- @@ -242,25 +302,17 @@ class CI_DB_mysql_driver extends CI_DB { /** * Commit Transaction * - * @access public * @return bool */ - function trans_commit() + protected function _trans_commit() { - if ( ! $this->trans_enabled) + if ($this->simple_query('COMMIT')) { + $this->simple_query('SET AUTOCOMMIT=1'); 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; + return FALSE; } // -------------------------------------------------------------------- @@ -268,58 +320,30 @@ class CI_DB_mysql_driver extends CI_DB { /** * Rollback Transaction * - * @access public * @return bool */ - function trans_rollback() + protected function _trans_rollback() { - if ( ! $this->trans_enabled) + if ($this->simple_query('ROLLBACK')) { + $this->simple_query('SET AUTOCOMMIT=1'); 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; + return FALSE; } // -------------------------------------------------------------------- /** - * Escape String + * Platform-dependant string escape * - * @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) + protected function _escape_str($str) { - if (is_array($str)) - { - foreach ($str as $key => $val) - { - $str[$key] = $this->escape_str($val, $like); - } - - return $str; - } - - $str = mysql_real_escape_string($str, $this->conn_id); - - // escape LIKE condition wildcards - if ($like === TRUE) - { - $str = str_replace(array('%', '_'), array('\\%', '\\_'), $str); - } - - return $str; + return mysql_real_escape_string($str, $this->conn_id); } // -------------------------------------------------------------------- @@ -327,12 +351,11 @@ class CI_DB_mysql_driver extends CI_DB { /** * Affected Rows * - * @access public - * @return integer + * @return int */ - function affected_rows() + public function affected_rows() { - return @mysql_affected_rows($this->conn_id); + return mysql_affected_rows($this->conn_id); } // -------------------------------------------------------------------- @@ -340,43 +363,11 @@ class CI_DB_mysql_driver extends CI_DB { /** * Insert ID * - * @access public - * @return integer + * @return int */ - function insert_id() + public 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; + return mysql_insert_id($this->conn_id); } // -------------------------------------------------------------------- @@ -386,17 +377,16 @@ class CI_DB_mysql_driver extends CI_DB { * * Generates a platform-specific query string so that the table names can be fetched * - * @access private - * @param boolean + * @param bool $prefix_limit * @return string */ - function _list_tables($prefix_limit = FALSE) + protected function _list_tables($prefix_limit = FALSE) { - $sql = "SHOW TABLES FROM ".$this->_escape_char.$this->database.$this->_escape_char; + $sql = 'SHOW TABLES FROM '.$this->escape_identifiers($this->database); - if ($prefix_limit !== FALSE AND $this->dbprefix != '') + if ($prefix_limit !== FALSE && $this->dbprefix !== '') { - $sql .= " LIKE '".$this->escape_like_str($this->dbprefix)."%'"; + return $sql." LIKE '".$this->escape_like_str($this->dbprefix)."%'"; } return $sql; @@ -409,343 +399,81 @@ class CI_DB_mysql_driver extends CI_DB { * * Generates a platform-specific query string so that the column names can be fetched * - * @access public - * @param string the table name + * @param string $table * @return string */ - function _list_columns($table = '') + protected function _list_columns($table = '') { - return "SHOW COLUMNS FROM ".$this->_protect_identifiers($table, TRUE, NULL, FALSE); + return 'SHOW COLUMNS FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE); } // -------------------------------------------------------------------- /** - * Field data query + * Returns an object with field data * - * Generates a platform-specific query so that the column data can be retrieved - * - * @access public - * @param string the table name - * @return object + * @param string $table + * @return array */ - function _field_data($table) + public function field_data($table) { - return "DESCRIBE ".$table; + if (($query = $this->query('SHOW COLUMNS FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE))) === FALSE) + { + return FALSE; + } + $query = $query->result_object(); + + $retval = array(); + for ($i = 0, $c = count($query); $i < $c; $i++) + { + $retval[$i] = new stdClass(); + $retval[$i]->name = $query[$i]->Field; + + sscanf($query[$i]->Type, '%[a-z](%d)', + $retval[$i]->type, + $retval[$i]->max_length + ); + + $retval[$i]->default = $query[$i]->Default; + $retval[$i]->primary_key = (int) ($query[$i]->Key === 'PRI'); + } + + return $retval; } // -------------------------------------------------------------------- /** - * The error message string + * Error + * + * Returns an array containing code and message of the last + * database error that has occured. + * + * @return array + */ + public function error() + { + return array('code' => mysql_errno($this->conn_id), 'message' => mysql_error($this->conn_id)); + } + + // -------------------------------------------------------------------- + + /** + * FROM tables + * + * Groups tables in FROM clauses if needed, so there is no confusion + * about operator precedence. * - * @access private * @return string */ - function _error_message() + protected function _from_tables() { - 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 == '') + if ( ! empty($this->qb_join) && count($this->qb_from) > 1) { - return $item; + return '('.implode(', ', $this->qb_from).')'; } - 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; + return implode(', ', $this->qb_from); } // -------------------------------------------------------------------- @@ -753,17 +481,13 @@ class CI_DB_mysql_driver extends CI_DB { /** * Close DB Connection * - * @access public - * @param resource * @return void */ - function _close($conn_id) + protected function _close() { - @mysql_close($conn_id); + // Error suppression to avoid annoying E_WARNINGs in cases + // where the connection has already been closed for some reason. + @mysql_close($this->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/src/system/database/drivers/mysql/mysql_forge.php b/src/system/database/drivers/mysql/mysql_forge.php index 5bb9ccd1..fa84be37 100644 --- a/src/system/database/drivers/mysql/mysql_forge.php +++ b/src/system/database/drivers/mysql/mysql_forge.php @@ -1,274 +1,243 @@ -$attributes) + foreach (array_keys($attributes) as $key) { - // 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)) + if (is_string($key)) { - $sql .= "\n\t$attributes"; + $sql .= ' '.strtoupper($key).' = '.$attributes[$key]; + } + } + + if ( ! empty($this->db->char_set) && ! strpos($sql, 'CHARACTER SET') && ! strpos($sql, 'CHARSET')) + { + $sql .= ' DEFAULT CHARACTER SET = '.$this->db->char_set; + } + + if ( ! empty($this->db->dbcollat) && ! strpos($sql, 'COLLATE')) + { + $sql .= ' COLLATE = '.$this->db->dbcollat; + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * ALTER TABLE + * + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * @return string|string[] + */ + protected function _alter_table($alter_type, $table, $field) + { + if ($alter_type === 'DROP') + { + return parent::_alter_table($alter_type, $table, $field); + } + + $sql = 'ALTER TABLE '.$this->db->escape_identifiers($table); + for ($i = 0, $c = count($field); $i < $c; $i++) + { + if ($field[$i]['_literal'] !== FALSE) + { + $field[$i] = ($alter_type === 'ADD') + ? "\n\tADD ".$field[$i]['_literal'] + : "\n\tMODIFY ".$field[$i]['_literal']; } else { - $attributes = array_change_key_case($attributes, CASE_UPPER); - - $sql .= "\n\t".$this->db->_protect_identifiers($field); - - if (array_key_exists('NAME', $attributes)) + if ($alter_type === 'ADD') { - $sql .= ' '.$this->db->_protect_identifiers($attributes['NAME']).' '; + $field[$i]['_literal'] = "\n\tADD "; + } + else + { + $field[$i]['_literal'] = empty($field[$i]['new_name']) ? "\n\tMODIFY " : "\n\tCHANGE "; } - if (array_key_exists('TYPE', $attributes)) + $field[$i] = $field[$i]['_literal'].$this->_process_column($field[$i]); + } + } + + return array($sql.implode(',', $field)); + } + + // -------------------------------------------------------------------- + + /** + * Process column + * + * @param array $field + * @return string + */ + protected function _process_column($field) + { + $extra_clause = isset($field['after']) + ? ' AFTER '.$this->db->escape_identifiers($field['after']) : ''; + + if (empty($extra_clause) && isset($field['first']) && $field['first'] === TRUE) + { + $extra_clause = ' FIRST'; + } + + + return $this->db->escape_identifiers($field['name']) + .(empty($field['new_name']) ? '' : ' '.$this->db->escape_identifiers($field['new_name'])) + .' '.$field['type'].$field['length'] + .$field['unsigned'] + .$field['null'] + .$field['default'] + .$field['auto_increment'] + .$field['unique'] + .(empty($field['comment']) ? '' : ' COMMENT '.$field['comment']) + .$extra_clause; + } + + // -------------------------------------------------------------------- + + /** + * Process indexes + * + * @param string $table (ignored) + * @return string + */ + protected function _process_indexes($table) + { + $sql = ''; + + for ($i = 0, $c = count($this->keys); $i < $c; $i++) + { + if (is_array($this->keys[$i])) + { + for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++) { - $sql .= ' '.$attributes['TYPE']; - - if (array_key_exists('CONSTRAINT', $attributes)) + if ( ! isset($this->fields[$this->keys[$i][$i2]])) { - 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'].')'; - } + unset($this->keys[$i][$i2]); + continue; } } - - 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)) + elseif ( ! isset($this->fields[$this->keys[$i]])) { - $sql .= ','; + unset($this->keys[$i]); + continue; } + + is_array($this->keys[$i]) OR $this->keys[$i] = array($this->keys[$i]); + + $sql .= ",\n\tKEY ".$this->db->escape_identifiers(implode('_', $this->keys[$i])) + .' ('.implode(', ', $this->db->escape_identifiers($this->keys[$i])).')'; } - return $sql; - } + $this->keys = array(); - // -------------------------------------------------------------------- - - /** - * 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/src/system/database/drivers/mysql/mysql_result.php b/src/system/database/drivers/mysql/mysql_result.php index 1ea5418c..20cade2e 100644 --- a/src/system/database/drivers/mysql/mysql_result.php +++ b/src/system/database/drivers/mysql/mysql_result.php @@ -1,20 +1,41 @@ -num_rows = mysql_num_rows($this->result_id); + } + + // -------------------------------------------------------------------- + /** * Number of rows in the result set * - * @access public - * @return integer + * @return int */ - function num_rows() + public function num_rows() { - return @mysql_num_rows($this->result_id); + return $this->num_rows; } // -------------------------------------------------------------------- @@ -43,12 +80,11 @@ class CI_DB_mysql_result extends CI_DB_result { /** * Number of fields in the result set * - * @access public - * @return integer + * @return int */ - function num_fields() + public function num_fields() { - return @mysql_num_fields($this->result_id); + return mysql_num_fields($this->result_id); } // -------------------------------------------------------------------- @@ -58,12 +94,12 @@ class CI_DB_mysql_result extends CI_DB_result { * * Generates an array of column names * - * @access public * @return array */ - function list_fields() + public function list_fields() { $field_names = array(); + mysql_field_seek($this->result_id, 0); while ($field = mysql_fetch_field($this->result_id)) { $field_names[] = $field->name; @@ -79,27 +115,18 @@ class CI_DB_mysql_result extends CI_DB_result { * * Generates an array of objects containing field meta-data * - * @access public * @return array */ - function field_data() + public function field_data() { $retval = array(); - while ($field = mysql_fetch_object($this->result_id)) + for ($i = 0, $c = $this->num_fields(); $i < $c; $i++) { - 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; + $retval[$i] = new stdClass(); + $retval[$i]->name = mysql_field_name($this->result_id, $i); + $retval[$i]->type = mysql_field_type($this->result_id, $i); + $retval[$i]->max_length = mysql_field_len($this->result_id, $i); + $retval[$i]->primary_key = (int) (strpos(mysql_field_flags($this->result_id, $i), 'primary_key') !== FALSE); } return $retval; @@ -110,9 +137,9 @@ class CI_DB_mysql_result extends CI_DB_result { /** * Free the result * - * @return null + * @return void */ - function free_result() + public function free_result() { if (is_resource($this->result_id)) { @@ -126,16 +153,18 @@ class CI_DB_mysql_result extends CI_DB_result { /** * Data Seek * - * Moves the internal pointer to the desired offset. We call + * Moves the internal pointer to the desired offset. We call * this internally before fetching results to make sure the - * result set starts at zero + * result set starts at zero. * - * @access private - * @return array + * @param int $n + * @return bool */ - function _data_seek($n = 0) + public function data_seek($n = 0) { - return mysql_data_seek($this->result_id, $n); + return $this->num_rows + ? mysql_data_seek($this->result_id, $n) + : FALSE; } // -------------------------------------------------------------------- @@ -145,10 +174,9 @@ class CI_DB_mysql_result extends CI_DB_result { * * Returns the result set as an array * - * @access private * @return array */ - function _fetch_assoc() + protected function _fetch_assoc() { return mysql_fetch_assoc($this->result_id); } @@ -160,16 +188,12 @@ class CI_DB_mysql_result extends CI_DB_result { * * Returns the result set as an object * - * @access private + * @param string $class_name * @return object */ - function _fetch_object() + protected function _fetch_object($class_name = 'stdClass') { - return mysql_fetch_object($this->result_id); + return mysql_fetch_object($this->result_id, $class_name); } } - - -/* End of file mysql_result.php */ -/* Location: ./system/database/drivers/mysql/mysql_result.php */ \ No newline at end of file diff --git a/src/system/database/drivers/mysql/mysql_utility.php b/src/system/database/drivers/mysql/mysql_utility.php index c3cea626..4c1f2391 100644 --- a/src/system/database/drivers/mysql/mysql_utility.php +++ b/src/system/database/drivers/mysql/mysql_utility.php @@ -1,84 +1,83 @@ -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 + * @param array $params Preferences * @return mixed */ - function _backup($params = array()) + protected function _backup($params = array()) { - if (count($params) == 0) + if (count($params) === 0) { return FALSE; } @@ -88,16 +87,23 @@ class CI_DB_mysql_utility extends CI_DB_utility { // Build the output $output = ''; - foreach ((array)$tables as $table) + + // Do we need to include a statement to disable foreign key checks? + if ($foreign_key_checks === FALSE) + { + $output .= 'SET foreign_key_checks = 0;'.$newline; + } + + foreach ( (array) $tables as $table) { // Is the table in the "ignore" list? - if (in_array($table, (array)$ignore, TRUE)) + if (in_array($table, (array) $ignore, TRUE)) { continue; } // Get the table schema - $query = $this->db->query("SHOW CREATE TABLE `".$this->db->database.'`.`'.$table.'`'); + $query = $this->db->query('SHOW CREATE TABLE '.$this->db->escape_identifiers($this->db->database.'.'.$table)); // No result means the table name was invalid if ($query === FALSE) @@ -108,9 +114,9 @@ class CI_DB_mysql_utility extends CI_DB_utility { // Write out the table schema $output .= '#'.$newline.'# TABLE STRUCTURE FOR: '.$table.$newline.'#'.$newline.$newline; - if ($add_drop == TRUE) + if ($add_drop === TRUE) { - $output .= 'DROP TABLE IF EXISTS '.$table.';'.$newline.$newline; + $output .= 'DROP TABLE IF EXISTS '.$this->db->protect_identifiers($table).';'.$newline.$newline; } $i = 0; @@ -124,21 +130,21 @@ class CI_DB_mysql_utility extends CI_DB_utility { } // If inserts are not needed we're done... - if ($add_insert == FALSE) + if ($add_insert === FALSE) { continue; } // Grab all the data from the current table - $query = $this->db->query("SELECT * FROM $table"); + $query = $this->db->query('SELECT * FROM '.$this->db->protect_identifiers($table)); - if ($query->num_rows() == 0) + 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 + // integer type. We use this info to decide whether to // surround the data with quotes or not $i = 0; @@ -147,20 +153,17 @@ class CI_DB_mysql_utility extends CI_DB_utility { 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; + $is_int[$i] = in_array(strtolower(mysql_field_type($query->result_id, $i)), + array('tinyint', 'smallint', 'mediumint', 'int', 'bigint'), //, 'timestamp'), + TRUE); // Create a string of field names - $field_str .= '`'.$field->name.'`, '; + $field_str .= $this->db->escape_identifiers($field->name).', '; $i++; } // Trim off the end comma - $field_str = preg_replace( "/, $/" , "" , $field_str); - + $field_str = preg_replace('/, $/' , '', $field_str); // Build the insert string foreach ($query->result_array() as $row) @@ -178,14 +181,7 @@ class CI_DB_mysql_utility extends CI_DB_utility { 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; - } + $val_str .= ($is_int[$i] === FALSE) ? $this->db->escape($v) : $v; } // Append a comma @@ -194,18 +190,22 @@ class CI_DB_mysql_utility extends CI_DB_utility { } // Remove the comma at the end of the string - $val_str = preg_replace( "/, $/" , "" , $val_str); + $val_str = preg_replace('/, $/' , '', $val_str); // Build the INSERT string - $output .= 'INSERT INTO '.$table.' ('.$field_str.') VALUES ('.$val_str.');'.$newline; + $output .= 'INSERT INTO '.$this->db->protect_identifiers($table).' ('.$field_str.') VALUES ('.$val_str.');'.$newline; } $output .= $newline.$newline; } + // Do we need to include a statement to re-enable foreign key checks? + if ($foreign_key_checks === FALSE) + { + $output .= 'SET foreign_key_checks = 1;'.$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/src/system/database/drivers/mysqli/index.html b/src/system/database/drivers/mysqli/index.html index c942a79c..b702fbc3 100644 --- a/src/system/database/drivers/mysqli/index.html +++ b/src/system/database/drivers/mysqli/index.html @@ -1,3 +1,4 @@ + 403 Forbidden @@ -7,4 +8,4 @@

Directory access is forbidden.

- \ No newline at end of file + diff --git a/src/system/database/drivers/mysqli/mysqli_driver.php b/src/system/database/drivers/mysqli/mysqli_driver.php index fb5003ed..06c34187 100644 --- a/src/system/database/drivers/mysqli/mysqli_driver.php +++ b/src/system/database/drivers/mysqli/mysqli_driver.php @@ -1,95 +1,223 @@ -port != '') + // Do we have a socket path? + if ($this->hostname[0] === '/') { - return @mysqli_connect($this->hostname, $this->username, $this->password, $this->database, $this->port); + $hostname = NULL; + $port = NULL; + $socket = $this->hostname; } else { - return @mysqli_connect($this->hostname, $this->username, $this->password, $this->database); + // Persistent connection support was added in PHP 5.3.0 + $hostname = ($persistent === TRUE && is_php('5.3')) + ? 'p:'.$this->hostname : $this->hostname; + $port = empty($this->port) ? NULL : $this->port; + $socket = NULL; } - } + $client_flags = ($this->compress === TRUE) ? MYSQLI_CLIENT_COMPRESS : 0; + $this->_mysqli = mysqli_init(); - // -------------------------------------------------------------------- + $this->_mysqli->options(MYSQLI_OPT_CONNECT_TIMEOUT, 10); - /** - * Persistent database connection - * - * @access private called by the base class - * @return resource - */ - function db_pconnect() - { - return $this->db_connect(); + if (isset($this->stricton)) + { + if ($this->stricton) + { + $this->_mysqli->options(MYSQLI_INIT_COMMAND, 'SET SESSION sql_mode = CONCAT(@@sql_mode, ",", "STRICT_ALL_TABLES")'); + } + else + { + $this->_mysqli->options(MYSQLI_INIT_COMMAND, + 'SET SESSION sql_mode = + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + @@sql_mode, + "STRICT_ALL_TABLES,", ""), + ",STRICT_ALL_TABLES", ""), + "STRICT_ALL_TABLES", ""), + "STRICT_TRANS_TABLES,", ""), + ",STRICT_TRANS_TABLES", ""), + "STRICT_TRANS_TABLES", "")' + ); + } + } + + if (is_array($this->encrypt)) + { + $ssl = array(); + empty($this->encrypt['ssl_key']) OR $ssl['key'] = $this->encrypt['ssl_key']; + empty($this->encrypt['ssl_cert']) OR $ssl['cert'] = $this->encrypt['ssl_cert']; + empty($this->encrypt['ssl_ca']) OR $ssl['ca'] = $this->encrypt['ssl_ca']; + empty($this->encrypt['ssl_capath']) OR $ssl['capath'] = $this->encrypt['ssl_capath']; + empty($this->encrypt['ssl_cipher']) OR $ssl['cipher'] = $this->encrypt['ssl_cipher']; + + if ( ! empty($ssl)) + { + if (isset($this->encrypt['ssl_verify'])) + { + if ($this->encrypt['ssl_verify']) + { + defined('MYSQLI_OPT_SSL_VERIFY_SERVER_CERT') && $this->_mysqli->options(MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, TRUE); + } + // Apparently (when it exists), setting MYSQLI_OPT_SSL_VERIFY_SERVER_CERT + // to FALSE didn't do anything, so PHP 5.6.16 introduced yet another + // constant ... + // + // https://secure.php.net/ChangeLog-5.php#5.6.16 + // https://bugs.php.net/bug.php?id=68344 + elseif (defined('MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT')) + { + $this->_mysqli->options(MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT, TRUE); + } + } + + $client_flags |= MYSQLI_CLIENT_SSL; + $this->_mysqli->ssl_set( + isset($ssl['key']) ? $ssl['key'] : NULL, + isset($ssl['cert']) ? $ssl['cert'] : NULL, + isset($ssl['ca']) ? $ssl['ca'] : NULL, + isset($ssl['capath']) ? $ssl['capath'] : NULL, + isset($ssl['cipher']) ? $ssl['cipher'] : NULL + ); + } + } + + if ($this->_mysqli->real_connect($hostname, $this->username, $this->password, $this->database, $port, $socket, $client_flags)) + { + // Prior to version 5.7.3, MySQL silently downgrades to an unencrypted connection if SSL setup fails + if ( + ($client_flags & MYSQLI_CLIENT_SSL) + && version_compare($this->_mysqli->client_info, '5.7.3', '<=') + && empty($this->_mysqli->query("SHOW STATUS LIKE 'ssl_cipher'")->fetch_object()->Value) + ) + { + $this->_mysqli->close(); + $message = 'MySQLi was configured for an SSL connection, but got an unencrypted connection instead!'; + log_message('error', $message); + return ($this->db->db_debug) ? $this->db->display_error($message, '', TRUE) : FALSE; + } + + return $this->_mysqli; + } + + return FALSE; } // -------------------------------------------------------------------- @@ -100,12 +228,11 @@ class CI_DB_mysqli_driver extends CI_DB { * 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() + public function reconnect() { - if (mysqli_ping($this->conn_id) === FALSE) + if ($this->conn_id !== FALSE && $this->conn_id->ping() === FALSE) { $this->conn_id = FALSE; } @@ -116,12 +243,23 @@ class CI_DB_mysqli_driver extends CI_DB { /** * Select the database * - * @access private called by the base class - * @return resource + * @param string $database + * @return bool */ - function db_select() + public function db_select($database = '') { - return @mysqli_select_db($this->conn_id, $this->database); + if ($database === '') + { + $database = $this->database; + } + + if ($this->conn_id->select_db($database)) + { + $this->database = $database; + return TRUE; + } + + return FALSE; } // -------------------------------------------------------------------- @@ -129,40 +267,29 @@ class CI_DB_mysqli_driver extends CI_DB { /** * Set client character set * - * @access private - * @param string - * @param string - * @return resource + * @param string $charset + * @return bool */ - function _db_set_charset($charset, $collation) + protected function _db_set_charset($charset) { - 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); - } + return $this->conn_id->set_charset($charset); } // -------------------------------------------------------------------- /** - * Version number query string + * Database version number * - * @access public * @return string */ - function _version() + public function version() { - return "SELECT version() AS ver"; + if (isset($this->data_cache['version'])) + { + return $this->data_cache['version']; + } + + return $this->data_cache['version'] = $this->conn_id->server_info; } // -------------------------------------------------------------------- @@ -170,15 +297,12 @@ class CI_DB_mysqli_driver extends CI_DB { /** * Execute the query * - * @access private called by the base class - * @param string an SQL query - * @return resource + * @param string $sql an SQL query + * @return mixed */ - function _execute($sql) + protected function _execute($sql) { - $sql = $this->_prep_query($sql); - $result = @mysqli_query($this->conn_id, $sql); - return $result; + return $this->conn_id->query($this->_prep_query($sql)); } // -------------------------------------------------------------------- @@ -188,20 +312,16 @@ class CI_DB_mysqli_driver extends CI_DB { * * If needed, each database adapter can prep the query string * - * @access private called by execute() - * @param string an SQL query + * @param string $sql an SQL query * @return string */ - function _prep_query($sql) + protected 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) + // mysqli_affected_rows() returns 0 for "DELETE FROM TABLE" queries. This hack + // modifies the query so that it a proper number of affected rows is returned. + if ($this->delete_hack === TRUE && preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $sql)) { - 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 trim($sql).' WHERE 1=1'; } return $sql; @@ -212,30 +332,14 @@ class CI_DB_mysqli_driver extends CI_DB { /** * Begin Transaction * - * @access public * @return bool */ - function trans_begin($test_mode = FALSE) + protected function _trans_begin() { - 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; + $this->conn_id->autocommit(FALSE); + return is_php('5.5') + ? $this->conn_id->begin_transaction() + : $this->simple_query('START TRANSACTION'); // can also be BEGIN or BEGIN WORK } // -------------------------------------------------------------------- @@ -243,25 +347,17 @@ class CI_DB_mysqli_driver extends CI_DB { /** * Commit Transaction * - * @access public * @return bool */ - function trans_commit() + protected function _trans_commit() { - if ( ! $this->trans_enabled) + if ($this->conn_id->commit()) { + $this->conn_id->autocommit(TRUE); 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; + return FALSE; } // -------------------------------------------------------------------- @@ -269,59 +365,30 @@ class CI_DB_mysqli_driver extends CI_DB { /** * Rollback Transaction * - * @access public * @return bool */ - function trans_rollback() + protected function _trans_rollback() { - if ( ! $this->trans_enabled) + if ($this->conn_id->rollback()) { + $this->conn_id->autocommit(TRUE); 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; + return FALSE; } // -------------------------------------------------------------------- /** - * Escape String + * Platform-dependant string escape * - * @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) + protected function _escape_str($str) { - if (is_array($str)) - { - foreach ($str as $key => $val) - { - $str[$key] = $this->escape_str($val, $like); - } - - return $str; - } - - - $str = mysqli_real_escape_string($this->conn_id, $str); - - // escape LIKE condition wildcards - if ($like === TRUE) - { - $str = str_replace(array('%', '_'), array('\\%', '\\_'), $str); - } - - return $str; + return $this->conn_id->real_escape_string($str); } // -------------------------------------------------------------------- @@ -329,12 +396,11 @@ class CI_DB_mysqli_driver extends CI_DB { /** * Affected Rows * - * @access public - * @return integer + * @return int */ - function affected_rows() + public function affected_rows() { - return @mysqli_affected_rows($this->conn_id); + return $this->conn_id->affected_rows; } // -------------------------------------------------------------------- @@ -342,43 +408,11 @@ class CI_DB_mysqli_driver extends CI_DB { /** * Insert ID * - * @access public - * @return integer + * @return int */ - function insert_id() + public 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; + return $this->conn_id->insert_id; } // -------------------------------------------------------------------- @@ -388,17 +422,16 @@ class CI_DB_mysqli_driver extends CI_DB { * * Generates a platform-specific query string so that the table names can be fetched * - * @access private - * @param boolean + * @param bool $prefix_limit * @return string */ - function _list_tables($prefix_limit = FALSE) + protected function _list_tables($prefix_limit = FALSE) { - $sql = "SHOW TABLES FROM ".$this->_escape_char.$this->database.$this->_escape_char; + $sql = 'SHOW TABLES FROM '.$this->escape_identifiers($this->database); - if ($prefix_limit !== FALSE AND $this->dbprefix != '') + if ($prefix_limit !== FALSE && $this->dbprefix !== '') { - $sql .= " LIKE '".$this->escape_like_str($this->dbprefix)."%'"; + return $sql." LIKE '".$this->escape_like_str($this->dbprefix)."%'"; } return $sql; @@ -411,338 +444,89 @@ class CI_DB_mysqli_driver extends CI_DB { * * Generates a platform-specific query string so that the column names can be fetched * - * @access public - * @param string the table name + * @param string $table * @return string */ - function _list_columns($table = '') + protected function _list_columns($table = '') { - return "SHOW COLUMNS FROM ".$this->_protect_identifiers($table, TRUE, NULL, FALSE); + return 'SHOW COLUMNS FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE); } // -------------------------------------------------------------------- /** - * Field data query + * Returns an object with field data * - * Generates a platform-specific query so that the column data can be retrieved - * - * @access public - * @param string the table name - * @return object + * @param string $table + * @return array */ - function _field_data($table) + public 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 == '') + if (($query = $this->query('SHOW COLUMNS FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE))) === FALSE) { - return $item; + return FALSE; + } + $query = $query->result_object(); + + $retval = array(); + for ($i = 0, $c = count($query); $i < $c; $i++) + { + $retval[$i] = new stdClass(); + $retval[$i]->name = $query[$i]->Field; + + sscanf($query[$i]->Type, '%[a-z](%d)', + $retval[$i]->type, + $retval[$i]->max_length + ); + + $retval[$i]->default = $query[$i]->Default; + $retval[$i]->primary_key = (int) ($query[$i]->Key === 'PRI'); } - 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); + return $retval; } // -------------------------------------------------------------------- /** - * From Tables + * Error * - * This function implicitly groups FROM tables so there is no confusion - * about operator precedence in harmony with SQL standards + * Returns an array containing code and message of the last + * database error that has occurred. * - * @access public - * @param type - * @return type + * @return array */ - function _from_tables($tables) + public function error() { - if ( ! is_array($tables)) + if ( ! empty($this->_mysqli->connect_errno)) { - $tables = array($tables); + return array( + 'code' => $this->_mysqli->connect_errno, + 'message' => is_php('5.2.9') ? $this->_mysqli->connect_error : mysqli_connect_error() + ); } - return '('.implode(', ', $tables).')'; + return array('code' => $this->conn_id->errno, 'message' => $this->conn_id->error); } // -------------------------------------------------------------------- /** - * Insert statement + * FROM tables * - * Generates a platform-specific insert string from the supplied data + * Groups tables in FROM clauses if needed, so there is no confusion + * about operator precedence. * - * @access public - * @param string the table name - * @param array the insert keys - * @param array the insert values * @return string */ - function _insert($table, $keys, $values) + protected function _from_tables() { - 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) + if ( ! empty($this->qb_join) && count($this->qb_from) > 1) { - $valstr[] = $key." = ".$val; + return '('.implode(', ', $this->qb_from).')'; } - $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; + return implode(', ', $this->qb_from); } // -------------------------------------------------------------------- @@ -750,18 +534,11 @@ class CI_DB_mysqli_driver extends CI_DB { /** * Close DB Connection * - * @access public - * @param resource * @return void */ - function _close($conn_id) + protected function _close() { - @mysqli_close($conn_id); + $this->conn_id->close(); } - } - - -/* End of file mysqli_driver.php */ -/* Location: ./system/database/drivers/mysqli/mysqli_driver.php */ \ No newline at end of file diff --git a/src/system/database/drivers/mysqli/mysqli_forge.php b/src/system/database/drivers/mysqli/mysqli_forge.php index cc400d40..c17f729c 100644 --- a/src/system/database/drivers/mysqli/mysqli_forge.php +++ b/src/system/database/drivers/mysqli/mysqli_forge.php @@ -1,259 +1,244 @@ -$attributes) + foreach (array_keys($attributes) as $key) { - // 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)) + if (is_string($key)) { - $sql .= "\n\t$attributes"; + $sql .= ' '.strtoupper($key).' = '.$attributes[$key]; + } + } + + if ( ! empty($this->db->char_set) && ! strpos($sql, 'CHARACTER SET') && ! strpos($sql, 'CHARSET')) + { + $sql .= ' DEFAULT CHARACTER SET = '.$this->db->char_set; + } + + if ( ! empty($this->db->dbcollat) && ! strpos($sql, 'COLLATE')) + { + $sql .= ' COLLATE = '.$this->db->dbcollat; + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * ALTER TABLE + * + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * @return string|string[] + */ + protected function _alter_table($alter_type, $table, $field) + { + if ($alter_type === 'DROP') + { + return parent::_alter_table($alter_type, $table, $field); + } + + $sql = 'ALTER TABLE '.$this->db->escape_identifiers($table); + for ($i = 0, $c = count($field); $i < $c; $i++) + { + if ($field[$i]['_literal'] !== FALSE) + { + $field[$i] = ($alter_type === 'ADD') + ? "\n\tADD ".$field[$i]['_literal'] + : "\n\tMODIFY ".$field[$i]['_literal']; } else { - $attributes = array_change_key_case($attributes, CASE_UPPER); - - $sql .= "\n\t".$this->db->_protect_identifiers($field); - - if (array_key_exists('NAME', $attributes)) + if ($alter_type === 'ADD') { - $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'; + $field[$i]['_literal'] = "\n\tADD "; } else { - $sql .= ' NOT NULL'; + $field[$i]['_literal'] = empty($field[$i]['new_name']) ? "\n\tMODIFY " : "\n\tCHANGE "; } - 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 .= ','; + $field[$i] = $field[$i]['_literal'].$this->_process_column($field[$i]); } } - return $sql; + return array($sql.implode(',', $field)); } // -------------------------------------------------------------------- /** - * Create Table + * Process column * - * @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 + * @param array $field * @return string */ - function _drop_table($table) + protected function _process_column($field) { - return "DROP TABLE IF EXISTS ".$this->db->_escape_identifiers($table); + $extra_clause = isset($field['after']) + ? ' AFTER '.$this->db->escape_identifiers($field['after']) : ''; + + if (empty($extra_clause) && isset($field['first']) && $field['first'] === TRUE) + { + $extra_clause = ' FIRST'; + } + + return $this->db->escape_identifiers($field['name']) + .(empty($field['new_name']) ? '' : ' '.$this->db->escape_identifiers($field['new_name'])) + .' '.$field['type'].$field['length'] + .$field['unsigned'] + .$field['null'] + .$field['default'] + .$field['auto_increment'] + .$field['unique'] + .(empty($field['comment']) ? '' : ' COMMENT '.$field['comment']) + .$extra_clause; } // -------------------------------------------------------------------- /** - * Alter table query + * Process indexes * - * 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 + * @param string $table (ignored) * @return string */ - function _rename_table($table_name, $new_table_name) + protected function _process_indexes($table) { - $sql = 'ALTER TABLE '.$this->db->_protect_identifiers($table_name)." RENAME TO ".$this->db->_protect_identifiers($new_table_name); + $sql = ''; + + for ($i = 0, $c = count($this->keys); $i < $c; $i++) + { + if (is_array($this->keys[$i])) + { + for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++) + { + if ( ! isset($this->fields[$this->keys[$i][$i2]])) + { + unset($this->keys[$i][$i2]); + continue; + } + } + } + elseif ( ! isset($this->fields[$this->keys[$i]])) + { + unset($this->keys[$i]); + continue; + } + + is_array($this->keys[$i]) OR $this->keys[$i] = array($this->keys[$i]); + + $sql .= ",\n\tKEY ".$this->db->escape_identifiers(implode('_', $this->keys[$i])) + .' ('.implode(', ', $this->db->escape_identifiers($this->keys[$i])).')'; + } + + $this->keys = array(); + return $sql; } } - -/* End of file mysqli_forge.php */ -/* Location: ./system/database/drivers/mysqli/mysqli_forge.php */ \ No newline at end of file diff --git a/src/system/database/drivers/mysqli/mysqli_result.php b/src/system/database/drivers/mysqli/mysqli_result.php index 4b6bcfbc..0ce07414 100644 --- a/src/system/database/drivers/mysqli/mysqli_result.php +++ b/src/system/database/drivers/mysqli/mysqli_result.php @@ -1,41 +1,65 @@ -result_id); + return is_int($this->num_rows) + ? $this->num_rows + : $this->num_rows = $this->result_id->num_rows; } // -------------------------------------------------------------------- @@ -43,12 +67,11 @@ class CI_DB_mysqli_result extends CI_DB_result { /** * Number of fields in the result set * - * @access public - * @return integer + * @return int */ - function num_fields() + public function num_fields() { - return @mysqli_num_fields($this->result_id); + return $this->result_id->field_count; } // -------------------------------------------------------------------- @@ -58,13 +81,13 @@ class CI_DB_mysqli_result extends CI_DB_result { * * Generates an array of column names * - * @access public * @return array */ - function list_fields() + public function list_fields() { $field_names = array(); - while ($field = mysqli_fetch_field($this->result_id)) + $this->result_id->field_seek(0); + while ($field = $this->result_id->fetch_field()) { $field_names[] = $field->name; } @@ -79,44 +102,37 @@ class CI_DB_mysqli_result extends CI_DB_result { * * Generates an array of objects containing field meta-data * - * @access public * @return array */ - function field_data() + public function field_data() { $retval = array(); - while ($field = mysqli_fetch_object($this->result_id)) + $field_data = $this->result_id->fetch_fields(); + for ($i = 0, $c = count($field_data); $i < $c; $i++) { - 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; + $retval[$i] = new stdClass(); + $retval[$i]->name = $field_data[$i]->name; + $retval[$i]->type = $field_data[$i]->type; + $retval[$i]->max_length = $field_data[$i]->max_length; + $retval[$i]->primary_key = (int) ($field_data[$i]->flags & 2); + $retval[$i]->default = $field_data[$i]->def; } return $retval; } - + // -------------------------------------------------------------------- /** * Free the result * - * @return null + * @return void */ - function free_result() + public function free_result() { if (is_object($this->result_id)) { - mysqli_free_result($this->result_id); + $this->result_id->free(); $this->result_id = FALSE; } } @@ -126,16 +142,16 @@ class CI_DB_mysqli_result extends CI_DB_result { /** * Data Seek * - * Moves the internal pointer to the desired offset. We call + * Moves the internal pointer to the desired offset. We call * this internally before fetching results to make sure the - * result set starts at zero + * result set starts at zero. * - * @access private - * @return array + * @param int $n + * @return bool */ - function _data_seek($n = 0) + public function data_seek($n = 0) { - return mysqli_data_seek($this->result_id, $n); + return $this->result_id->data_seek($n); } // -------------------------------------------------------------------- @@ -145,12 +161,11 @@ class CI_DB_mysqli_result extends CI_DB_result { * * Returns the result set as an array * - * @access private * @return array */ - function _fetch_assoc() + protected function _fetch_assoc() { - return mysqli_fetch_assoc($this->result_id); + return $this->result_id->fetch_assoc(); } // -------------------------------------------------------------------- @@ -160,16 +175,12 @@ class CI_DB_mysqli_result extends CI_DB_result { * * Returns the result set as an object * - * @access private + * @param string $class_name * @return object */ - function _fetch_object() + protected function _fetch_object($class_name = 'stdClass') { - return mysqli_fetch_object($this->result_id); + return $this->result_id->fetch_object($class_name); } } - - -/* End of file mysqli_result.php */ -/* Location: ./system/database/drivers/mysqli/mysqli_result.php */ \ No newline at end of file diff --git a/src/system/database/drivers/mysqli/mysqli_utility.php b/src/system/database/drivers/mysqli/mysqli_utility.php index 93b0da0b..79d9f367 100644 --- a/src/system/database/drivers/mysqli/mysqli_utility.php +++ b/src/system/database/drivers/mysqli/mysqli_utility.php @@ -1,88 +1,213 @@ -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 + * @param array $params Preferences * @return mixed */ - function _backup($params = array()) + protected function _backup($params = array()) { - // Currently unsupported - return $this->db->display_error('db_unsuported_feature'); - } -} + if (count($params) === 0) + { + return FALSE; + } -/* End of file mysqli_utility.php */ -/* Location: ./system/database/drivers/mysqli/mysqli_utility.php */ \ No newline at end of file + // Extract the prefs for simplicity + extract($params); + + // Build the output + $output = ''; + + // Do we need to include a statement to disable foreign key checks? + if ($foreign_key_checks === FALSE) + { + $output .= 'SET foreign_key_checks = 0;'.$newline; + } + + 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->escape_identifiers($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 '.$this->db->protect_identifiers($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 '.$this->db->protect_identifiers($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 = $query->result_id->fetch_field()) + { + // Most versions of MySQL store timestamp as a string + $is_int[$i] = in_array(strtolower($field->type), + array('tinyint', 'smallint', 'mediumint', 'int', 'bigint'), //, 'timestamp'), + TRUE); + + // Create a string of field names + $field_str .= $this->db->escape_identifiers($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 + $val_str .= ($is_int[$i] === FALSE) ? $this->db->escape($v) : $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 '.$this->db->protect_identifiers($table).' ('.$field_str.') VALUES ('.$val_str.');'.$newline; + } + + $output .= $newline.$newline; + } + + // Do we need to include a statement to re-enable foreign key checks? + if ($foreign_key_checks === FALSE) + { + $output .= 'SET foreign_key_checks = 1;'.$newline; + } + + return $output; + } + +} diff --git a/src/system/database/drivers/oci8/index.html b/src/system/database/drivers/oci8/index.html index c942a79c..b702fbc3 100644 --- a/src/system/database/drivers/oci8/index.html +++ b/src/system/database/drivers/oci8/index.html @@ -1,3 +1,4 @@ + 403 Forbidden @@ -7,4 +8,4 @@

Directory access is forbidden.

- \ No newline at end of file + diff --git a/src/system/database/drivers/oci8/oci8_driver.php b/src/system/database/drivers/oci8/oci8_driver.php index 0728a779..59f0e845 100644 --- a/src/system/database/drivers/oci8/oci8_driver.php +++ b/src/system/database/drivers/oci8/oci8_driver.php @@ -1,33 +1,54 @@ - '/^\(DESCRIPTION=(\(.+\)){2,}\)$/', // TNS + // Easy Connect string (Oracle 10g+) + 'ec' => '/^(\/\/)?[a-z0-9.:_-]+(:[1-9][0-9]{0,4})?(\/[a-z0-9$_]+)?(:[^\/])?(\/[a-z0-9$_]+)?$/i', + 'in' => '/^[a-z0-9$_]+$/i' // Instance name (defined in tnsnames.ora) + ); + + /* Space characters don't have any effect when actually + * connecting, but can be a hassle while validating the DSN. + */ + $this->dsn = str_replace(array("\n", "\r", "\t", ' '), '', $this->dsn); + + if ($this->dsn !== '') + { + foreach ($valid_dsns as $regexp) + { + if (preg_match($regexp, $this->dsn)) + { + return; + } + } + } + + // Legacy support for TNS in the hostname configuration field + $this->hostname = str_replace(array("\n", "\r", "\t", ' '), '', $this->hostname); + if (preg_match($valid_dsns['tns'], $this->hostname)) + { + $this->dsn = $this->hostname; + return; + } + elseif ($this->hostname !== '' && strpos($this->hostname, '/') === FALSE && strpos($this->hostname, ':') === FALSE + && (( ! empty($this->port) && ctype_digit($this->port)) OR $this->database !== '')) + { + /* If the hostname field isn't empty, doesn't contain + * ':' and/or '/' and if port and/or database aren't + * empty, then the hostname field is most likely indeed + * just a hostname. Therefore we'll try and build an + * Easy Connect string from these 3 settings, assuming + * that the database field is a service name. + */ + $this->dsn = $this->hostname + .(( ! empty($this->port) && ctype_digit($this->port)) ? ':'.$this->port : '') + .($this->database !== '' ? '/'.ltrim($this->database, '/') : ''); + + if (preg_match($valid_dsns['ec'], $this->dsn)) + { + return; + } + } + + /* At this point, we can only try and validate the hostname and + * database fields separately as DSNs. + */ + if (preg_match($valid_dsns['ec'], $this->hostname) OR preg_match($valid_dsns['in'], $this->hostname)) + { + $this->dsn = $this->hostname; + return; + } + + $this->database = str_replace(array("\n", "\r", "\t", ' '), '', $this->database); + foreach ($valid_dsns as $regexp) + { + if (preg_match($regexp, $this->database)) + { + return; + } + } + + /* Well - OK, an empty string should work as well. + * PHP will try to use environment variables to + * determine which Oracle instance to connect to. + */ + $this->dsn = ''; + } + + // -------------------------------------------------------------------- /** * Non-persistent database connection * - * @access private called by the base class - * @return resource - */ - public function db_connect() - { - return @oci_connect($this->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 + * @param bool $persistent * @return resource */ - public function db_set_charset($charset, $collation) + public function db_connect($persistent = FALSE) { - // @todo - add support if needed - return TRUE; + $func = ($persistent === TRUE) ? 'oci_pconnect' : 'oci_connect'; + return empty($this->char_set) + ? $func($this->username, $this->password, $this->dsn) + : $func($this->username, $this->password, $this->dsn, $this->char_set); } // -------------------------------------------------------------------- /** - * Version number query string + * Database version number * - * @access protected - * @return string + * @return string */ - protected function _version() + public function version() { - return oci_server_version($this->conn_id); + if (isset($this->data_cache['version'])) + { + return $this->data_cache['version']; + } + + if ( ! $this->conn_id OR ($version_string = oci_server_version($this->conn_id)) === FALSE) + { + return FALSE; + } + elseif (preg_match('#Release\s(\d+(?:\.\d+)+)#', $version_string, $match)) + { + return $this->data_cache['version'] = $match[1]; + } + + return FALSE; } // -------------------------------------------------------------------- @@ -159,63 +269,33 @@ class CI_DB_oci8_driver extends CI_DB { /** * Execute the query * - * @access protected called by the base class - * @param string an SQL query - * @return resource + * @param string $sql 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)) + /* Oracle must parse the query before it is run. All of the actions with + * the query are based on the statement id returned by oci_parse(). + */ + if ($this->_reset_stmt_id === TRUE) { - $this->stmt_id = oci_parse($this->conn_id, $this->_prep_query($sql)); + $this->stmt_id = oci_parse($this->conn_id, $sql); } + + oci_set_prefetch($this->stmt_id, 1000); + return oci_execute($this->stmt_id, $this->commit_mode); } // -------------------------------------------------------------------- /** - * Prep the query + * Get cursor. Returns a cursor from the database * - * 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 + * @return resource */ public function get_cursor() { - $this->curs_id = oci_new_cursor($this->conn_id); - return $this->curs_id; + return $this->curs_id = oci_new_cursor($this->conn_id); } // -------------------------------------------------------------------- @@ -223,52 +303,49 @@ class CI_DB_oci8_driver extends CI_DB { /** * 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 + * @param string package name in which the stored procedure is in + * @param string stored procedure name to execute + * @param array parameters + * @return mixed * * 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 + * 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) + public function stored_procedure($package, $procedure, array $params) { - if ($package == '' OR $procedure == '' OR ! is_array($params)) + if ($package === '' OR $procedure === '') { - if ($this->db_debug) - { - log_message('error', 'Invalid query: '.$package.'.'.$procedure); - return $this->display_error('db_invalid_query'); - } - return FALSE; + log_message('error', 'Invalid query: '.$package.'.'.$procedure); + return ($this->db_debug) ? $this->display_error('db_invalid_query') : FALSE; } - // build the query string - $sql = "begin $package.$procedure("; + // Build the query string + $sql = 'BEGIN '.$package.'.'.$procedure.'('; $have_cursor = FALSE; foreach ($params as $param) { - $sql .= $param['name'] . ","; + $sql .= $param['name'].','; - if (array_key_exists('type', $param) && ($param['type'] === OCI_B_CURSOR)) + if (isset($param['type']) && $param['type'] === OCI_B_CURSOR) { $have_cursor = TRUE; } } - $sql = trim($sql, ",") . "); end;"; + $sql = trim($sql, ',').'); END;'; - $this->stmt_id = FALSE; - $this->_set_stmt_id($sql); + $this->_reset_stmt_id = FALSE; + $this->stmt_id = oci_parse($this->conn_id, $sql); $this->_bind_params($params); - $this->query($sql, FALSE, $have_cursor); + $result = $this->query($sql, FALSE, $have_cursor); + $this->_reset_stmt_id = TRUE; + return $result; } // -------------------------------------------------------------------- @@ -276,10 +353,10 @@ class CI_DB_oci8_driver extends CI_DB { /** * Bind parameters * - * @access private - * @return none + * @param array $params + * @return void */ - private function _bind_params($params) + protected function _bind_params($params) { if ( ! is_array($params) OR ! is_resource($this->stmt_id)) { @@ -305,28 +382,11 @@ class CI_DB_oci8_driver extends CI_DB { /** * Begin Transaction * - * @access public * @return bool */ - public function trans_begin($test_mode = FALSE) + protected function _trans_begin() { - 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; + $this->commit_mode = is_php('5.3.2') ? OCI_NO_AUTO_COMMIT : OCI_DEFAULT; return TRUE; } @@ -335,25 +395,13 @@ class CI_DB_oci8_driver extends CI_DB { /** * Commit Transaction * - * @access public * @return bool */ - public function trans_commit() + protected function _trans_commit() { - if ( ! $this->trans_enabled) - { - return TRUE; - } + $this->commit_mode = OCI_COMMIT_ON_SUCCESS; - // 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; + return oci_commit($this->conn_id); } // -------------------------------------------------------------------- @@ -361,60 +409,12 @@ class CI_DB_oci8_driver extends CI_DB { /** * Rollback Transaction * - * @access public * @return bool */ - public function trans_rollback() + protected 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; + $this->commit_mode = OCI_COMMIT_ON_SUCCESS; + return oci_rollback($this->conn_id); } // -------------------------------------------------------------------- @@ -422,12 +422,11 @@ class CI_DB_oci8_driver extends CI_DB { /** * Affected Rows * - * @access public - * @return integer + * @return int */ public function affected_rows() { - return @oci_num_rows($this->stmt_id); + return oci_num_rows($this->stmt_id); } // -------------------------------------------------------------------- @@ -435,8 +434,7 @@ class CI_DB_oci8_driver extends CI_DB { /** * Insert ID * - * @access public - * @return integer + * @return int */ public function insert_id() { @@ -446,53 +444,22 @@ class CI_DB_oci8_driver extends CI_DB { // -------------------------------------------------------------------- - /** - * "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 + * @param bool $prefix_limit * @return string */ protected function _list_tables($prefix_limit = FALSE) { - $sql = "SELECT TABLE_NAME FROM ALL_TABLES"; + $sql = 'SELECT "TABLE_NAME" FROM "ALL_TABLES"'; - if ($prefix_limit !== FALSE AND $this->dbprefix != '') + if ($prefix_limit !== FALSE && $this->dbprefix !== '') { - $sql .= " WHERE TABLE_NAME LIKE '".$this->escape_like_str($this->dbprefix)."%' ".sprintf($this->_like_escape_str, $this->_like_escape_chr); + return $sql.' WHERE "TABLE_NAME" LIKE \''.$this->escape_like_str($this->dbprefix)."%' " + .sprintf($this->_like_escape_str, $this->_like_escape_chr); } return $sql; @@ -505,155 +472,123 @@ class CI_DB_oci8_driver extends CI_DB { * * Generates a platform-specific query string so that the column names can be fetched * - * @access protected - * @param string the table name - * @return string + * @param string $table + * @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 == '') + if (strpos($table, '.') !== FALSE) { - 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; + sscanf($table, '%[^.].%s', $owner, $table); } else { - $str = $this->_escape_char.$item.$this->_escape_char; + $owner = $this->username; } - // remove duplicates if the user already included the escape - return preg_replace('/['.$this->_escape_char.']+/', $this->_escape_char, $str); + return 'SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS + WHERE UPPER(OWNER) = '.$this->escape(strtoupper($owner)).' + AND UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table)); } // -------------------------------------------------------------------- /** - * From Tables + * Returns an object with field data * - * 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 + * @param string $table + * @return array */ - protected function _from_tables($tables) + public function field_data($table) { - if ( ! is_array($tables)) + if (strpos($table, '.') !== FALSE) { - $tables = array($tables); + sscanf($table, '%[^.].%s', $owner, $table); + } + else + { + $owner = $this->username; } - return implode(', ', $tables); + $sql = 'SELECT COLUMN_NAME, DATA_TYPE, CHAR_LENGTH, DATA_PRECISION, DATA_LENGTH, DATA_DEFAULT, NULLABLE + FROM ALL_TAB_COLUMNS + WHERE UPPER(OWNER) = '.$this->escape(strtoupper($owner)).' + AND UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table)); + + if (($query = $this->query($sql)) === FALSE) + { + return FALSE; + } + $query = $query->result_object(); + + $retval = array(); + for ($i = 0, $c = count($query); $i < $c; $i++) + { + $retval[$i] = new stdClass(); + $retval[$i]->name = $query[$i]->COLUMN_NAME; + $retval[$i]->type = $query[$i]->DATA_TYPE; + + $length = ($query[$i]->CHAR_LENGTH > 0) + ? $query[$i]->CHAR_LENGTH : $query[$i]->DATA_PRECISION; + if ($length === NULL) + { + $length = $query[$i]->DATA_LENGTH; + } + $retval[$i]->max_length = $length; + + $default = $query[$i]->DATA_DEFAULT; + if ($default === NULL && $query[$i]->NULLABLE === 'N') + { + $default = ''; + } + $retval[$i]->default = $default; + } + + return $retval; } // -------------------------------------------------------------------- /** - * Insert statement + * Error * - * Generates a platform-specific insert string from the supplied data + * Returns an array containing code and message of the last + * database error that has occured. * - * @access public - * @param string the table name - * @param array the insert keys - * @param array the insert values - * @return string + * @return array */ - protected function _insert($table, $keys, $values) + public function error() { - return "INSERT INTO ".$table." (".implode(', ', $keys).") VALUES (".implode(', ', $values).")"; + /* oci_error() returns an array that already contains the + * 'code' and 'message' keys, so we can just return it. + */ + if (is_resource($this->curs_id)) + { + return oci_error($this->curs_id); + } + elseif (is_resource($this->stmt_id)) + { + return oci_error($this->stmt_id); + } + elseif (is_resource($this->conn_id)) + { + return oci_error($this->conn_id); + } + + return oci_error(); } // -------------------------------------------------------------------- /** - * Insert_batch statement + * 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 + * @param string $table Table name + * @param array $keys INSERT keys + * @param array $values INSERT values + * @return string */ protected function _insert_batch($table, $keys, $values) { @@ -662,47 +597,10 @@ class CI_DB_oci8_driver extends CI_DB { for ($i = 0, $c = count($values); $i < $c; $i++) { - $sql .= ' INTO ' . $table . ' (' . $keys . ') VALUES ' . $values[$i] . "\n"; + $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; + return $sql.'SELECT * FROM dual'; } // -------------------------------------------------------------------- @@ -711,16 +609,16 @@ class CI_DB_oci8_driver extends CI_DB { * 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 + * If the database does not support the TRUNCATE statement, + * then this method maps to 'DELETE FROM table' + * + * @param string $table * @return string */ protected function _truncate($table) { - return "TRUNCATE TABLE ".$table; + return 'TRUNCATE TABLE '.$table; } // -------------------------------------------------------------------- @@ -730,60 +628,43 @@ class CI_DB_oci8_driver extends CI_DB { * * 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 + * @param string $table * @return string */ - protected function _delete($table, $where = array(), $like = array(), $limit = FALSE) + protected function _delete($table) { - $conditions = ''; - - if (count($where) > 0 OR count($like) > 0) + if ($this->qb_limit) { - $conditions = "\nWHERE "; - $conditions .= implode("\n", $this->ar_where); - - if (count($where) > 0 && count($like) > 0) - { - $conditions .= " AND "; - } - $conditions .= implode("\n", $like); + $this->where('rownum <= ',$this->qb_limit, FALSE); + $this->qb_limit = FALSE; } - $limit = ( ! $limit) ? '' : ' LIMIT '.$limit; - - return "DELETE FROM ".$table.$conditions.$limit; + return parent::_delete($table); } // -------------------------------------------------------------------- /** - * Limit string + * LIMIT * * 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 + * @param string $sql SQL Query + * @return string */ - protected function _limit($sql, $limit, $offset) + protected function _limit($sql) { - $limit = $offset + $limit; - $newsql = "SELECT * FROM (select inner_query.*, rownum rnum FROM ($sql) inner_query WHERE rownum < $limit)"; - - if ($offset != 0) + if (version_compare($this->version(), '12.1', '>=')) { - $newsql .= " WHERE rnum >= $offset"; + // OFFSET-FETCH can be used only with the ORDER BY clause + empty($this->qb_orderby) && $sql .= ' ORDER BY 1'; + + return $sql.' OFFSET '.(int) $this->qb_offset.' ROWS FETCH NEXT '.$this->qb_limit.' ROWS ONLY'; } - // remember that we used limits $this->limit_used = TRUE; - - return $newsql; + return 'SELECT * FROM (SELECT inner_query.*, rownum rnum FROM ('.$sql.') inner_query WHERE rownum < '.($this->qb_offset + $this->qb_limit + 1).')' + .($this->qb_offset ? ' WHERE rnum >= '.($this->qb_offset + 1) : ''); } // -------------------------------------------------------------------- @@ -791,19 +672,11 @@ class CI_DB_oci8_driver extends CI_DB { /** * Close DB Connection * - * @access protected - * @param resource - * @return void + * @return void */ - protected function _close($conn_id) + protected function _close() { - @oci_close($conn_id); + oci_close($this->conn_id); } - } - - - -/* End of file oci8_driver.php */ -/* Location: ./system/database/drivers/oci8/oci8_driver.php */ diff --git a/src/system/database/drivers/oci8/oci8_forge.php b/src/system/database/drivers/oci8/oci8_forge.php index 11918018..1ca559a3 100644 --- a/src/system/database/drivers/oci8/oci8_forge.php +++ b/src/system/database/drivers/oci8/oci8_forge.php @@ -1,249 +1,149 @@ -db->_escape_identifiers($table)." ("; - $current_field_count = 0; - - foreach ($fields as $field=>$attributes) + $sql = 'ALTER TABLE '.$this->db->escape_identifiers($table); + $sqls = array(); + for ($i = 0, $c = count($field); $i < $c; $i++) { - // 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)) + if ($field[$i]['_literal'] !== FALSE) { - $sql .= "\n\t$attributes"; + $field[$i] = "\n\t".$field[$i]['_literal']; } else { - $attributes = array_change_key_case($attributes, CASE_UPPER); + $field[$i]['_literal'] = "\n\t".$this->_process_column($field[$i]); - $sql .= "\n\t".$this->db->_protect_identifiers($field); - - $sql .= ' '.$attributes['TYPE']; - - if (array_key_exists('CONSTRAINT', $attributes)) + if ( ! empty($field[$i]['comment'])) { - $sql .= '('.$attributes['CONSTRAINT'].')'; + $sqls[] = 'COMMENT ON COLUMN ' + .$this->db->escape_identifiers($table).'.'.$this->db->escape_identifiers($field[$i]['name']) + .' IS '.$field[$i]['comment']; } - if (array_key_exists('UNSIGNED', $attributes) && $attributes['UNSIGNED'] === TRUE) + if ($alter_type === 'MODIFY' && ! empty($field[$i]['new_name'])) { - $sql .= ' UNSIGNED'; + $sqls[] = $sql.' RENAME COLUMN '.$this->db->escape_identifiers($field[$i]['name']) + .' '.$this->db->escape_identifiers($field[$i]['new_name']); } - - 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)"; + $sql .= ' '.$alter_type.' '; + $sql .= (count($field) === 1) + ? $field[0] + : '('.implode(',', $field).')'; + // RENAME COLUMN must be executed after MODIFY + array_unshift($sqls, $sql); return $sql; } // -------------------------------------------------------------------- /** - * Drop Table + * Field attribute AUTO_INCREMENT * - * @access private - * @return bool + * @param array &$attributes + * @param array &$field + * @return void */ - function _drop_table($table) + protected function _attr_auto_increment(&$attributes, &$field) { - return FALSE; + // Not supported - sequences and triggers must be used instead } - // -------------------------------------------------------------------- - - /** - * 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/src/system/database/drivers/oci8/oci8_result.php b/src/system/database/drivers/oci8/oci8_result.php index 2696421f..fc860ea1 100644 --- a/src/system/database/drivers/oci8/oci8_result.php +++ b/src/system/database/drivers/oci8/oci8_result.php @@ -1,20 +1,41 @@ -num_rows === 0 && count($this->result_array()) > 0) - { - $this->num_rows = count($this->result_array()); - @oci_execute($this->stmt_id, OCI_DEFAULT); + parent::__construct($driver_object); - if ($this->curs_id) - { - @oci_execute($this->curs_id, OCI_DEFAULT); - } - } - - return $this->num_rows; + $this->stmt_id = $driver_object->stmt_id; + $this->curs_id = $driver_object->curs_id; + $this->limit_used = $driver_object->limit_used; + $this->commit_mode =& $driver_object->commit_mode; + $driver_object->stmt_id = FALSE; } // -------------------------------------------------------------------- @@ -60,20 +100,14 @@ class CI_DB_oci8_result extends CI_DB_result { /** * Number of fields in the result set * - * @access public - * @return integer + * @return int */ public function num_fields() { - $count = @oci_num_fields($this->stmt_id); + $count = oci_num_fields($this->stmt_id); // if we used a limit we subtract it - if ($this->limit_used) - { - $count = $count - 1; - } - - return $count; + return ($this->limit_used) ? $count - 1 : $count; } // -------------------------------------------------------------------- @@ -83,7 +117,6 @@ class CI_DB_oci8_result extends CI_DB_result { * * Generates an array of column names * - * @access public * @return array */ public function list_fields() @@ -103,18 +136,17 @@ class CI_DB_oci8_result extends CI_DB_result { * * Generates an array of objects containing field meta-data * - * @access public - * @return array + * @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); + $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; } @@ -127,7 +159,7 @@ class CI_DB_oci8_result extends CI_DB_result { /** * Free the result * - * @return null + * @return void */ public function free_result() { @@ -136,6 +168,17 @@ class CI_DB_oci8_result extends CI_DB_result { oci_free_statement($this->result_id); $this->result_id = FALSE; } + + if (is_resource($this->stmt_id)) + { + oci_free_statement($this->stmt_id); + } + + if (is_resource($this->curs_id)) + { + oci_cancel($this->curs_id); + $this->curs_id = NULL; + } } // -------------------------------------------------------------------- @@ -145,8 +188,7 @@ class CI_DB_oci8_result extends CI_DB_result { * * Returns the result set as an array * - * @access protected - * @return array + * @return array */ protected function _fetch_assoc() { @@ -161,58 +203,27 @@ class CI_DB_oci8_result extends CI_DB_result { * * Returns the result set as an object * - * @access protected - * @return object + * @param string $class_name + * @return object */ - protected function _fetch_object() + protected function _fetch_object($class_name = 'stdClass') { - $id = ($this->curs_id) ? $this->curs_id : $this->stmt_id; - return @oci_fetch_object($id); - } + $row = ($this->curs_id) + ? oci_fetch_object($this->curs_id) + : oci_fetch_object($this->stmt_id); - // -------------------------------------------------------------------- - - /** - * Query result. "array" version. - * - * @access public - * @return array - */ - public function result_array() - { - if (count($this->result_array) > 0) + if ($class_name === 'stdClass' OR ! $row) { - return $this->result_array; + return $row; } - $row = NULL; - while ($row = $this->_fetch_assoc()) + $class_name = new $class_name(); + foreach ($row as $key => $value) { - $this->result_array[] = $row; + $class_name->$key = $value; } - 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 + return $class_name; } } - - -/* End of file oci8_result.php */ -/* Location: ./system/database/drivers/oci8/oci8_result.php */ diff --git a/src/system/database/drivers/oci8/oci8_utility.php b/src/system/database/drivers/oci8/oci8_utility.php index 06821c94..ebe49c46 100644 --- a/src/system/database/drivers/oci8/oci8_utility.php +++ b/src/system/database/drivers/oci8/oci8_utility.php @@ -1,88 +1,68 @@ -db->display_error('db_unsuported_feature'); + return $this->db->display_error('db_unsupported_feature'); } -} -/* End of file oci8_utility.php */ -/* Location: ./system/database/drivers/oci8/oci8_utility.php */ \ No newline at end of file +} diff --git a/src/system/database/drivers/odbc/index.html b/src/system/database/drivers/odbc/index.html index c942a79c..b702fbc3 100644 --- a/src/system/database/drivers/odbc/index.html +++ b/src/system/database/drivers/odbc/index.html @@ -1,3 +1,4 @@ + 403 Forbidden @@ -7,4 +8,4 @@

Directory access is forbidden.

- \ No newline at end of file + diff --git a/src/system/database/drivers/odbc/odbc_driver.php b/src/system/database/drivers/odbc/odbc_driver.php index 06131995..19b7b744 100644 --- a/src/system/database/drivers/odbc/odbc_driver.php +++ b/src/system/database/drivers/odbc/odbc_driver.php @@ -1,142 +1,128 @@ -_random_keyword = ' RND('.time().')'; // database specific random keyword + // Legacy support for DSN in the hostname field + if (empty($this->dsn)) + { + $this->dsn = $this->hostname; + } } + // -------------------------------------------------------------------- + /** * Non-persistent database connection * - * @access private called by the base class + * @param bool $persistent * @return resource */ - function db_connect() + public function db_connect($persistent = FALSE) { - 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"; + return ($persistent === TRUE) + ? odbc_pconnect($this->dsn, $this->username, $this->password) + : odbc_connect($this->dsn, $this->username, $this->password); } // -------------------------------------------------------------------- @@ -144,30 +130,12 @@ class CI_DB_odbc_driver extends CI_DB { /** * Execute the query * - * @access private called by the base class - * @param string an SQL query + * @param string $sql an SQL query * @return resource */ - function _execute($sql) + protected 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; + return odbc_exec($this->conn_id, $sql); } // -------------------------------------------------------------------- @@ -175,27 +143,10 @@ class CI_DB_odbc_driver extends CI_DB { /** * Begin Transaction * - * @access public * @return bool */ - function trans_begin($test_mode = FALSE) + protected function _trans_begin() { - 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); } @@ -204,25 +155,17 @@ class CI_DB_odbc_driver extends CI_DB { /** * Commit Transaction * - * @access public * @return bool */ - function trans_commit() + protected function _trans_commit() { - if ( ! $this->trans_enabled) + if (odbc_commit($this->conn_id)) { + odbc_autocommit($this->conn_id, TRUE); 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; + return FALSE; } // -------------------------------------------------------------------- @@ -230,61 +173,48 @@ class CI_DB_odbc_driver extends CI_DB { /** * Rollback Transaction * - * @access public * @return bool */ - function trans_rollback() + protected function _trans_rollback() { - if ( ! $this->trans_enabled) + if (odbc_rollback($this->conn_id)) { + odbc_autocommit($this->conn_id, TRUE); 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; + return FALSE; } // -------------------------------------------------------------------- /** - * Escape String + * Determines if a query is a "write" type. + * + * @param string An SQL query string + * @return bool + */ + public function is_write_type($sql) + { + if (preg_match('#^(INSERT|UPDATE).*RETURNING\s.+(\,\s?.+)*$#i', $sql)) + { + return FALSE; + } + + return parent::is_write_type($sql); + } + + // -------------------------------------------------------------------- + + /** + * Platform-dependant string escape * - * @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) + protected function _escape_str($str) { - 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; + return remove_invisible_characters($str); } // -------------------------------------------------------------------- @@ -292,12 +222,11 @@ class CI_DB_odbc_driver extends CI_DB { /** * Affected Rows * - * @access public - * @return integer + * @return int */ - function affected_rows() + public function affected_rows() { - return @odbc_num_rows($this->conn_id); + return odbc_num_rows($this->result_id); } // -------------------------------------------------------------------- @@ -305,43 +234,11 @@ class CI_DB_odbc_driver extends CI_DB { /** * Insert ID * - * @access public - * @return integer + * @return bool */ - function insert_id() + public 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; + return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE; } // -------------------------------------------------------------------- @@ -351,18 +248,17 @@ class CI_DB_odbc_driver extends CI_DB { * * Generates a platform-specific query string so that the table names can be fetched * - * @access private - * @param boolean + * @param bool $prefix_limit * @return string */ - function _list_tables($prefix_limit = FALSE) + protected function _list_tables($prefix_limit = FALSE) { - $sql = "SHOW TABLES FROM `".$this->database."`"; + $sql = "SELECT table_name FROM information_schema.tables WHERE table_schema = '".$this->schema."'"; - if ($prefix_limit !== FALSE AND $this->dbprefix != '') + if ($prefix_limit !== FALSE && $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." AND table_name LIKE '".$this->escape_like_str($this->dbprefix)."%' " + .sprintf($this->_like_escape_str, $this->_like_escape_chr); } return $sql; @@ -375,13 +271,12 @@ class CI_DB_odbc_driver extends CI_DB { * * Generates a platform-specific query string so that the column names can be fetched * - * @access public - * @param string the table name + * @param string $table * @return string */ - function _list_columns($table = '') + protected function _list_columns($table = '') { - return "SHOW COLUMNS FROM ".$table; + return 'SHOW COLUMNS FROM '.$table; } // -------------------------------------------------------------------- @@ -391,121 +286,27 @@ class CI_DB_odbc_driver extends CI_DB { * * 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 + * @param string $table * @return string */ - function _error_message() + protected function _field_data($table) { - return odbc_errormsg($this->conn_id); + return 'SELECT TOP 1 FROM '.$table; } // -------------------------------------------------------------------- /** - * The error message number + * Error * - * @access private - * @return integer + * Returns an array containing code and message of the last + * database error that has occured. + * + * @return array */ - function _error_number() + public function error() { - 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).")"; + return array('code' => odbc_error($this->conn_id), 'message' => odbc_errormsg($this->conn_id)); } // -------------------------------------------------------------------- @@ -515,51 +316,33 @@ class CI_DB_odbc_driver extends CI_DB { * * 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 + * @param string $table + * @param array $values * @return string */ - function _update($table, $values, $where, $orderby = array(), $limit = FALSE) + protected function _update($table, $values) { - 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; + $this->qb_limit = FALSE; + $this->qb_orderby = array(); + return parent::_update($table, $values); } - // -------------------------------------------------------------------- /** * Truncate statement * * Generates a platform-specific truncate string from the supplied data - * If the database does not support the truncate() command - * This function maps to "DELETE FROM table" * - * @access public - * @param string the table name + * If the database does not support the TRUNCATE statement, + * then this method maps to 'DELETE FROM table' + * + * @param string $table * @return string */ - function _truncate($table) + protected function _truncate($table) { - return $this->_delete($table); + return 'DELETE FROM '.$table; } // -------------------------------------------------------------------- @@ -569,50 +352,13 @@ class CI_DB_odbc_driver extends CI_DB { * * 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 + * @param string $table * @return string */ - function _delete($table, $where = array(), $like = array(), $limit = FALSE) + protected function _delete($table) { - $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; + $this->qb_limit = FALSE; + return parent::_delete($table); } // -------------------------------------------------------------------- @@ -620,19 +366,11 @@ class CI_DB_odbc_driver extends CI_DB { /** * Close DB Connection * - * @access public - * @param resource * @return void */ - function _close($conn_id) + protected function _close() { - @odbc_close($conn_id); + odbc_close($this->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/src/system/database/drivers/odbc/odbc_forge.php b/src/system/database/drivers/odbc/odbc_forge.php index ed8d9d79..bac30bed 100644 --- a/src/system/database/drivers/odbc/odbc_forge.php +++ b/src/system/database/drivers/odbc/odbc_forge.php @@ -1,267 +1,86 @@ -db->db_debug) - { - return $this->db->display_error('db_unsuported_feature'); - } - return FALSE; - } + protected $_create_table_if = FALSE; + + /** + * DROP TABLE IF statement + * + * @var string + */ + protected $_drop_table_if = FALSE; + + /** + * UNSIGNED support + * + * @var bool|array + */ + protected $_unsigned = FALSE; // -------------------------------------------------------------------- /** - * Drop database + * Field attribute AUTO_INCREMENT * - * @access private - * @param string the database name - * @return bool + * @param array &$attributes + * @param array &$field + * @return void */ - function _drop_database($name) + protected function _attr_auto_increment(&$attributes, &$field) { - // 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; + // Not supported (in most databases at least) } - // -------------------------------------------------------------------- - - /** - * 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/src/system/database/drivers/odbc/odbc_result.php b/src/system/database/drivers/odbc/odbc_result.php index 2f70e0a5..110d6ab0 100644 --- a/src/system/database/drivers/odbc/odbc_result.php +++ b/src/system/database/drivers/odbc/odbc_result.php @@ -1,41 +1,82 @@ -result_id); + if (is_int($this->num_rows)) + { + return $this->num_rows; + } + elseif (($this->num_rows = odbc_num_rows($this->result_id)) !== -1) + { + return $this->num_rows; + } + + // Work-around for ODBC subdrivers that don't support num_rows() + if (count($this->result_array) > 0) + { + return $this->num_rows = count($this->result_array); + } + elseif (count($this->result_object) > 0) + { + return $this->num_rows = count($this->result_object); + } + + return $this->num_rows = count($this->result_array()); } // -------------------------------------------------------------------- @@ -43,12 +84,11 @@ class CI_DB_odbc_result extends CI_DB_result { /** * Number of fields in the result set * - * @access public - * @return integer + * @return int */ - function num_fields() + public function num_fields() { - return @odbc_num_fields($this->result_id); + return odbc_num_fields($this->result_id); } // -------------------------------------------------------------------- @@ -58,15 +98,19 @@ class CI_DB_odbc_result extends CI_DB_result { * * Generates an array of column names * - * @access public * @return array */ - function list_fields() + public function list_fields() { $field_names = array(); - for ($i = 0; $i < $this->num_fields(); $i++) + $num_fields = $this->num_fields(); + + if ($num_fields > 0) { - $field_names[] = odbc_field_name($this->result_id, $i); + for ($i = 1; $i <= $num_fields; $i++) + { + $field_names[] = odbc_field_name($this->result_id, $i); + } } return $field_names; @@ -79,22 +123,19 @@ class CI_DB_odbc_result extends CI_DB_result { * * Generates an array of objects containing field meta-data * - * @access public * @return array */ - function field_data() + public function field_data() { $retval = array(); - for ($i = 0; $i < $this->num_fields(); $i++) + for ($i = 0, $odbc_index = 1, $c = $this->num_fields(); $i < $c; $i++, $odbc_index++) { - $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; + $retval[$i] = new stdClass(); + $retval[$i]->name = odbc_field_name($this->result_id, $odbc_index); + $retval[$i]->type = odbc_field_type($this->result_id, $odbc_index); + $retval[$i]->max_length = odbc_field_len($this->result_id, $odbc_index); + $retval[$i]->primary_key = 0; + $retval[$i]->default = ''; } return $retval; @@ -105,9 +146,9 @@ class CI_DB_odbc_result extends CI_DB_result { /** * Free the result * - * @return null + * @return void */ - function free_result() + public function free_result() { if (is_resource($this->result_id)) { @@ -118,41 +159,16 @@ class CI_DB_odbc_result extends CI_DB_result { // -------------------------------------------------------------------- - /** - * 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() + protected 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); - } + return odbc_fetch_array($this->result_id); } // -------------------------------------------------------------------- @@ -162,68 +178,91 @@ class CI_DB_odbc_result extends CI_DB_result { * * Returns the result set as an object * - * @access private + * @param string $class_name * @return object */ - function _fetch_object() + protected function _fetch_object($class_name = 'stdClass') { - if (function_exists('odbc_fetch_object')) + $row = odbc_fetch_object($this->result_id); + + if ($class_name === 'stdClass' OR ! $row) { - return odbc_fetch_object($this->result_id); + return $row; } - else + + $class_name = new $class_name(); + foreach ($row as $key => $value) { - return $this->_odbc_fetch_object($this->result_id); + $class_name->$key = $value; } - } - - /** - * 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; + return $class_name; } } +// -------------------------------------------------------------------- -/* End of file odbc_result.php */ -/* Location: ./system/database/drivers/odbc/odbc_result.php */ \ No newline at end of file +if ( ! function_exists('odbc_fetch_array')) +{ + /** + * ODBC Fetch array + * + * Emulates the native odbc_fetch_array() function when + * it is not available (odbc_fetch_array() requires unixODBC) + * + * @param resource &$result + * @param int $rownumber + * @return array + */ + function odbc_fetch_array(&$result, $rownumber = 1) + { + $rs = array(); + if ( ! odbc_fetch_into($result, $rs, $rownumber)) + { + return FALSE; + } + + $rs_assoc = array(); + foreach ($rs as $k => $v) + { + $field_name = odbc_field_name($result, $k+1); + $rs_assoc[$field_name] = $v; + } + + return $rs_assoc; + } +} + +// -------------------------------------------------------------------- + +if ( ! function_exists('odbc_fetch_object')) +{ + /** + * ODBC Fetch object + * + * Emulates the native odbc_fetch_object() function when + * it is not available. + * + * @param resource &$result + * @param int $rownumber + * @return object + */ + function odbc_fetch_object(&$result, $rownumber = 1) + { + $rs = array(); + if ( ! odbc_fetch_into($result, $rs, $rownumber)) + { + return FALSE; + } + + $rs_object = new stdClass(); + foreach ($rs as $k => $v) + { + $field_name = odbc_field_name($result, $k+1); + $rs_object->$field_name = $v; + } + + return $rs_object; + } +} diff --git a/src/system/database/drivers/odbc/odbc_utility.php b/src/system/database/drivers/odbc/odbc_utility.php index ae099c12..2e344963 100644 --- a/src/system/database/drivers/odbc/odbc_utility.php +++ b/src/system/database/drivers/odbc/odbc_utility.php @@ -1,104 +1,63 @@ -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 + * @param array $params Preferences * @return mixed */ - function _backup($params = array()) + protected function _backup($params = array()) { // Currently unsupported - return $this->db->display_error('db_unsuported_feature'); + return $this->db->display_error('db_unsupported_feature'); } } - -/* End of file odbc_utility.php */ -/* Location: ./system/database/drivers/odbc/odbc_utility.php */ \ No newline at end of file diff --git a/src/system/database/drivers/pdo/index.html b/src/system/database/drivers/pdo/index.html index c942a79c..b702fbc3 100644 --- a/src/system/database/drivers/pdo/index.html +++ b/src/system/database/drivers/pdo/index.html @@ -1,3 +1,4 @@ + 403 Forbidden @@ -7,4 +8,4 @@

Directory access is forbidden.

- \ No newline at end of file + diff --git a/src/system/database/drivers/pdo/pdo_driver.php b/src/system/database/drivers/pdo/pdo_driver.php index 3c2d08b9..c6f84e0f 100644 --- a/src/system/database/drivers/pdo/pdo_driver.php +++ b/src/system/database/drivers/pdo/pdo_driver.php @@ -1,181 +1,171 @@ -hostname, 'mysql') !== FALSE) + if (preg_match('/([^:]+):/', $this->dsn, $match) && count($match) === 2) { - $this->_like_escape_str = ''; - $this->_like_escape_chr = ''; + // If there is a minimum valid dsn string pattern found, we're done + // This is for general PDO users, who tend to have a full DSN string. + $this->subdriver = $match[1]; + return; + } + // Legacy support for DSN specified in the hostname field + elseif (preg_match('/([^:]+):/', $this->hostname, $match) && count($match) === 2) + { + $this->dsn = $this->hostname; + $this->hostname = NULL; + $this->subdriver = $match[1]; + return; + } + elseif (in_array($this->subdriver, array('mssql', 'sybase'), TRUE)) + { + $this->subdriver = 'dblib'; + } + elseif ($this->subdriver === '4D') + { + $this->subdriver = '4d'; + } + elseif ( ! in_array($this->subdriver, array('4d', 'cubrid', 'dblib', 'firebird', 'ibm', 'informix', 'mysql', 'oci', 'odbc', 'pgsql', 'sqlite', 'sqlsrv'), TRUE)) + { + log_message('error', 'PDO: Invalid or non-existent subdriver'); - //Prior to this version, the charset can't be set in the dsn - if(is_php('5.3.6')) + if ($this->db_debug) { - $this->hostname .= ";charset={$this->char_set}"; + show_error('Invalid or non-existent PDO subdriver'); + } + } + + $this->dsn = NULL; + } + + // -------------------------------------------------------------------- + + /** + * Database connection + * + * @param bool $persistent + * @return object + */ + public function db_connect($persistent = FALSE) + { + $this->options[PDO::ATTR_PERSISTENT] = $persistent; + + try + { + return new PDO($this->dsn, $this->username, $this->password, $this->options); + } + catch (PDOException $e) + { + if ($this->db_debug && empty($this->failover)) + { + $this->display_error($e->getMessage(), '', TRUE); } - //Set the charset with the connection options - $this->options['PDO::MYSQL_ATTR_INIT_COMMAND'] = "SET NAMES {$this->char_set}"; + return FALSE; } - 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 + * Database version number * - * @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() + public function version() { - return $this->conn_id->getAttribute(PDO::ATTR_CLIENT_VERSION); + if (isset($this->data_cache['version'])) + { + return $this->data_cache['version']; + } + + // Not all subdrivers support the getAttribute() method + try + { + return $this->data_cache['version'] = $this->conn_id->getAttribute(PDO::ATTR_SERVER_VERSION); + } + catch (PDOException $e) + { + return parent::version(); + } } // -------------------------------------------------------------------- @@ -183,49 +173,12 @@ class CI_DB_pdo_driver extends CI_DB { /** * Execute the query * - * @access private called by the base class - * @param string an SQL query - * @return object + * @param string $sql SQL query + * @return mixed */ - function _execute($sql) + protected function _execute($sql) { - $sql = $this->_prep_query($sql); - $result_id = $this->conn_id->prepare($sql); - - if (is_object($result_id) && $result_id->execute()) - { - if (is_numeric(stripos($sql, 'SELECT'))) - { - $this->affect_rows = count($result_id->fetchAll()); - } - else - { - $this->affect_rows = $result_id->rowCount(); - } - } - else - { - $this->affect_rows = 0; - return FALSE; - } - - 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; + return $this->conn_id->query($sql); } // -------------------------------------------------------------------- @@ -233,27 +186,10 @@ class CI_DB_pdo_driver extends CI_DB { /** * Begin Transaction * - * @access public * @return bool */ - function trans_begin($test_mode = FALSE) + protected function _trans_begin() { - 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(); } @@ -262,24 +198,11 @@ class CI_DB_pdo_driver extends CI_DB { /** * Commit Transaction * - * @access public * @return bool */ - function trans_commit() + protected 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; + return $this->conn_id->commit(); } // -------------------------------------------------------------------- @@ -287,66 +210,30 @@ class CI_DB_pdo_driver extends CI_DB { /** * Rollback Transaction * - * @access public * @return bool */ - function trans_rollback() + protected 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; + return $this->conn_id->rollBack(); } // -------------------------------------------------------------------- /** - * Escape String + * Platform-dependant string escape * - * @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) + protected function _escape_str($str) { - if (is_array($str)) - { - foreach ($str as $key => $val) - { - $str[$key] = $this->escape_str($val, $like); - } - - return $str; - } - - //Escape the string + // 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; + // If there are duplicated quotes, trim them away + return ($str[0] === "'") + ? substr($str, 1, -1) + : $str; } // -------------------------------------------------------------------- @@ -354,114 +241,24 @@ class CI_DB_pdo_driver extends CI_DB { /** * Affected Rows * - * @access public - * @return integer + * @return int */ - function affected_rows() + public function affected_rows() { - return $this->affect_rows; + return is_object($this->result_id) ? $this->result_id->rowCount() : 0; } // -------------------------------------------------------------------- /** * Insert ID - * - * @access public - * @return integer + * + * @param string $name + * @return int */ - function insert_id($name=NULL) + public 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; + return $this->conn_id->lastInsertId($name); } // -------------------------------------------------------------------- @@ -471,215 +268,76 @@ class CI_DB_pdo_driver extends CI_DB { * * 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 + * @param string $table * @return string */ - function _error_message() + protected function _field_data($table) { - $error_array = $this->conn_id->errorInfo(); - return $error_array[2]; + return 'SELECT TOP 1 * FROM '.$this->protect_identifiers($table); } // -------------------------------------------------------------------- /** - * The error message number + * Error * - * @access private - * @return integer + * Returns an array containing code and message of the last + * database error that has occured. + * + * @return array */ - function _error_number() + public function error() { - return $this->conn_id->errorCode(); - } + $error = array('code' => '00000', 'message' => ''); + $pdo_error = $this->conn_id->errorInfo(); - // -------------------------------------------------------------------- - - /** - * 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 == '') + if (empty($pdo_error[0])) { - return $item; + return $error; } - foreach ($this->_reserved_identifiers as $id) + $error['code'] = isset($pdo_error[1]) ? $pdo_error[0].'/'.$pdo_error[1] : $pdo_error[0]; + if (isset($pdo_error[2])) { - 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); - } + $error['message'] = $pdo_error[2]; } - 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); + return $error; } // -------------------------------------------------------------------- - /** - * 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 + * @param string $table Table name + * @param array $values Update data + * @param string $index WHERE key * @return string */ - function _update_batch($table, $values, $index, $where = NULL) + protected function _update_batch($table, $values, $index) { $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) + if ($field !== $index) { - $final[$field][] = 'WHEN '.$index.' = '.$val[$index].' THEN '.$val[$field]; + $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"; @@ -688,125 +346,27 @@ class CI_DB_pdo_driver extends CI_DB { $cases .= 'ELSE '.$k.' END, '; } - $sql .= substr($cases, 0, -2); + $this->where($index.' IN('.implode(',', $ids).')', NULL, FALSE); - $sql .= ' WHERE '.$where.$index.' IN ('.implode(',', $ids).')'; - - return $sql; + return 'UPDATE '.$table.' SET '.substr($cases, 0, -2).$this->_compile_wh('qb_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 + * If the database does not support the TRUNCATE statement, + * then this method maps to 'DELETE FROM table' + * + * @param string $table * @return string */ - function _truncate($table) + protected function _truncate($table) { - return $this->_delete($table); + return 'TRUNCATE TABLE '.$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/src/system/database/drivers/pdo/pdo_forge.php b/src/system/database/drivers/pdo/pdo_forge.php index 86e82932..2595f7b6 100644 --- a/src/system/database/drivers/pdo/pdo_forge.php +++ b/src/system/database/drivers/pdo/pdo_forge.php @@ -1,267 +1,65 @@ -db->db_debug) - { - return $this->db->display_error('db_unsuported_feature'); - } - return FALSE; - } - - // -------------------------------------------------------------------- + protected $_create_table_if = FALSE; /** - * Drop database + * DROP TABLE IF statement * - * @access private - * @param string the database name - * @return bool + * @var string */ - 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; - } - + protected $_drop_table_if = FALSE; } - -/* End of file pdo_forge.php */ -/* Location: ./system/database/drivers/pdo/pdo_forge.php */ \ No newline at end of file diff --git a/src/system/database/drivers/pdo/pdo_result.php b/src/system/database/drivers/pdo/pdo_result.php index 705a66cd..d1809bef 100644 --- a/src/system/database/drivers/pdo/pdo_result.php +++ b/src/system/database/drivers/pdo/pdo_result.php @@ -1,34 +1,55 @@ -num_rows; } - elseif (($this->num_rows = $this->result_id->rowCount()) > 0) + elseif (count($this->result_array) > 0) { - return $this->num_rows; + return $this->num_rows = count($this->result_array); + } + elseif (count($this->result_object) > 0) + { + return $this->num_rows = count($this->result_object); + } + elseif (($num_rows = $this->result_id->rowCount()) > 0) + { + return $this->num_rows = $num_rows; } - $this->num_rows = count($this->result_id->fetchAll()); - $this->result_id->execute(); - return $this->num_rows; + return $this->num_rows = count($this->result_array()); } // -------------------------------------------------------------------- @@ -55,10 +82,9 @@ class CI_DB_pdo_result extends CI_DB_result { /** * Number of fields in the result set * - * @access public - * @return integer + * @return int */ - function num_fields() + public function num_fields() { return $this->result_id->columnCount(); } @@ -70,16 +96,20 @@ class CI_DB_pdo_result extends CI_DB_result { * * Generates an array of column names * - * @access public - * @return array + * @return bool */ - function list_fields() + public function list_fields() { - if ($this->db->db_debug) + $field_names = array(); + for ($i = 0, $c = $this->num_fields(); $i < $c; $i++) { - return $this->db->display_error('db_unsuported_feature'); + // Might trigger an E_WARNING due to not all subdrivers + // supporting getColumnMeta() + $field_names[$i] = @$this->result_id->getColumnMeta($i); + $field_names[$i] = $field_names[$i]['name']; } - return FALSE; + + return $field_names; } // -------------------------------------------------------------------- @@ -89,28 +119,34 @@ class CI_DB_pdo_result extends CI_DB_result { * * Generates an array of objects containing field meta-data * - * @access public * @return array */ - function field_data() + public function field_data() { - $data = array(); - try { - for($i = 0; $i < $this->num_fields(); $i++) + $retval = array(); + + for ($i = 0, $c = $this->num_fields(); $i < $c; $i++) { - $data[] = $this->result_id->getColumnMeta($i); + $field = $this->result_id->getColumnMeta($i); + + $retval[$i] = new stdClass(); + $retval[$i]->name = $field['name']; + $retval[$i]->type = $field['native_type']; + $retval[$i]->max_length = ($field['len'] > 0) ? $field['len'] : NULL; + $retval[$i]->primary_key = (int) ( ! empty($field['flags']) && in_array('primary_key', $field['flags'], TRUE)); } - - return $data; + + return $retval; } catch (Exception $e) { if ($this->db->db_debug) { - return $this->db->display_error('db_unsuported_feature'); + return $this->db->display_error('db_unsupported_feature'); } + return FALSE; } } @@ -120,9 +156,9 @@ class CI_DB_pdo_result extends CI_DB_result { /** * Free the result * - * @return null + * @return void */ - function free_result() + public function free_result() { if (is_object($this->result_id)) { @@ -132,32 +168,14 @@ class CI_DB_pdo_result extends CI_DB_result { // -------------------------------------------------------------------- - /** - * 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() + protected function _fetch_assoc() { return $this->result_id->fetch(PDO::FETCH_ASSOC); } @@ -169,16 +187,12 @@ class CI_DB_pdo_result extends CI_DB_result { * * Returns the result set as an object * - * @access private + * @param string $class_name * @return object */ - function _fetch_object() - { - return $this->result_id->fetchObject(); + protected function _fetch_object($class_name = 'stdClass') + { + return $this->result_id->fetchObject($class_name); } } - - -/* End of file pdo_result.php */ -/* Location: ./system/database/drivers/pdo/pdo_result.php */ \ No newline at end of file diff --git a/src/system/database/drivers/pdo/pdo_utility.php b/src/system/database/drivers/pdo/pdo_utility.php index da70c342..384661bf 100644 --- a/src/system/database/drivers/pdo/pdo_utility.php +++ b/src/system/database/drivers/pdo/pdo_utility.php @@ -1,104 +1,63 @@ -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 + * @param array $params Preferences * @return mixed */ - function _backup($params = array()) + protected function _backup($params = array()) { // Currently unsupported - return $this->db->display_error('db_unsuported_feature'); + return $this->db->display_error('db_unsupported_feature'); } } - -/* End of file pdo_utility.php */ -/* Location: ./system/database/drivers/pdo/pdo_utility.php */ \ No newline at end of file diff --git a/src/system/database/drivers/pdo/subdrivers/index.html b/src/system/database/drivers/pdo/subdrivers/index.html new file mode 100644 index 00000000..b702fbc3 --- /dev/null +++ b/src/system/database/drivers/pdo/subdrivers/index.html @@ -0,0 +1,11 @@ + + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + diff --git a/src/system/database/drivers/pdo/subdrivers/pdo_4d_driver.php b/src/system/database/drivers/pdo/subdrivers/pdo_4d_driver.php new file mode 100644 index 00000000..3dedfd9b --- /dev/null +++ b/src/system/database/drivers/pdo/subdrivers/pdo_4d_driver.php @@ -0,0 +1,200 @@ +dsn)) + { + $this->dsn = '4D:host='.(empty($this->hostname) ? '127.0.0.1' : $this->hostname); + + empty($this->port) OR $this->dsn .= ';port='.$this->port; + empty($this->database) OR $this->dsn .= ';dbname='.$this->database; + empty($this->char_set) OR $this->dsn .= ';charset='.$this->char_set; + } + elseif ( ! empty($this->char_set) && strpos($this->dsn, 'charset=', 3) === FALSE) + { + $this->dsn .= ';charset='.$this->char_set; + } + } + + // -------------------------------------------------------------------- + + /** + * Show table query + * + * Generates a platform-specific query string so that the table names can be fetched + * + * @param bool $prefix_limit + * @return string + */ + protected function _list_tables($prefix_limit = FALSE) + { + $sql = 'SELECT '.$this->escape_identifiers('TABLE_NAME').' FROM '.$this->escape_identifiers('_USER_TABLES'); + + if ($prefix_limit === TRUE && $this->dbprefix !== '') + { + $sql .= ' WHERE '.$this->escape_identifiers('TABLE_NAME')." LIKE '".$this->escape_like_str($this->dbprefix)."%' " + .sprintf($this->_like_escape_str, $this->_like_escape_chr); + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Show column query + * + * Generates a platform-specific query string so that the column names can be fetched + * + * @param string $table + * @return string + */ + protected function _list_columns($table = '') + { + return 'SELECT '.$this->escape_identifiers('COLUMN_NAME').' FROM '.$this->escape_identifiers('_USER_COLUMNS') + .' WHERE '.$this->escape_identifiers('TABLE_NAME').' = '.$this->escape($table); + } + + // -------------------------------------------------------------------- + + /** + * Field data query + * + * Generates a platform-specific query so that the column data can be retrieved + * + * @param string $table + * @return string + */ + protected function _field_data($table) + { + return 'SELECT * FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE).' LIMIT 1'; + } + + // -------------------------------------------------------------------- + + /** + * Update statement + * + * Generates a platform-specific update string from the supplied data + * + * @param string $table + * @param array $values + * @return string + */ + protected function _update($table, $values) + { + $this->qb_limit = FALSE; + $this->qb_orderby = array(); + return parent::_update($table, $values); + } + + // -------------------------------------------------------------------- + + /** + * Delete statement + * + * Generates a platform-specific delete string from the supplied data + * + * @param string $table + * @return string + */ + protected function _delete($table) + { + $this->qb_limit = FALSE; + return parent::_delete($table); + } + + // -------------------------------------------------------------------- + + /** + * LIMIT + * + * Generates a platform-specific LIMIT clause + * + * @param string $sql SQL Query + * @return string + */ + protected function _limit($sql) + { + return $sql.' LIMIT '.$this->qb_limit.($this->qb_offset ? ' OFFSET '.$this->qb_offset : ''); + } + +} diff --git a/src/system/database/drivers/pdo/subdrivers/pdo_4d_forge.php b/src/system/database/drivers/pdo/subdrivers/pdo_4d_forge.php new file mode 100644 index 00000000..41994f9d --- /dev/null +++ b/src/system/database/drivers/pdo/subdrivers/pdo_4d_forge.php @@ -0,0 +1,217 @@ + 'INT', + 'SMALLINT' => 'INT', + 'INT' => 'INT64', + 'INT32' => 'INT64' + ); + + /** + * DEFAULT value representation in CREATE/ALTER TABLE statements + * + * @var string + */ + protected $_default = FALSE; + + // -------------------------------------------------------------------- + + /** + * ALTER TABLE + * + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * @return string|string[] + */ + protected function _alter_table($alter_type, $table, $field) + { + if (in_array($alter_type, array('ADD', 'DROP'), TRUE)) + { + return parent::_alter_table($alter_type, $table, $field); + } + + // No method of modifying columns is supported + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Process column + * + * @param array $field + * @return string + */ + protected function _process_column($field) + { + return $this->db->escape_identifiers($field['name']) + .' '.$field['type'].$field['length'] + .$field['null'] + .$field['unique'] + .$field['auto_increment']; + } + + // -------------------------------------------------------------------- + + /** + * Field attribute TYPE + * + * Performs a data type mapping between different databases. + * + * @param array &$attributes + * @return void + */ + protected function _attr_type(&$attributes) + { + switch (strtoupper($attributes['TYPE'])) + { + case 'TINYINT': + $attributes['TYPE'] = 'SMALLINT'; + $attributes['UNSIGNED'] = FALSE; + return; + case 'MEDIUMINT': + $attributes['TYPE'] = 'INTEGER'; + $attributes['UNSIGNED'] = FALSE; + return; + case 'INTEGER': + $attributes['TYPE'] = 'INT'; + return; + case 'BIGINT': + $attributes['TYPE'] = 'INT64'; + return; + default: return; + } + } + + // -------------------------------------------------------------------- + + /** + * Field attribute UNIQUE + * + * @param array &$attributes + * @param array &$field + * @return void + */ + protected function _attr_unique(&$attributes, &$field) + { + if ( ! empty($attributes['UNIQUE']) && $attributes['UNIQUE'] === TRUE) + { + $field['unique'] = ' UNIQUE'; + + // UNIQUE must be used with NOT NULL + $field['null'] = ' NOT NULL'; + } + } + + // -------------------------------------------------------------------- + + /** + * Field attribute AUTO_INCREMENT + * + * @param array &$attributes + * @param array &$field + * @return void + */ + protected function _attr_auto_increment(&$attributes, &$field) + { + if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE) + { + if (stripos($field['type'], 'int') !== FALSE) + { + $field['auto_increment'] = ' AUTO_INCREMENT'; + } + elseif (strcasecmp($field['type'], 'UUID') === 0) + { + $field['auto_increment'] = ' AUTO_GENERATE'; + } + } + } + +} diff --git a/src/system/database/drivers/pdo/subdrivers/pdo_cubrid_driver.php b/src/system/database/drivers/pdo/subdrivers/pdo_cubrid_driver.php new file mode 100644 index 00000000..83777980 --- /dev/null +++ b/src/system/database/drivers/pdo/subdrivers/pdo_cubrid_driver.php @@ -0,0 +1,250 @@ +dsn)) + { + $this->dsn = 'cubrid:host='.(empty($this->hostname) ? '127.0.0.1' : $this->hostname); + + empty($this->port) OR $this->dsn .= ';port='.$this->port; + empty($this->database) OR $this->dsn .= ';dbname='.$this->database; + empty($this->char_set) OR $this->dsn .= ';charset='.$this->char_set; + } + } + + // -------------------------------------------------------------------- + + /** + * Show table query + * + * Generates a platform-specific query string so that the table names can be fetched + * + * @param bool $prefix_limit + * @return string + */ + protected function _list_tables($prefix_limit = FALSE) + { + $sql = 'SHOW TABLES'; + + if ($prefix_limit === TRUE && $this->dbprefix !== '') + { + return $sql." LIKE '".$this->escape_like_str($this->dbprefix)."%'"; + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Show column query + * + * Generates a platform-specific query string so that the column names can be fetched + * + * @param string $table + * @return string + */ + protected function _list_columns($table = '') + { + return 'SHOW COLUMNS FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE); + } + + // -------------------------------------------------------------------- + + /** + * Returns an object with field data + * + * @param string $table + * @return array + */ + public function field_data($table) + { + if (($query = $this->query('SHOW COLUMNS FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE))) === FALSE) + { + return FALSE; + } + $query = $query->result_object(); + + $retval = array(); + for ($i = 0, $c = count($query); $i < $c; $i++) + { + $retval[$i] = new stdClass(); + $retval[$i]->name = $query[$i]->Field; + + sscanf($query[$i]->Type, '%[a-z](%d)', + $retval[$i]->type, + $retval[$i]->max_length + ); + + $retval[$i]->default = $query[$i]->Default; + $retval[$i]->primary_key = (int) ($query[$i]->Key === 'PRI'); + } + + return $retval; + } + + // -------------------------------------------------------------------- + + /** + * Update_Batch statement + * + * Generates a platform-specific batch update string from the supplied data + * + * @param string $table Table name + * @param array $values Update data + * @param string $index WHERE key + * @return string + */ + protected function _update_batch($table, $values, $index) + { + $ids = array(); + foreach ($values as $key => $val) + { + $ids[] = $val[$index]; + + foreach (array_keys($val) as $field) + { + if ($field !== $index) + { + $final[$field][] = 'WHEN '.$index.' = '.$val[$index].' THEN '.$val[$field]; + } + } + } + + $cases = ''; + foreach ($final as $k => $v) + { + $cases .= $k." = CASE \n" + .implode("\n", $v)."\n" + .'ELSE '.$k.' END), '; + } + + $this->where($index.' IN('.implode(',', $ids).')', NULL, FALSE); + + return 'UPDATE '.$table.' SET '.substr($cases, 0, -2).$this->_compile_wh('qb_where'); + } + + // -------------------------------------------------------------------- + + /** + * Truncate statement + * + * Generates a platform-specific truncate string from the supplied data + * + * If the database does not support the TRUNCATE statement, + * then this method maps to 'DELETE FROM table' + * + * @param string $table + * @return string + */ + protected function _truncate($table) + { + return 'TRUNCATE '.$table; + } + + // -------------------------------------------------------------------- + + /** + * FROM tables + * + * Groups tables in FROM clauses if needed, so there is no confusion + * about operator precedence. + * + * @return string + */ + protected function _from_tables() + { + if ( ! empty($this->qb_join) && count($this->qb_from) > 1) + { + return '('.implode(', ', $this->qb_from).')'; + } + + return implode(', ', $this->qb_from); + } + +} diff --git a/src/system/database/drivers/pdo/subdrivers/pdo_cubrid_forge.php b/src/system/database/drivers/pdo/subdrivers/pdo_cubrid_forge.php new file mode 100644 index 00000000..5f854061 --- /dev/null +++ b/src/system/database/drivers/pdo/subdrivers/pdo_cubrid_forge.php @@ -0,0 +1,227 @@ + 'INTEGER', + 'SMALLINT' => 'INTEGER', + 'INT' => 'BIGINT', + 'INTEGER' => 'BIGINT', + 'BIGINT' => 'NUMERIC', + 'FLOAT' => 'DOUBLE', + 'REAL' => 'DOUBLE' + ); + + // -------------------------------------------------------------------- + + /** + * ALTER TABLE + * + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * @return string|string[] + */ + protected function _alter_table($alter_type, $table, $field) + { + if (in_array($alter_type, array('DROP', 'ADD'), TRUE)) + { + return parent::_alter_table($alter_type, $table, $field); + } + + $sql = 'ALTER TABLE '.$this->db->escape_identifiers($table); + $sqls = array(); + for ($i = 0, $c = count($field); $i < $c; $i++) + { + if ($field[$i]['_literal'] !== FALSE) + { + $sqls[] = $sql.' CHANGE '.$field[$i]['_literal']; + } + else + { + $alter_type = empty($field[$i]['new_name']) ? ' MODIFY ' : ' CHANGE '; + $sqls[] = $sql.$alter_type.$this->_process_column($field[$i]); + } + } + + return $sqls; + } + + // -------------------------------------------------------------------- + + /** + * Process column + * + * @param array $field + * @return string + */ + protected function _process_column($field) + { + $extra_clause = isset($field['after']) + ? ' AFTER '.$this->db->escape_identifiers($field['after']) : ''; + + if (empty($extra_clause) && isset($field['first']) && $field['first'] === TRUE) + { + $extra_clause = ' FIRST'; + } + + return $this->db->escape_identifiers($field['name']) + .(empty($field['new_name']) ? '' : ' '.$this->db->escape_identifiers($field['new_name'])) + .' '.$field['type'].$field['length'] + .$field['unsigned'] + .$field['null'] + .$field['default'] + .$field['auto_increment'] + .$field['unique'] + .$extra_clause; + } + + // -------------------------------------------------------------------- + + /** + * Field attribute TYPE + * + * Performs a data type mapping between different databases. + * + * @param array &$attributes + * @return void + */ + protected function _attr_type(&$attributes) + { + switch (strtoupper($attributes['TYPE'])) + { + case 'TINYINT': + $attributes['TYPE'] = 'SMALLINT'; + $attributes['UNSIGNED'] = FALSE; + return; + case 'MEDIUMINT': + $attributes['TYPE'] = 'INTEGER'; + $attributes['UNSIGNED'] = FALSE; + return; + default: return; + } + } + + // -------------------------------------------------------------------- + + /** + * Process indexes + * + * @param string $table (ignored) + * @return string + */ + protected function _process_indexes($table) + { + $sql = ''; + + for ($i = 0, $c = count($this->keys); $i < $c; $i++) + { + if (is_array($this->keys[$i])) + { + for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++) + { + if ( ! isset($this->fields[$this->keys[$i][$i2]])) + { + unset($this->keys[$i][$i2]); + continue; + } + } + } + elseif ( ! isset($this->fields[$this->keys[$i]])) + { + unset($this->keys[$i]); + continue; + } + + is_array($this->keys[$i]) OR $this->keys[$i] = array($this->keys[$i]); + + $sql .= ",\n\tKEY ".$this->db->escape_identifiers(implode('_', $this->keys[$i])) + .' ('.implode(', ', $this->db->escape_identifiers($this->keys[$i])).')'; + } + + $this->keys = array(); + + return $sql; + } + +} diff --git a/src/system/database/drivers/pdo/subdrivers/pdo_dblib_driver.php b/src/system/database/drivers/pdo/subdrivers/pdo_dblib_driver.php new file mode 100644 index 00000000..84695ee9 --- /dev/null +++ b/src/system/database/drivers/pdo/subdrivers/pdo_dblib_driver.php @@ -0,0 +1,332 @@ +dsn)) + { + $this->dsn = $params['subdriver'].':host='.(empty($this->hostname) ? '127.0.0.1' : $this->hostname); + + if ( ! empty($this->port)) + { + $this->dsn .= (DIRECTORY_SEPARATOR === '\\' ? ',' : ':').$this->port; + } + + empty($this->database) OR $this->dsn .= ';dbname='.$this->database; + empty($this->char_set) OR $this->dsn .= ';charset='.$this->char_set; + empty($this->appname) OR $this->dsn .= ';appname='.$this->appname; + } + else + { + if ( ! empty($this->char_set) && strpos($this->dsn, 'charset=', 6) === FALSE) + { + $this->dsn .= ';charset='.$this->char_set; + } + + $this->subdriver = 'dblib'; + } + } + + // -------------------------------------------------------------------- + + /** + * Database connection + * + * @param bool $persistent + * @return object + */ + public function db_connect($persistent = FALSE) + { + $this->conn_id = parent::db_connect($persistent); + + if ( ! is_object($this->conn_id)) + { + return $this->conn_id; + } + + // Determine how identifiers are escaped + $query = $this->query('SELECT CASE WHEN (@@OPTIONS | 256) = @@OPTIONS THEN 1 ELSE 0 END AS qi'); + $query = $query->row_array(); + $this->_quoted_identifier = empty($query) ? FALSE : (bool) $query['qi']; + $this->_escape_char = ($this->_quoted_identifier) ? '"' : array('[', ']'); + + return $this->conn_id; + } + + // -------------------------------------------------------------------- + + /** + * Show table query + * + * Generates a platform-specific query string so that the table names can be fetched + * + * @param bool $prefix_limit + * @return string + */ + protected function _list_tables($prefix_limit = FALSE) + { + $sql = 'SELECT '.$this->escape_identifiers('name') + .' FROM '.$this->escape_identifiers('sysobjects') + .' WHERE '.$this->escape_identifiers('type')." = 'U'"; + + if ($prefix_limit === TRUE && $this->dbprefix !== '') + { + $sql .= ' AND '.$this->escape_identifiers('name')." LIKE '".$this->escape_like_str($this->dbprefix)."%' " + .sprintf($this->_like_escape_str, $this->_like_escape_chr); + } + + return $sql.' ORDER BY '.$this->escape_identifiers('name'); + } + + // -------------------------------------------------------------------- + + /** + * Show column query + * + * Generates a platform-specific query string so that the column names can be fetched + * + * @param string $table + * @return string + */ + protected function _list_columns($table = '') + { + return 'SELECT COLUMN_NAME + FROM INFORMATION_SCHEMA.Columns + WHERE UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table)); + } + + // -------------------------------------------------------------------- + + /** + * Returns an object with field data + * + * @param string $table + * @return array + */ + public function field_data($table) + { + $sql = 'SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, COLUMN_DEFAULT + FROM INFORMATION_SCHEMA.Columns + WHERE UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table)); + + if (($query = $this->query($sql)) === FALSE) + { + return FALSE; + } + $query = $query->result_object(); + + $retval = array(); + for ($i = 0, $c = count($query); $i < $c; $i++) + { + $retval[$i] = new stdClass(); + $retval[$i]->name = $query[$i]->COLUMN_NAME; + $retval[$i]->type = $query[$i]->DATA_TYPE; + $retval[$i]->max_length = ($query[$i]->CHARACTER_MAXIMUM_LENGTH > 0) ? $query[$i]->CHARACTER_MAXIMUM_LENGTH : $query[$i]->NUMERIC_PRECISION; + $retval[$i]->default = $query[$i]->COLUMN_DEFAULT; + } + + return $retval; + } + + // -------------------------------------------------------------------- + + /** + * Update statement + * + * Generates a platform-specific update string from the supplied data + * + * @param string $table + * @param array $values + * @return string + */ + protected function _update($table, $values) + { + $this->qb_limit = FALSE; + $this->qb_orderby = array(); + return parent::_update($table, $values); + } + + // -------------------------------------------------------------------- + + /** + * Delete statement + * + * Generates a platform-specific delete string from the supplied data + * + * @param string $table + * @return string + */ + protected function _delete($table) + { + if ($this->qb_limit) + { + return 'WITH ci_delete AS (SELECT TOP '.$this->qb_limit.' * FROM '.$table.$this->_compile_wh('qb_where').') DELETE FROM ci_delete'; + } + + return parent::_delete($table); + } + + // -------------------------------------------------------------------- + + /** + * LIMIT + * + * Generates a platform-specific LIMIT clause + * + * @param string $sql SQL Query + * @return string + */ + protected function _limit($sql) + { + $limit = $this->qb_offset + $this->qb_limit; + + // As of SQL Server 2005 (9.0.*) ROW_NUMBER() is supported, + // however an ORDER BY clause is required for it to work + if (version_compare($this->version(), '9', '>=') && $this->qb_offset && ! empty($this->qb_orderby)) + { + $orderby = $this->_compile_order_by(); + + // We have to strip the ORDER BY clause + $sql = trim(substr($sql, 0, strrpos($sql, $orderby))); + + // Get the fields to select from our subquery, so that we can avoid CI_rownum appearing in the actual results + if (count($this->qb_select) === 0) + { + $select = '*'; // Inevitable + } + else + { + // Use only field names and their aliases, everything else is out of our scope. + $select = array(); + $field_regexp = ($this->_quoted_identifier) + ? '("[^\"]+")' : '(\[[^\]]+\])'; + for ($i = 0, $c = count($this->qb_select); $i < $c; $i++) + { + $select[] = preg_match('/(?:\s|\.)'.$field_regexp.'$/i', $this->qb_select[$i], $m) + ? $m[1] : $this->qb_select[$i]; + } + $select = implode(', ', $select); + } + + return 'SELECT '.$select." FROM (\n\n" + .preg_replace('/^(SELECT( DISTINCT)?)/i', '\\1 ROW_NUMBER() OVER('.trim($orderby).') AS '.$this->escape_identifiers('CI_rownum').', ', $sql) + ."\n\n) ".$this->escape_identifiers('CI_subquery') + ."\nWHERE ".$this->escape_identifiers('CI_rownum').' BETWEEN '.($this->qb_offset + 1).' AND '.$limit; + } + + return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.$limit.' ', $sql); + } + + // -------------------------------------------------------------------- + + /** + * Insert batch statement + * + * Generates a platform-specific insert string from the supplied data. + * + * @param string $table Table name + * @param array $keys INSERT keys + * @param array $values INSERT values + * @return string|bool + */ + protected function _insert_batch($table, $keys, $values) + { + // Multiple-value inserts are only supported as of SQL Server 2008 + if (version_compare($this->version(), '10', '>=')) + { + return parent::_insert_batch($table, $keys, $values); + } + + return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE; + } + +} diff --git a/src/system/database/drivers/pdo/subdrivers/pdo_dblib_forge.php b/src/system/database/drivers/pdo/subdrivers/pdo_dblib_forge.php new file mode 100644 index 00000000..d5dd9aad --- /dev/null +++ b/src/system/database/drivers/pdo/subdrivers/pdo_dblib_forge.php @@ -0,0 +1,144 @@ + 'SMALLINT', + 'SMALLINT' => 'INT', + 'INT' => 'BIGINT', + 'REAL' => 'FLOAT' + ); + + // -------------------------------------------------------------------- + + /** + * ALTER TABLE + * + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * @return string|string[] + */ + protected function _alter_table($alter_type, $table, $field) + { + if (in_array($alter_type, array('ADD', 'DROP'), TRUE)) + { + return parent::_alter_table($alter_type, $table, $field); + } + + $sql = 'ALTER TABLE '.$this->db->escape_identifiers($table).' ALTER COLUMN '; + $sqls = array(); + for ($i = 0, $c = count($field); $i < $c; $i++) + { + $sqls[] = $sql.$this->_process_column($field[$i]); + } + + return $sqls; + } + + // -------------------------------------------------------------------- + + /** + * Field attribute TYPE + * + * Performs a data type mapping between different databases. + * + * @param array &$attributes + * @return void + */ + protected function _attr_type(&$attributes) + { + switch (strtoupper($attributes['TYPE'])) + { + case 'MEDIUMINT': + $attributes['TYPE'] = 'INTEGER'; + $attributes['UNSIGNED'] = FALSE; + return; + case 'INTEGER': + $attributes['TYPE'] = 'INT'; + return; + default: return; + } + } + + // -------------------------------------------------------------------- + + /** + * Field attribute AUTO_INCREMENT + * + * @param array &$attributes + * @param array &$field + * @return void + */ + protected function _attr_auto_increment(&$attributes, &$field) + { + if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE && stripos($field['type'], 'int') !== FALSE) + { + $field['auto_increment'] = ' IDENTITY(1,1)'; + } + } + +} diff --git a/src/system/database/drivers/pdo/subdrivers/pdo_firebird_driver.php b/src/system/database/drivers/pdo/subdrivers/pdo_firebird_driver.php new file mode 100644 index 00000000..96dcc5ec --- /dev/null +++ b/src/system/database/drivers/pdo/subdrivers/pdo_firebird_driver.php @@ -0,0 +1,263 @@ +dsn)) + { + $this->dsn = 'firebird:'; + + if ( ! empty($this->database)) + { + $this->dsn .= 'dbname='.$this->database; + } + elseif ( ! empty($this->hostname)) + { + $this->dsn .= 'dbname='.$this->hostname; + } + + empty($this->char_set) OR $this->dsn .= ';charset='.$this->char_set; + empty($this->role) OR $this->dsn .= ';role='.$this->role; + } + elseif ( ! empty($this->char_set) && strpos($this->dsn, 'charset=', 9) === FALSE) + { + $this->dsn .= ';charset='.$this->char_set; + } + } + + // -------------------------------------------------------------------- + + /** + * Show table query + * + * Generates a platform-specific query string so that the table names can be fetched + * + * @param bool $prefix_limit + * @return string + */ + protected function _list_tables($prefix_limit = FALSE) + { + $sql = 'SELECT "RDB$RELATION_NAME" FROM "RDB$RELATIONS" WHERE "RDB$RELATION_NAME" NOT LIKE \'RDB$%\' AND "RDB$RELATION_NAME" NOT LIKE \'MON$%\''; + + if ($prefix_limit === TRUE && $this->dbprefix !== '') + { + return $sql.' AND "RDB$RELATION_NAME" LIKE \''.$this->escape_like_str($this->dbprefix)."%' " + .sprintf($this->_like_escape_str, $this->_like_escape_chr); + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Show column query + * + * Generates a platform-specific query string so that the column names can be fetched + * + * @param string $table + * @return string + */ + protected function _list_columns($table = '') + { + return 'SELECT "RDB$FIELD_NAME" FROM "RDB$RELATION_FIELDS" WHERE "RDB$RELATION_NAME" = '.$this->escape($table); + } + + // -------------------------------------------------------------------- + + /** + * Returns an object with field data + * + * @param string $table + * @return array + */ + public function field_data($table) + { + $sql = 'SELECT "rfields"."RDB$FIELD_NAME" AS "name", + CASE "fields"."RDB$FIELD_TYPE" + WHEN 7 THEN \'SMALLINT\' + WHEN 8 THEN \'INTEGER\' + WHEN 9 THEN \'QUAD\' + WHEN 10 THEN \'FLOAT\' + WHEN 11 THEN \'DFLOAT\' + WHEN 12 THEN \'DATE\' + WHEN 13 THEN \'TIME\' + WHEN 14 THEN \'CHAR\' + WHEN 16 THEN \'INT64\' + WHEN 27 THEN \'DOUBLE\' + WHEN 35 THEN \'TIMESTAMP\' + WHEN 37 THEN \'VARCHAR\' + WHEN 40 THEN \'CSTRING\' + WHEN 261 THEN \'BLOB\' + ELSE NULL + END AS "type", + "fields"."RDB$FIELD_LENGTH" AS "max_length", + "rfields"."RDB$DEFAULT_VALUE" AS "default" + FROM "RDB$RELATION_FIELDS" "rfields" + JOIN "RDB$FIELDS" "fields" ON "rfields"."RDB$FIELD_SOURCE" = "fields"."RDB$FIELD_NAME" + WHERE "rfields"."RDB$RELATION_NAME" = '.$this->escape($table).' + ORDER BY "rfields"."RDB$FIELD_POSITION"'; + + return (($query = $this->query($sql)) !== FALSE) + ? $query->result_object() + : FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Update statement + * + * Generates a platform-specific update string from the supplied data + * + * @param string $table + * @param array $values + * @return string + */ + protected function _update($table, $values) + { + $this->qb_limit = FALSE; + return parent::_update($table, $values); + } + + // -------------------------------------------------------------------- + + /** + * Truncate statement + * + * Generates a platform-specific truncate string from the supplied data + * + * If the database does not support the TRUNCATE statement, + * then this method maps to 'DELETE FROM table' + * + * @param string $table + * @return string + */ + protected function _truncate($table) + { + return 'DELETE FROM '.$table; + } + + // -------------------------------------------------------------------- + + /** + * Delete statement + * + * Generates a platform-specific delete string from the supplied data + * + * @param string $table + * @return string + */ + protected function _delete($table) + { + $this->qb_limit = FALSE; + return parent::_delete($table); + } + + // -------------------------------------------------------------------- + + /** + * LIMIT + * + * Generates a platform-specific LIMIT clause + * + * @param string $sql SQL Query + * @return string + */ + protected function _limit($sql) + { + // Limit clause depends on if Interbase or Firebird + if (stripos($this->version(), 'firebird') !== FALSE) + { + $select = 'FIRST '.$this->qb_limit + .($this->qb_offset > 0 ? ' SKIP '.$this->qb_offset : ''); + } + else + { + $select = 'ROWS ' + .($this->qb_offset > 0 ? $this->qb_offset.' TO '.($this->qb_limit + $this->qb_offset) : $this->qb_limit); + } + + return preg_replace('`SELECT`i', 'SELECT '.$select, $sql); + } + +} diff --git a/src/system/database/drivers/pdo/subdrivers/pdo_firebird_forge.php b/src/system/database/drivers/pdo/subdrivers/pdo_firebird_forge.php new file mode 100644 index 00000000..256fa141 --- /dev/null +++ b/src/system/database/drivers/pdo/subdrivers/pdo_firebird_forge.php @@ -0,0 +1,237 @@ + 'INTEGER', + 'INTEGER' => 'INT64', + 'FLOAT' => 'DOUBLE PRECISION' + ); + + /** + * NULL value representation in CREATE/ALTER TABLE statements + * + * @var string + */ + protected $_null = 'NULL'; + + // -------------------------------------------------------------------- + + /** + * Create database + * + * @param string $db_name + * @return string + */ + public function create_database($db_name) + { + // Firebird databases are flat files, so a path is required + + // Hostname is needed for remote access + empty($this->db->hostname) OR $db_name = $this->hostname.':'.$db_name; + + return parent::create_database('"'.$db_name.'"'); + } + + // -------------------------------------------------------------------- + + /** + * Drop database + * + * @param string $db_name (ignored) + * @return bool + */ + public function drop_database($db_name = '') + { + if ( ! ibase_drop_db($this->conn_id)) + { + return ($this->db->db_debug) ? $this->db->display_error('db_unable_to_drop') : FALSE; + } + elseif ( ! empty($this->db->data_cache['db_names'])) + { + $key = array_search(strtolower($this->db->database), array_map('strtolower', $this->db->data_cache['db_names']), TRUE); + if ($key !== FALSE) + { + unset($this->db->data_cache['db_names'][$key]); + } + } + + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * ALTER TABLE + * + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * @return string|string[] + */ + protected function _alter_table($alter_type, $table, $field) + { + if (in_array($alter_type, array('DROP', 'ADD'), TRUE)) + { + return parent::_alter_table($alter_type, $table, $field); + } + + $sql = 'ALTER TABLE '.$this->db->escape_identifiers($table); + $sqls = array(); + for ($i = 0, $c = count($field); $i < $c; $i++) + { + if ($field[$i]['_literal'] !== FALSE) + { + return FALSE; + } + + if (isset($field[$i]['type'])) + { + $sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identifiers($field[$i]['name']) + .' TYPE '.$field[$i]['type'].$field[$i]['length']; + } + + if ( ! empty($field[$i]['default'])) + { + $sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identifiers($field[$i]['name']) + .' SET DEFAULT '.$field[$i]['default']; + } + + if (isset($field[$i]['null'])) + { + $sqls[] = 'UPDATE "RDB$RELATION_FIELDS" SET "RDB$NULL_FLAG" = ' + .($field[$i]['null'] === TRUE ? 'NULL' : '1') + .' WHERE "RDB$FIELD_NAME" = '.$this->db->escape($field[$i]['name']) + .' AND "RDB$RELATION_NAME" = '.$this->db->escape($table); + } + + if ( ! empty($field[$i]['new_name'])) + { + $sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identifiers($field[$i]['name']) + .' TO '.$this->db->escape_identifiers($field[$i]['new_name']); + } + } + + return $sqls; + } + + // -------------------------------------------------------------------- + + /** + * Process column + * + * @param array $field + * @return string + */ + protected function _process_column($field) + { + return $this->db->escape_identifiers($field['name']) + .' '.$field['type'].$field['length'] + .$field['null'] + .$field['unique'] + .$field['default']; + } + + // -------------------------------------------------------------------- + + /** + * Field attribute TYPE + * + * Performs a data type mapping between different databases. + * + * @param array &$attributes + * @return void + */ + protected function _attr_type(&$attributes) + { + switch (strtoupper($attributes['TYPE'])) + { + case 'TINYINT': + $attributes['TYPE'] = 'SMALLINT'; + $attributes['UNSIGNED'] = FALSE; + return; + case 'MEDIUMINT': + $attributes['TYPE'] = 'INTEGER'; + $attributes['UNSIGNED'] = FALSE; + return; + case 'INT': + $attributes['TYPE'] = 'INTEGER'; + return; + case 'BIGINT': + $attributes['TYPE'] = 'INT64'; + return; + default: return; + } + } + + // -------------------------------------------------------------------- + + /** + * Field attribute AUTO_INCREMENT + * + * @param array &$attributes + * @param array &$field + * @return void + */ + protected function _attr_auto_increment(&$attributes, &$field) + { + // Not supported + } + +} diff --git a/src/system/database/drivers/pdo/subdrivers/pdo_ibm_driver.php b/src/system/database/drivers/pdo/subdrivers/pdo_ibm_driver.php new file mode 100644 index 00000000..2366c403 --- /dev/null +++ b/src/system/database/drivers/pdo/subdrivers/pdo_ibm_driver.php @@ -0,0 +1,244 @@ +dsn)) + { + $this->dsn = 'ibm:'; + + // Pre-defined DSN + if (empty($this->hostname) && empty($this->HOSTNAME) && empty($this->port) && empty($this->PORT)) + { + if (isset($this->DSN)) + { + $this->dsn .= 'DSN='.$this->DSN; + } + elseif ( ! empty($this->database)) + { + $this->dsn .= 'DSN='.$this->database; + } + + return; + } + + $this->dsn .= 'DRIVER='.(isset($this->DRIVER) ? '{'.$this->DRIVER.'}' : '{IBM DB2 ODBC DRIVER}').';'; + + if (isset($this->DATABASE)) + { + $this->dsn .= 'DATABASE='.$this->DATABASE.';'; + } + elseif ( ! empty($this->database)) + { + $this->dsn .= 'DATABASE='.$this->database.';'; + } + + if (isset($this->HOSTNAME)) + { + $this->dsn .= 'HOSTNAME='.$this->HOSTNAME.';'; + } + else + { + $this->dsn .= 'HOSTNAME='.(empty($this->hostname) ? '127.0.0.1;' : $this->hostname.';'); + } + + if (isset($this->PORT)) + { + $this->dsn .= 'PORT='.$this->port.';'; + } + elseif ( ! empty($this->port)) + { + $this->dsn .= ';PORT='.$this->port.';'; + } + + $this->dsn .= 'PROTOCOL='.(isset($this->PROTOCOL) ? $this->PROTOCOL.';' : 'TCPIP;'); + } + } + + // -------------------------------------------------------------------- + + /** + * Show table query + * + * Generates a platform-specific query string so that the table names can be fetched + * + * @param bool $prefix_limit + * @return string + */ + protected function _list_tables($prefix_limit = FALSE) + { + $sql = 'SELECT "tabname" FROM "syscat"."tables" + WHERE "type" = \'T\' AND LOWER("tabschema") = '.$this->escape(strtolower($this->database)); + + if ($prefix_limit === TRUE && $this->dbprefix !== '') + { + $sql .= ' AND "tabname" LIKE \''.$this->escape_like_str($this->dbprefix)."%' " + .sprintf($this->_like_escape_str, $this->_like_escape_chr); + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Show column query + * + * Generates a platform-specific query string so that the column names can be fetched + * + * @param string $table + * @return array + */ + protected function _list_columns($table = '') + { + return 'SELECT "colname" FROM "syscat"."columns" + WHERE LOWER("tabschema") = '.$this->escape(strtolower($this->database)).' + AND LOWER("tabname") = '.$this->escape(strtolower($table)); + } + + // -------------------------------------------------------------------- + + /** + * Returns an object with field data + * + * @param string $table + * @return array + */ + public function field_data($table) + { + $sql = 'SELECT "colname" AS "name", "typename" AS "type", "default" AS "default", "length" AS "max_length", + CASE "keyseq" WHEN NULL THEN 0 ELSE 1 END AS "primary_key" + FROM "syscat"."columns" + WHERE LOWER("tabschema") = '.$this->escape(strtolower($this->database)).' + AND LOWER("tabname") = '.$this->escape(strtolower($table)).' + ORDER BY "colno"'; + + return (($query = $this->query($sql)) !== FALSE) + ? $query->result_object() + : FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Update statement + * + * Generates a platform-specific update string from the supplied data + * + * @param string $table + * @param array $values + * @return string + */ + protected function _update($table, $values) + { + $this->qb_limit = FALSE; + $this->qb_orderby = array(); + return parent::_update($table, $values); + } + + // -------------------------------------------------------------------- + + /** + * Delete statement + * + * Generates a platform-specific delete string from the supplied data + * + * @param string $table + * @return string + */ + protected function _delete($table) + { + $this->qb_limit = FALSE; + return parent::_delete($table); + } + + // -------------------------------------------------------------------- + + /** + * LIMIT + * + * Generates a platform-specific LIMIT clause + * + * @param string $sql SQL Query + * @return string + */ + protected function _limit($sql) + { + $sql .= ' FETCH FIRST '.($this->qb_limit + $this->qb_offset).' ROWS ONLY'; + + return ($this->qb_offset) + ? 'SELECT * FROM ('.$sql.') WHERE rownum > '.$this->qb_offset + : $sql; + } + +} diff --git a/src/system/database/drivers/pdo/subdrivers/pdo_ibm_forge.php b/src/system/database/drivers/pdo/subdrivers/pdo_ibm_forge.php new file mode 100644 index 00000000..a2dbfc25 --- /dev/null +++ b/src/system/database/drivers/pdo/subdrivers/pdo_ibm_forge.php @@ -0,0 +1,154 @@ + 'INTEGER', + 'INT' => 'BIGINT', + 'INTEGER' => 'BIGINT' + ); + + /** + * DEFAULT value representation in CREATE/ALTER TABLE statements + * + * @var string + */ + protected $_default = FALSE; + + // -------------------------------------------------------------------- + + /** + * ALTER TABLE + * + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * @return string|string[] + */ + protected function _alter_table($alter_type, $table, $field) + { + if ($alter_type === 'CHANGE') + { + $alter_type = 'MODIFY'; + } + + return parent::_alter_table($alter_type, $table, $field); + } + + // -------------------------------------------------------------------- + + /** + * Field attribute TYPE + * + * Performs a data type mapping between different databases. + * + * @param array &$attributes + * @return void + */ + protected function _attr_type(&$attributes) + { + switch (strtoupper($attributes['TYPE'])) + { + case 'TINYINT': + $attributes['TYPE'] = 'SMALLINT'; + $attributes['UNSIGNED'] = FALSE; + return; + case 'MEDIUMINT': + $attributes['TYPE'] = 'INTEGER'; + $attributes['UNSIGNED'] = FALSE; + return; + default: return; + } + } + + // -------------------------------------------------------------------- + + /** + * Field attribute UNIQUE + * + * @param array &$attributes + * @param array &$field + * @return void + */ + protected function _attr_unique(&$attributes, &$field) + { + if ( ! empty($attributes['UNIQUE']) && $attributes['UNIQUE'] === TRUE) + { + $field['unique'] = ' UNIQUE'; + + // UNIQUE must be used with NOT NULL + $field['null'] = ' NOT NULL'; + } + } + + // -------------------------------------------------------------------- + + /** + * Field attribute AUTO_INCREMENT + * + * @param array &$attributes + * @param array &$field + * @return void + */ + protected function _attr_auto_increment(&$attributes, &$field) + { + // Not supported + } + +} diff --git a/src/system/database/drivers/pdo/subdrivers/pdo_informix_driver.php b/src/system/database/drivers/pdo/subdrivers/pdo_informix_driver.php new file mode 100644 index 00000000..d40d17a8 --- /dev/null +++ b/src/system/database/drivers/pdo/subdrivers/pdo_informix_driver.php @@ -0,0 +1,309 @@ +dsn)) + { + $this->dsn = 'informix:'; + + // Pre-defined DSN + if (empty($this->hostname) && empty($this->host) && empty($this->port) && empty($this->service)) + { + if (isset($this->DSN)) + { + $this->dsn .= 'DSN='.$this->DSN; + } + elseif ( ! empty($this->database)) + { + $this->dsn .= 'DSN='.$this->database; + } + + return; + } + + if (isset($this->host)) + { + $this->dsn .= 'host='.$this->host; + } + else + { + $this->dsn .= 'host='.(empty($this->hostname) ? '127.0.0.1' : $this->hostname); + } + + if (isset($this->service)) + { + $this->dsn .= '; service='.$this->service; + } + elseif ( ! empty($this->port)) + { + $this->dsn .= '; service='.$this->port; + } + + empty($this->database) OR $this->dsn .= '; database='.$this->database; + empty($this->server) OR $this->dsn .= '; server='.$this->server; + + $this->dsn .= '; protocol='.(isset($this->protocol) ? $this->protocol : 'onsoctcp') + .'; EnableScrollableCursors=1'; + } + } + + // -------------------------------------------------------------------- + + /** + * Show table query + * + * Generates a platform-specific query string so that the table names can be fetched + * + * @param bool $prefix_limit + * @return string + */ + protected function _list_tables($prefix_limit = FALSE) + { + $sql = 'SELECT "tabname" FROM "systables" + WHERE "tabid" > 99 AND "tabtype" = \'T\' AND LOWER("owner") = '.$this->escape(strtolower($this->username)); + + if ($prefix_limit === TRUE && $this->dbprefix !== '') + { + $sql .= ' AND "tabname" LIKE \''.$this->escape_like_str($this->dbprefix)."%' " + .sprintf($this->_like_escape_str, $this->_like_escape_chr); + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Show column query + * + * Generates a platform-specific query string so that the column names can be fetched + * + * @param string $table + * @return string + */ + protected function _list_columns($table = '') + { + if (strpos($table, '.') !== FALSE) + { + sscanf($table, '%[^.].%s', $owner, $table); + } + else + { + $owner = $this->username; + } + + return 'SELECT "colname" FROM "systables", "syscolumns" + WHERE "systables"."tabid" = "syscolumns"."tabid" + AND "systables"."tabtype" = \'T\' + AND LOWER("systables"."owner") = '.$this->escape(strtolower($owner)).' + AND LOWER("systables"."tabname") = '.$this->escape(strtolower($table)); + } + + // -------------------------------------------------------------------- + + /** + * Returns an object with field data + * + * @param string $table + * @return array + */ + public function field_data($table) + { + $sql = 'SELECT "syscolumns"."colname" AS "name", + CASE "syscolumns"."coltype" + WHEN 0 THEN \'CHAR\' + WHEN 1 THEN \'SMALLINT\' + WHEN 2 THEN \'INTEGER\' + WHEN 3 THEN \'FLOAT\' + WHEN 4 THEN \'SMALLFLOAT\' + WHEN 5 THEN \'DECIMAL\' + WHEN 6 THEN \'SERIAL\' + WHEN 7 THEN \'DATE\' + WHEN 8 THEN \'MONEY\' + WHEN 9 THEN \'NULL\' + WHEN 10 THEN \'DATETIME\' + WHEN 11 THEN \'BYTE\' + WHEN 12 THEN \'TEXT\' + WHEN 13 THEN \'VARCHAR\' + WHEN 14 THEN \'INTERVAL\' + WHEN 15 THEN \'NCHAR\' + WHEN 16 THEN \'NVARCHAR\' + WHEN 17 THEN \'INT8\' + WHEN 18 THEN \'SERIAL8\' + WHEN 19 THEN \'SET\' + WHEN 20 THEN \'MULTISET\' + WHEN 21 THEN \'LIST\' + WHEN 22 THEN \'Unnamed ROW\' + WHEN 40 THEN \'LVARCHAR\' + WHEN 41 THEN \'BLOB/CLOB/BOOLEAN\' + WHEN 4118 THEN \'Named ROW\' + ELSE "syscolumns"."coltype" + END AS "type", + "syscolumns"."collength" as "max_length", + CASE "sysdefaults"."type" + WHEN \'L\' THEN "sysdefaults"."default" + ELSE NULL + END AS "default" + FROM "syscolumns", "systables", "sysdefaults" + WHERE "syscolumns"."tabid" = "systables"."tabid" + AND "systables"."tabid" = "sysdefaults"."tabid" + AND "syscolumns"."colno" = "sysdefaults"."colno" + AND "systables"."tabtype" = \'T\' + AND LOWER("systables"."owner") = '.$this->escape(strtolower($this->username)).' + AND LOWER("systables"."tabname") = '.$this->escape(strtolower($table)).' + ORDER BY "syscolumns"."colno"'; + + return (($query = $this->query($sql)) !== FALSE) + ? $query->result_object() + : FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Update statement + * + * Generates a platform-specific update string from the supplied data + * + * @param string $table + * @param array $values + * @return string + */ + protected function _update($table, $values) + { + $this->qb_limit = FALSE; + $this->qb_orderby = array(); + return parent::_update($table, $values); + } + + // -------------------------------------------------------------------- + + /** + * Truncate statement + * + * Generates a platform-specific truncate string from the supplied data + * + * If the database does not support the TRUNCATE statement, + * then this method maps to 'DELETE FROM table' + * + * @param string $table + * @return string + */ + protected function _truncate($table) + { + return 'TRUNCATE TABLE ONLY '.$table; + } + + // -------------------------------------------------------------------- + + /** + * Delete statement + * + * Generates a platform-specific delete string from the supplied data + * + * @param string $table + * @return string + */ + protected function _delete($table) + { + $this->qb_limit = FALSE; + return parent::_delete($table); + } + + // -------------------------------------------------------------------- + + /** + * LIMIT + * + * Generates a platform-specific LIMIT clause + * + * @param string $sql $SQL Query + * @return string + */ + protected function _limit($sql) + { + $select = 'SELECT '.($this->qb_offset ? 'SKIP '.$this->qb_offset : '').'FIRST '.$this->qb_limit.' '; + return preg_replace('/^(SELECT\s)/i', $select, $sql, 1); + } + +} diff --git a/src/system/database/drivers/pdo/subdrivers/pdo_informix_forge.php b/src/system/database/drivers/pdo/subdrivers/pdo_informix_forge.php new file mode 100644 index 00000000..5af39b18 --- /dev/null +++ b/src/system/database/drivers/pdo/subdrivers/pdo_informix_forge.php @@ -0,0 +1,163 @@ + 'INTEGER', + 'INT' => 'BIGINT', + 'INTEGER' => 'BIGINT', + 'REAL' => 'DOUBLE PRECISION', + 'SMALLFLOAT' => 'DOUBLE PRECISION' + ); + + /** + * DEFAULT value representation in CREATE/ALTER TABLE statements + * + * @var string + */ + protected $_default = ', '; + + // -------------------------------------------------------------------- + + /** + * ALTER TABLE + * + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * @return string|string[] + */ + protected function _alter_table($alter_type, $table, $field) + { + if ($alter_type === 'CHANGE') + { + $alter_type = 'MODIFY'; + } + + return parent::_alter_table($alter_type, $table, $field); + } + + // -------------------------------------------------------------------- + + /** + * Field attribute TYPE + * + * Performs a data type mapping between different databases. + * + * @param array &$attributes + * @return void + */ + protected function _attr_type(&$attributes) + { + switch (strtoupper($attributes['TYPE'])) + { + case 'TINYINT': + $attributes['TYPE'] = 'SMALLINT'; + $attributes['UNSIGNED'] = FALSE; + return; + case 'MEDIUMINT': + $attributes['TYPE'] = 'INTEGER'; + $attributes['UNSIGNED'] = FALSE; + return; + case 'BYTE': + case 'TEXT': + case 'BLOB': + case 'CLOB': + $attributes['UNIQUE'] = FALSE; + if (isset($attributes['DEFAULT'])) + { + unset($attributes['DEFAULT']); + } + return; + default: return; + } + } + + // -------------------------------------------------------------------- + + /** + * Field attribute UNIQUE + * + * @param array &$attributes + * @param array &$field + * @return void + */ + protected function _attr_unique(&$attributes, &$field) + { + if ( ! empty($attributes['UNIQUE']) && $attributes['UNIQUE'] === TRUE) + { + $field['unique'] = ' UNIQUE CONSTRAINT '.$this->db->escape_identifiers($field['name']); + } + } + + // -------------------------------------------------------------------- + + /** + * Field attribute AUTO_INCREMENT + * + * @param array &$attributes + * @param array &$field + * @return void + */ + protected function _attr_auto_increment(&$attributes, &$field) + { + // Not supported + } + +} diff --git a/src/system/database/drivers/pdo/subdrivers/pdo_mysql_driver.php b/src/system/database/drivers/pdo/subdrivers/pdo_mysql_driver.php new file mode 100644 index 00000000..70f2bfd4 --- /dev/null +++ b/src/system/database/drivers/pdo/subdrivers/pdo_mysql_driver.php @@ -0,0 +1,336 @@ +dsn)) + { + $this->dsn = 'mysql:host='.(empty($this->hostname) ? '127.0.0.1' : $this->hostname); + + empty($this->port) OR $this->dsn .= ';port='.$this->port; + empty($this->database) OR $this->dsn .= ';dbname='.$this->database; + empty($this->char_set) OR $this->dsn .= ';charset='.$this->char_set; + } + elseif ( ! empty($this->char_set) && strpos($this->dsn, 'charset=', 6) === FALSE && is_php('5.3.6')) + { + $this->dsn .= ';charset='.$this->char_set; + } + } + + // -------------------------------------------------------------------- + + /** + * Database connection + * + * @param bool $persistent + * @return object + */ + public function db_connect($persistent = FALSE) + { + /* Prior to PHP 5.3.6, even if the charset was supplied in the DSN + * on connect - it was ignored. This is a work-around for the issue. + * + * Reference: http://www.php.net/manual/en/ref.pdo-mysql.connection.php + */ + if ( ! is_php('5.3.6') && ! empty($this->char_set)) + { + $this->options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES '.$this->char_set + .(empty($this->dbcollat) ? '' : ' COLLATE '.$this->dbcollat); + } + + if (isset($this->stricton)) + { + if ($this->stricton) + { + $sql = 'CONCAT(@@sql_mode, ",", "STRICT_ALL_TABLES")'; + } + else + { + $sql = 'REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + @@sql_mode, + "STRICT_ALL_TABLES,", ""), + ",STRICT_ALL_TABLES", ""), + "STRICT_ALL_TABLES", ""), + "STRICT_TRANS_TABLES,", ""), + ",STRICT_TRANS_TABLES", ""), + "STRICT_TRANS_TABLES", "")'; + } + + if ( ! empty($sql)) + { + if (empty($this->options[PDO::MYSQL_ATTR_INIT_COMMAND])) + { + $this->options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET SESSION sql_mode = '.$sql; + } + else + { + $this->options[PDO::MYSQL_ATTR_INIT_COMMAND] .= ', @@session.sql_mode = '.$sql; + } + } + } + + if ($this->compress === TRUE) + { + $this->options[PDO::MYSQL_ATTR_COMPRESS] = TRUE; + } + + // SSL support was added to PDO_MYSQL in PHP 5.3.7 + if (is_array($this->encrypt) && is_php('5.3.7')) + { + $ssl = array(); + empty($this->encrypt['ssl_key']) OR $ssl[PDO::MYSQL_ATTR_SSL_KEY] = $this->encrypt['ssl_key']; + empty($this->encrypt['ssl_cert']) OR $ssl[PDO::MYSQL_ATTR_SSL_CERT] = $this->encrypt['ssl_cert']; + empty($this->encrypt['ssl_ca']) OR $ssl[PDO::MYSQL_ATTR_SSL_CA] = $this->encrypt['ssl_ca']; + empty($this->encrypt['ssl_capath']) OR $ssl[PDO::MYSQL_ATTR_SSL_CAPATH] = $this->encrypt['ssl_capath']; + empty($this->encrypt['ssl_cipher']) OR $ssl[PDO::MYSQL_ATTR_SSL_CIPHER] = $this->encrypt['ssl_cipher']; + + // DO NOT use array_merge() here! + // It re-indexes numeric keys and the PDO_MYSQL_ATTR_SSL_* constants are integers. + empty($ssl) OR $this->options += $ssl; + } + + // Prior to version 5.7.3, MySQL silently downgrades to an unencrypted connection if SSL setup fails + if ( + ($pdo = parent::db_connect($persistent)) !== FALSE + && ! empty($ssl) + && version_compare($pdo->getAttribute(PDO::ATTR_CLIENT_VERSION), '5.7.3', '<=') + && empty($pdo->query("SHOW STATUS LIKE 'ssl_cipher'")->fetchObject()->Value) + ) + { + $message = 'PDO_MYSQL was configured for an SSL connection, but got an unencrypted connection instead!'; + log_message('error', $message); + return ($this->db->db_debug) ? $this->db->display_error($message, '', TRUE) : FALSE; + } + + return $pdo; + } + + // -------------------------------------------------------------------- + + /** + * Select the database + * + * @param string $database + * @return bool + */ + public function db_select($database = '') + { + if ($database === '') + { + $database = $this->database; + } + + if (FALSE !== $this->simple_query('USE '.$this->escape_identifiers($database))) + { + $this->database = $database; + return TRUE; + } + + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Show table query + * + * Generates a platform-specific query string so that the table names can be fetched + * + * @param bool $prefix_limit + * @return string + */ + protected function _list_tables($prefix_limit = FALSE) + { + $sql = 'SHOW TABLES'; + + if ($prefix_limit === TRUE && $this->dbprefix !== '') + { + return $sql." LIKE '".$this->escape_like_str($this->dbprefix)."%'"; + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Show column query + * + * Generates a platform-specific query string so that the column names can be fetched + * + * @param string $table + * @return string + */ + protected function _list_columns($table = '') + { + return 'SHOW COLUMNS FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE); + } + + // -------------------------------------------------------------------- + + /** + * Returns an object with field data + * + * @param string $table + * @return array + */ + public function field_data($table) + { + if (($query = $this->query('SHOW COLUMNS FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE))) === FALSE) + { + return FALSE; + } + $query = $query->result_object(); + + $retval = array(); + for ($i = 0, $c = count($query); $i < $c; $i++) + { + $retval[$i] = new stdClass(); + $retval[$i]->name = $query[$i]->Field; + + sscanf($query[$i]->Type, '%[a-z](%d)', + $retval[$i]->type, + $retval[$i]->max_length + ); + + $retval[$i]->default = $query[$i]->Default; + $retval[$i]->primary_key = (int) ($query[$i]->Key === 'PRI'); + } + + return $retval; + } + + // -------------------------------------------------------------------- + + /** + * Truncate statement + * + * Generates a platform-specific truncate string from the supplied data + * + * If the database does not support the TRUNCATE statement, + * then this method maps to 'DELETE FROM table' + * + * @param string $table + * @return string + */ + protected function _truncate($table) + { + return 'TRUNCATE '.$table; + } + + // -------------------------------------------------------------------- + + /** + * FROM tables + * + * Groups tables in FROM clauses if needed, so there is no confusion + * about operator precedence. + * + * @return string + */ + protected function _from_tables() + { + if ( ! empty($this->qb_join) && count($this->qb_from) > 1) + { + return '('.implode(', ', $this->qb_from).')'; + } + + return implode(', ', $this->qb_from); + } + +} diff --git a/src/system/database/drivers/pdo/subdrivers/pdo_mysql_forge.php b/src/system/database/drivers/pdo/subdrivers/pdo_mysql_forge.php new file mode 100644 index 00000000..9d04a8a9 --- /dev/null +++ b/src/system/database/drivers/pdo/subdrivers/pdo_mysql_forge.php @@ -0,0 +1,256 @@ +db->char_set) && ! strpos($sql, 'CHARACTER SET') && ! strpos($sql, 'CHARSET')) + { + $sql .= ' DEFAULT CHARACTER SET = '.$this->db->char_set; + } + + if ( ! empty($this->db->dbcollat) && ! strpos($sql, 'COLLATE')) + { + $sql .= ' COLLATE = '.$this->db->dbcollat; + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * ALTER TABLE + * + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * @return string|string[] + */ + protected function _alter_table($alter_type, $table, $field) + { + if ($alter_type === 'DROP') + { + return parent::_alter_table($alter_type, $table, $field); + } + + $sql = 'ALTER TABLE '.$this->db->escape_identifiers($table); + for ($i = 0, $c = count($field); $i < $c; $i++) + { + if ($field[$i]['_literal'] !== FALSE) + { + $field[$i] = ($alter_type === 'ADD') + ? "\n\tADD ".$field[$i]['_literal'] + : "\n\tMODIFY ".$field[$i]['_literal']; + } + else + { + if ($alter_type === 'ADD') + { + $field[$i]['_literal'] = "\n\tADD "; + } + else + { + $field[$i]['_literal'] = empty($field[$i]['new_name']) ? "\n\tMODIFY " : "\n\tCHANGE "; + } + + $field[$i] = $field[$i]['_literal'].$this->_process_column($field[$i]); + } + } + + return array($sql.implode(',', $field)); + } + + // -------------------------------------------------------------------- + + /** + * Process column + * + * @param array $field + * @return string + */ + protected function _process_column($field) + { + $extra_clause = isset($field['after']) + ? ' AFTER '.$this->db->escape_identifiers($field['after']) : ''; + + if (empty($extra_clause) && isset($field['first']) && $field['first'] === TRUE) + { + $extra_clause = ' FIRST'; + } + + return $this->db->escape_identifiers($field['name']) + .(empty($field['new_name']) ? '' : ' '.$this->db->escape_identifiers($field['new_name'])) + .' '.$field['type'].$field['length'] + .$field['unsigned'] + .$field['null'] + .$field['default'] + .$field['auto_increment'] + .$field['unique'] + .(empty($field['comment']) ? '' : ' COMMENT '.$field['comment']) + .$extra_clause; + } + + // -------------------------------------------------------------------- + + /** + * Process indexes + * + * @param string $table (ignored) + * @return string + */ + protected function _process_indexes($table) + { + $sql = ''; + + for ($i = 0, $c = count($this->keys); $i < $c; $i++) + { + if (is_array($this->keys[$i])) + { + for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++) + { + if ( ! isset($this->fields[$this->keys[$i][$i2]])) + { + unset($this->keys[$i][$i2]); + continue; + } + } + } + elseif ( ! isset($this->fields[$this->keys[$i]])) + { + unset($this->keys[$i]); + continue; + } + + is_array($this->keys[$i]) OR $this->keys[$i] = array($this->keys[$i]); + + $sql .= ",\n\tKEY ".$this->db->escape_identifiers(implode('_', $this->keys[$i])) + .' ('.implode(', ', $this->db->escape_identifiers($this->keys[$i])).')'; + } + + $this->keys = array(); + + return $sql; + } + +} diff --git a/src/system/database/drivers/pdo/subdrivers/pdo_oci_driver.php b/src/system/database/drivers/pdo/subdrivers/pdo_oci_driver.php new file mode 100644 index 00000000..dd1d31c2 --- /dev/null +++ b/src/system/database/drivers/pdo/subdrivers/pdo_oci_driver.php @@ -0,0 +1,326 @@ +dsn)) + { + $this->dsn = 'oci:dbname='; + + // Oracle has a slightly different PDO DSN format (Easy Connect), + // which also supports pre-defined DSNs. + if (empty($this->hostname) && empty($this->port)) + { + $this->dsn .= $this->database; + } + else + { + $this->dsn .= '//'.(empty($this->hostname) ? '127.0.0.1' : $this->hostname) + .(empty($this->port) ? '' : ':'.$this->port).'/'; + + empty($this->database) OR $this->dsn .= $this->database; + } + + empty($this->char_set) OR $this->dsn .= ';charset='.$this->char_set; + } + elseif ( ! empty($this->char_set) && strpos($this->dsn, 'charset=', 4) === FALSE) + { + $this->dsn .= ';charset='.$this->char_set; + } + } + + // -------------------------------------------------------------------- + + /** + * Database version number + * + * @return string + */ + public function version() + { + if (isset($this->data_cache['version'])) + { + return $this->data_cache['version']; + } + + $version_string = parent::version(); + if (preg_match('#Release\s(?\d+(?:\.\d+)+)#', $version_string, $match)) + { + return $this->data_cache['version'] = $match[1]; + } + + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Show table query + * + * Generates a platform-specific query string so that the table names can be fetched + * + * @param bool $prefix_limit + * @return string + */ + protected function _list_tables($prefix_limit = FALSE) + { + $sql = 'SELECT "TABLE_NAME" FROM "ALL_TABLES"'; + + if ($prefix_limit === TRUE && $this->dbprefix !== '') + { + return $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 + * + * @param string $table + * @return string + */ + protected function _list_columns($table = '') + { + if (strpos($table, '.') !== FALSE) + { + sscanf($table, '%[^.].%s', $owner, $table); + } + else + { + $owner = $this->username; + } + + return 'SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS + WHERE UPPER(OWNER) = '.$this->escape(strtoupper($owner)).' + AND UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table)); + } + + // -------------------------------------------------------------------- + + /** + * Returns an object with field data + * + * @param string $table + * @return array + */ + public function field_data($table) + { + if (strpos($table, '.') !== FALSE) + { + sscanf($table, '%[^.].%s', $owner, $table); + } + else + { + $owner = $this->username; + } + + $sql = 'SELECT COLUMN_NAME, DATA_TYPE, CHAR_LENGTH, DATA_PRECISION, DATA_LENGTH, DATA_DEFAULT, NULLABLE + FROM ALL_TAB_COLUMNS + WHERE UPPER(OWNER) = '.$this->escape(strtoupper($owner)).' + AND UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table)); + + if (($query = $this->query($sql)) === FALSE) + { + return FALSE; + } + $query = $query->result_object(); + + $retval = array(); + for ($i = 0, $c = count($query); $i < $c; $i++) + { + $retval[$i] = new stdClass(); + $retval[$i]->name = $query[$i]->COLUMN_NAME; + $retval[$i]->type = $query[$i]->DATA_TYPE; + + $length = ($query[$i]->CHAR_LENGTH > 0) + ? $query[$i]->CHAR_LENGTH : $query[$i]->DATA_PRECISION; + if ($length === NULL) + { + $length = $query[$i]->DATA_LENGTH; + } + $retval[$i]->max_length = $length; + + $default = $query[$i]->DATA_DEFAULT; + if ($default === NULL && $query[$i]->NULLABLE === 'N') + { + $default = ''; + } + $retval[$i]->default = $query[$i]->COLUMN_DEFAULT; + } + + return $retval; + } + + // -------------------------------------------------------------------- + + /** + * Insert batch statement + * + * @param string $table Table name + * @param array $keys INSERT keys + * @param array $values 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"; + } + + return $sql.'SELECT * FROM dual'; + } + + // -------------------------------------------------------------------- + + /** + * Delete statement + * + * Generates a platform-specific delete string from the supplied data + * + * @param string $table + * @return string + */ + protected function _delete($table) + { + if ($this->qb_limit) + { + $this->where('rownum <= ',$this->qb_limit, FALSE); + $this->qb_limit = FALSE; + } + + return parent::_delete($table); + } + + // -------------------------------------------------------------------- + + /** + * LIMIT + * + * Generates a platform-specific LIMIT clause + * + * @param string $sql SQL Query + * @return string + */ + protected function _limit($sql) + { + if (version_compare($this->version(), '12.1', '>=')) + { + // OFFSET-FETCH can be used only with the ORDER BY clause + empty($this->qb_orderby) && $sql .= ' ORDER BY 1'; + + return $sql.' OFFSET '.(int) $this->qb_offset.' ROWS FETCH NEXT '.$this->qb_limit.' ROWS ONLY'; + } + + return 'SELECT * FROM (SELECT inner_query.*, rownum rnum FROM ('.$sql.') inner_query WHERE rownum < '.($this->qb_offset + $this->qb_limit + 1).')' + .($this->qb_offset ? ' WHERE rnum >= '.($this->qb_offset + 1): ''); + } + +} diff --git a/src/system/database/drivers/pdo/subdrivers/pdo_oci_forge.php b/src/system/database/drivers/pdo/subdrivers/pdo_oci_forge.php new file mode 100644 index 00000000..d0b7be8f --- /dev/null +++ b/src/system/database/drivers/pdo/subdrivers/pdo_oci_forge.php @@ -0,0 +1,149 @@ +db->escape_identifiers($table); + $sqls = array(); + for ($i = 0, $c = count($field); $i < $c; $i++) + { + if ($field[$i]['_literal'] !== FALSE) + { + $field[$i] = "\n\t".$field[$i]['_literal']; + } + else + { + $field[$i]['_literal'] = "\n\t".$this->_process_column($field[$i]); + + if ( ! empty($field[$i]['comment'])) + { + $sqls[] = 'COMMENT ON COLUMN ' + .$this->db->escape_identifiers($table).'.'.$this->db->escape_identifiers($field[$i]['name']) + .' IS '.$field[$i]['comment']; + } + + if ($alter_type === 'MODIFY' && ! empty($field[$i]['new_name'])) + { + $sqls[] = $sql.' RENAME COLUMN '.$this->db->escape_identifiers($field[$i]['name']) + .' '.$this->db->escape_identifiers($field[$i]['new_name']); + } + } + } + + $sql .= ' '.$alter_type.' '; + $sql .= (count($field) === 1) + ? $field[0] + : '('.implode(',', $field).')'; + + // RENAME COLUMN must be executed after MODIFY + array_unshift($sqls, $sql); + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Field attribute AUTO_INCREMENT + * + * @param array &$attributes + * @param array &$field + * @return void + */ + protected function _attr_auto_increment(&$attributes, &$field) + { + // Not supported - sequences and triggers must be used instead + } + +} diff --git a/src/system/database/drivers/pdo/subdrivers/pdo_odbc_driver.php b/src/system/database/drivers/pdo/subdrivers/pdo_odbc_driver.php new file mode 100644 index 00000000..33344883 --- /dev/null +++ b/src/system/database/drivers/pdo/subdrivers/pdo_odbc_driver.php @@ -0,0 +1,284 @@ +dsn)) + { + $this->dsn = 'odbc:'; + + // Pre-defined DSN + if (empty($this->hostname) && empty($this->HOSTNAME) && empty($this->port) && empty($this->PORT)) + { + if (isset($this->DSN)) + { + $this->dsn .= 'DSN='.$this->DSN; + } + elseif ( ! empty($this->database)) + { + $this->dsn .= 'DSN='.$this->database; + } + + return; + } + + // If the DSN is not pre-configured - try to build an IBM DB2 connection string + $this->dsn .= 'DRIVER='.(isset($this->DRIVER) ? '{'.$this->DRIVER.'}' : '{IBM DB2 ODBC DRIVER}').';'; + + if (isset($this->DATABASE)) + { + $this->dsn .= 'DATABASE='.$this->DATABASE.';'; + } + elseif ( ! empty($this->database)) + { + $this->dsn .= 'DATABASE='.$this->database.';'; + } + + if (isset($this->HOSTNAME)) + { + $this->dsn .= 'HOSTNAME='.$this->HOSTNAME.';'; + } + else + { + $this->dsn .= 'HOSTNAME='.(empty($this->hostname) ? '127.0.0.1;' : $this->hostname.';'); + } + + if (isset($this->PORT)) + { + $this->dsn .= 'PORT='.$this->port.';'; + } + elseif ( ! empty($this->port)) + { + $this->dsn .= ';PORT='.$this->port.';'; + } + + $this->dsn .= 'PROTOCOL='.(isset($this->PROTOCOL) ? $this->PROTOCOL.';' : 'TCPIP;'); + } + } + + // -------------------------------------------------------------------- + + /** + * Determines if a query is a "write" type. + * + * @param string An SQL query string + * @return bool + */ + public function is_write_type($sql) + { + if (preg_match('#^(INSERT|UPDATE).*RETURNING\s.+(\,\s?.+)*$#i', $sql)) + { + return FALSE; + } + + return parent::is_write_type($sql); + } + + // -------------------------------------------------------------------- + + /** + * Show table query + * + * Generates a platform-specific query string so that the table names can be fetched + * + * @param bool $prefix_limit + * @return string + */ + protected function _list_tables($prefix_limit = FALSE) + { + $sql = "SELECT table_name FROM information_schema.tables WHERE table_schema = '".$this->schema."'"; + + if ($prefix_limit !== FALSE && $this->dbprefix !== '') + { + return $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 + * + * @param string $table + * @return string + */ + protected function _list_columns($table = '') + { + return 'SELECT column_name FROM information_schema.columns WHERE table_name = '.$this->escape($table); + } + + // -------------------------------------------------------------------- + + /** + * Update statement + * + * Generates a platform-specific update string from the supplied data + * + * @param string $table + * @param array $values + * @return string + */ + protected function _update($table, $values) + { + $this->qb_limit = FALSE; + $this->qb_orderby = array(); + return parent::_update($table, $values); + } + + // -------------------------------------------------------------------- + + /** + * Truncate statement + * + * Generates a platform-specific truncate string from the supplied data + * + * If the database does not support the TRUNCATE statement, + * then this method maps to 'DELETE FROM table' + * + * @param string $table + * @return string + */ + protected function _truncate($table) + { + return 'DELETE FROM '.$table; + } + + // -------------------------------------------------------------------- + + /** + * Delete statement + * + * Generates a platform-specific delete string from the supplied data + * + * @param string the table name + * @return string + */ + protected function _delete($table) + { + $this->qb_limit = FALSE; + return parent::_delete($table); + } + + // -------------------------------------------------------------------- + + /** + * LIMIT + * + * Generates a platform-specific LIMIT clause + * + * @param string $sql SQL Query + * @return string + */ + protected function _limit($sql) + { + return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.$this->qb_limit.' ', $sql); + } + +} diff --git a/src/system/database/drivers/pdo/subdrivers/pdo_odbc_forge.php b/src/system/database/drivers/pdo/subdrivers/pdo_odbc_forge.php new file mode 100644 index 00000000..7c65daa8 --- /dev/null +++ b/src/system/database/drivers/pdo/subdrivers/pdo_odbc_forge.php @@ -0,0 +1,70 @@ +dsn)) + { + $this->dsn = 'pgsql:host='.(empty($this->hostname) ? '127.0.0.1' : $this->hostname); + + empty($this->port) OR $this->dsn .= ';port='.$this->port; + empty($this->database) OR $this->dsn .= ';dbname='.$this->database; + + if ( ! empty($this->username)) + { + $this->dsn .= ';username='.$this->username; + empty($this->password) OR $this->dsn .= ';password='.$this->password; + } + } + } + + // -------------------------------------------------------------------- + + /** + * Database connection + * + * @param bool $persistent + * @return object + */ + public function db_connect($persistent = FALSE) + { + $this->conn_id = parent::db_connect($persistent); + + if (is_object($this->conn_id) && ! empty($this->schema)) + { + $this->simple_query('SET search_path TO '.$this->schema.',public'); + } + + return $this->conn_id; + } + + // -------------------------------------------------------------------- + + /** + * Insert ID + * + * @param string $name + * @return int + */ + public function insert_id($name = NULL) + { + if ($name === NULL && version_compare($this->version(), '8.1', '>=')) + { + $query = $this->query('SELECT LASTVAL() AS ins_id'); + $query = $query->row(); + return $query->ins_id; + } + + return $this->conn_id->lastInsertId($name); + } + + // -------------------------------------------------------------------- + + /** + * Determines if a query is a "write" type. + * + * @param string An SQL query string + * @return bool + */ + public function is_write_type($sql) + { + if (preg_match('#^(INSERT|UPDATE).*RETURNING\s.+(\,\s?.+)*$#i', $sql)) + { + return FALSE; + } + + return parent::is_write_type($sql); + } + + // -------------------------------------------------------------------- + + /** + * "Smart" Escape String + * + * Escapes data based on type + * + * @param string $str + * @return mixed + */ + public function escape($str) + { + if (is_bool($str)) + { + return ($str) ? 'TRUE' : 'FALSE'; + } + + return parent::escape($str); + } + + // -------------------------------------------------------------------- + + /** + * ORDER BY + * + * @param string $orderby + * @param string $direction ASC, DESC or RANDOM + * @param bool $escape + * @return object + */ + public function order_by($orderby, $direction = '', $escape = NULL) + { + $direction = strtoupper(trim($direction)); + if ($direction === 'RANDOM') + { + if ( ! is_float($orderby) && ctype_digit((string) $orderby)) + { + $orderby = ($orderby > 1) + ? (float) '0.'.$orderby + : (float) $orderby; + } + + if (is_float($orderby)) + { + $this->simple_query('SET SEED '.$orderby); + } + + $orderby = $this->_random_keyword[0]; + $direction = ''; + $escape = FALSE; + } + + return parent::order_by($orderby, $direction, $escape); + } + + // -------------------------------------------------------------------- + + /** + * Show table query + * + * Generates a platform-specific query string so that the table names can be fetched + * + * @param bool $prefix_limit + * @return string + */ + protected function _list_tables($prefix_limit = FALSE) + { + $sql = 'SELECT "table_name" FROM "information_schema"."tables" WHERE "table_schema" = \''.$this->schema."'"; + + if ($prefix_limit === TRUE && $this->dbprefix !== '') + { + return $sql.' AND "table_name" LIKE \'' + .$this->escape_like_str($this->dbprefix)."%' " + .sprintf($this->_like_escape_str, $this->_like_escape_chr); + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * List column query + * + * Generates a platform-specific query string so that the column names can be fetched + * + * @param string $table + * @return string + */ + protected function _list_columns($table = '') + { + return 'SELECT "column_name" + FROM "information_schema"."columns" + WHERE LOWER("table_name") = '.$this->escape(strtolower($table)); + } + + // -------------------------------------------------------------------- + + /** + * Returns an object with field data + * + * @param string $table + * @return array + */ + public function field_data($table) + { + $sql = 'SELECT "column_name", "data_type", "character_maximum_length", "numeric_precision", "column_default" + FROM "information_schema"."columns" + WHERE LOWER("table_name") = '.$this->escape(strtolower($table)); + + if (($query = $this->query($sql)) === FALSE) + { + return FALSE; + } + $query = $query->result_object(); + + $retval = array(); + for ($i = 0, $c = count($query); $i < $c; $i++) + { + $retval[$i] = new stdClass(); + $retval[$i]->name = $query[$i]->column_name; + $retval[$i]->type = $query[$i]->data_type; + $retval[$i]->max_length = ($query[$i]->character_maximum_length > 0) ? $query[$i]->character_maximum_length : $query[$i]->numeric_precision; + $retval[$i]->default = $query[$i]->column_default; + } + + return $retval; + } + + // -------------------------------------------------------------------- + + /** + * Update statement + * + * Generates a platform-specific update string from the supplied data + * + * @param string $table + * @param array $values + * @return string + */ + protected function _update($table, $values) + { + $this->qb_limit = FALSE; + $this->qb_orderby = array(); + return parent::_update($table, $values); + } + + // -------------------------------------------------------------------- + + /** + * Update_Batch statement + * + * Generates a platform-specific batch update string from the supplied data + * + * @param string $table Table name + * @param array $values Update data + * @param string $index WHERE key + * @return string + */ + protected function _update_batch($table, $values, $index) + { + $ids = array(); + foreach ($values as $key => $val) + { + $ids[] = $val[$index]; + + foreach (array_keys($val) as $field) + { + if ($field !== $index) + { + $final[$field][] = 'WHEN '.$val[$index].' THEN '.$val[$field]; + } + } + } + + $cases = ''; + foreach ($final as $k => $v) + { + $cases .= $k.' = (CASE '.$index."\n" + .implode("\n", $v)."\n" + .'ELSE '.$k.' END), '; + } + + $this->where($index.' IN('.implode(',', $ids).')', NULL, FALSE); + + return 'UPDATE '.$table.' SET '.substr($cases, 0, -2).$this->_compile_wh('qb_where'); + } + + // -------------------------------------------------------------------- + + /** + * Delete statement + * + * Generates a platform-specific delete string from the supplied data + * + * @param string $table + * @return string + */ + protected function _delete($table) + { + $this->qb_limit = FALSE; + return parent::_delete($table); + } + + // -------------------------------------------------------------------- + + /** + * LIMIT + * + * Generates a platform-specific LIMIT clause + * + * @param string $sql SQL Query + * @return string + */ + protected function _limit($sql) + { + return $sql.' LIMIT '.$this->qb_limit.($this->qb_offset ? ' OFFSET '.$this->qb_offset : ''); + } + +} diff --git a/src/system/database/drivers/pdo/subdrivers/pdo_pgsql_forge.php b/src/system/database/drivers/pdo/subdrivers/pdo_pgsql_forge.php new file mode 100644 index 00000000..214b6f52 --- /dev/null +++ b/src/system/database/drivers/pdo/subdrivers/pdo_pgsql_forge.php @@ -0,0 +1,210 @@ + 'INTEGER', + 'SMALLINT' => 'INTEGER', + 'INT' => 'BIGINT', + 'INT4' => 'BIGINT', + 'INTEGER' => 'BIGINT', + 'INT8' => 'NUMERIC', + 'BIGINT' => 'NUMERIC', + 'REAL' => 'DOUBLE PRECISION', + 'FLOAT' => 'DOUBLE PRECISION' + ); + + /** + * NULL value representation in CREATE/ALTER TABLE statements + * + * @var string + */ + protected $_null = 'NULL'; + + // -------------------------------------------------------------------- + + /** + * Class constructor + * + * @param object &$db Database object + * @return void + */ + public function __construct(&$db) + { + parent::__construct($db); + + if (version_compare($this->db->version(), '9.0', '>')) + { + $this->create_table_if = 'CREATE TABLE IF NOT EXISTS'; + } + } + + // -------------------------------------------------------------------- + + /** + * ALTER TABLE + * + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * @return string|string[] + */ + protected function _alter_table($alter_type, $table, $field) + { + if (in_array($alter_type, array('DROP', 'ADD'), TRUE)) + { + return parent::_alter_table($alter_type, $table, $field); + } + + $sql = 'ALTER TABLE '.$this->db->escape_identifiers($table); + $sqls = array(); + for ($i = 0, $c = count($field); $i < $c; $i++) + { + if ($field[$i]['_literal'] !== FALSE) + { + return FALSE; + } + + if (version_compare($this->db->version(), '8', '>=') && isset($field[$i]['type'])) + { + $sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identifiers($field[$i]['name']) + .' TYPE '.$field[$i]['type'].$field[$i]['length']; + } + + if ( ! empty($field[$i]['default'])) + { + $sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identifiers($field[$i]['name']) + .' SET DEFAULT '.$field[$i]['default']; + } + + if (isset($field[$i]['null'])) + { + $sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identifiers($field[$i]['name']) + .($field[$i]['null'] === TRUE ? ' DROP NOT NULL' : ' SET NOT NULL'); + } + + if ( ! empty($field[$i]['new_name'])) + { + $sqls[] = $sql.' RENAME COLUMN '.$this->db->escape_identifiers($field[$i]['name']) + .' TO '.$this->db->escape_identifiers($field[$i]['new_name']); + } + + if ( ! empty($field[$i]['comment'])) + { + $sqls[] = 'COMMENT ON COLUMN ' + .$this->db->escape_identifiers($table).'.'.$this->db->escape_identifiers($field[$i]['name']) + .' IS '.$field[$i]['comment']; + } + } + + return $sqls; + } + + // -------------------------------------------------------------------- + + /** + * Field attribute TYPE + * + * Performs a data type mapping between different databases. + * + * @param array &$attributes + * @return void + */ + protected function _attr_type(&$attributes) + { + // Reset field lenghts for data types that don't support it + if (isset($attributes['CONSTRAINT']) && stripos($attributes['TYPE'], 'int') !== FALSE) + { + $attributes['CONSTRAINT'] = NULL; + } + + switch (strtoupper($attributes['TYPE'])) + { + case 'TINYINT': + $attributes['TYPE'] = 'SMALLINT'; + $attributes['UNSIGNED'] = FALSE; + return; + case 'MEDIUMINT': + $attributes['TYPE'] = 'INTEGER'; + $attributes['UNSIGNED'] = FALSE; + return; + default: return; + } + } + + // -------------------------------------------------------------------- + + /** + * Field attribute AUTO_INCREMENT + * + * @param array &$attributes + * @param array &$field + * @return void + */ + protected function _attr_auto_increment(&$attributes, &$field) + { + if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE) + { + $field['type'] = ($field['type'] === 'NUMERIC') + ? 'BIGSERIAL' + : 'SERIAL'; + } + } + +} diff --git a/src/system/database/drivers/pdo/subdrivers/pdo_sqlite_driver.php b/src/system/database/drivers/pdo/subdrivers/pdo_sqlite_driver.php new file mode 100644 index 00000000..62690139 --- /dev/null +++ b/src/system/database/drivers/pdo/subdrivers/pdo_sqlite_driver.php @@ -0,0 +1,219 @@ +dsn)) + { + $this->dsn = 'sqlite:'; + + if (empty($this->database) && empty($this->hostname)) + { + $this->database = ':memory:'; + } + + $this->database = empty($this->database) ? $this->hostname : $this->database; + } + } + + // -------------------------------------------------------------------- + + /** + * Show table query + * + * Generates a platform-specific query string so that the table names can be fetched + * + * @param bool $prefix_limit + * @return string + */ + protected function _list_tables($prefix_limit = FALSE) + { + $sql = 'SELECT "NAME" FROM "SQLITE_MASTER" WHERE "TYPE" = \'table\''; + + if ($prefix_limit === TRUE && $this->dbprefix !== '') + { + return $sql.' AND "NAME" LIKE \''.$this->escape_like_str($this->dbprefix)."%' " + .sprintf($this->_like_escape_str, $this->_like_escape_chr); + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Fetch Field Names + * + * @param string $table Table name + * @return array + */ + public function list_fields($table) + { + // Is there a cached result? + if (isset($this->data_cache['field_names'][$table])) + { + return $this->data_cache['field_names'][$table]; + } + + if (($result = $this->query('PRAGMA TABLE_INFO('.$this->protect_identifiers($table, TRUE, NULL, FALSE).')')) === FALSE) + { + return FALSE; + } + + $this->data_cache['field_names'][$table] = array(); + foreach ($result->result_array() as $row) + { + $this->data_cache['field_names'][$table][] = $row['name']; + } + + return $this->data_cache['field_names'][$table]; + } + + // -------------------------------------------------------------------- + + /** + * Returns an object with field data + * + * @param string $table + * @return array + */ + public function field_data($table) + { + if (($query = $this->query('PRAGMA TABLE_INFO('.$this->protect_identifiers($table, TRUE, NULL, FALSE).')')) === FALSE) + { + return FALSE; + } + + $query = $query->result_array(); + if (empty($query)) + { + return FALSE; + } + + $retval = array(); + for ($i = 0, $c = count($query); $i < $c; $i++) + { + $retval[$i] = new stdClass(); + $retval[$i]->name = $query[$i]['name']; + $retval[$i]->type = $query[$i]['type']; + $retval[$i]->max_length = NULL; + $retval[$i]->default = $query[$i]['dflt_value']; + $retval[$i]->primary_key = isset($query[$i]['pk']) ? (int) $query[$i]['pk'] : 0; + } + + return $retval; + } + + // -------------------------------------------------------------------- + + /** + * Replace statement + * + * @param string $table Table name + * @param array $keys INSERT keys + * @param array $values INSERT values + * @return string + */ + protected function _replace($table, $keys, $values) + { + return 'INSERT OR '.parent::_replace($table, $keys, $values); + } + + // -------------------------------------------------------------------- + + /** + * Truncate statement + * + * Generates a platform-specific truncate string from the supplied data + * + * If the database does not support the TRUNCATE statement, + * then this method maps to 'DELETE FROM table' + * + * @param string $table + * @return string + */ + protected function _truncate($table) + { + return 'DELETE FROM '.$table; + } + +} diff --git a/src/system/database/drivers/pdo/subdrivers/pdo_sqlite_forge.php b/src/system/database/drivers/pdo/subdrivers/pdo_sqlite_forge.php new file mode 100644 index 00000000..f6f9bb48 --- /dev/null +++ b/src/system/database/drivers/pdo/subdrivers/pdo_sqlite_forge.php @@ -0,0 +1,238 @@ +db->version(), '3.3', '<')) + { + $this->_create_table_if = FALSE; + $this->_drop_table_if = FALSE; + } + } + + // -------------------------------------------------------------------- + + /** + * Create database + * + * @param string $db_name (ignored) + * @return bool + */ + public function create_database($db_name = '') + { + // In SQLite, a database is created when you connect to the database. + // We'll return TRUE so that an error isn't generated + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Drop database + * + * @param string $db_name (ignored) + * @return bool + */ + public function drop_database($db_name = '') + { + // In SQLite, a database is dropped when we delete a file + if (file_exists($this->db->database)) + { + // We need to close the pseudo-connection first + $this->db->close(); + if ( ! @unlink($this->db->database)) + { + return $this->db->db_debug ? $this->db->display_error('db_unable_to_drop') : FALSE; + } + elseif ( ! empty($this->db->data_cache['db_names'])) + { + $key = array_search(strtolower($this->db->database), array_map('strtolower', $this->db->data_cache['db_names']), TRUE); + if ($key !== FALSE) + { + unset($this->db->data_cache['db_names'][$key]); + } + } + + return TRUE; + } + + return $this->db->db_debug ? $this->db->display_error('db_unable_to_drop') : FALSE; + } + + // -------------------------------------------------------------------- + + /** + * ALTER TABLE + * + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * @return string|string[] + */ + protected function _alter_table($alter_type, $table, $field) + { + if ($alter_type === 'DROP' OR $alter_type === 'CHANGE') + { + // drop_column(): + // BEGIN TRANSACTION; + // CREATE TEMPORARY TABLE t1_backup(a,b); + // INSERT INTO t1_backup SELECT a,b FROM t1; + // DROP TABLE t1; + // CREATE TABLE t1(a,b); + // INSERT INTO t1 SELECT a,b FROM t1_backup; + // DROP TABLE t1_backup; + // COMMIT; + + return FALSE; + } + + return parent::_alter_table($alter_type, $table, $field); + } + + // -------------------------------------------------------------------- + + /** + * Process column + * + * @param array $field + * @return string + */ + protected function _process_column($field) + { + return $this->db->escape_identifiers($field['name']) + .' '.$field['type'] + .$field['auto_increment'] + .$field['null'] + .$field['unique'] + .$field['default']; + } + + // -------------------------------------------------------------------- + + /** + * Field attribute TYPE + * + * Performs a data type mapping between different databases. + * + * @param array &$attributes + * @return void + */ + protected function _attr_type(&$attributes) + { + switch (strtoupper($attributes['TYPE'])) + { + case 'ENUM': + case 'SET': + $attributes['TYPE'] = 'TEXT'; + return; + default: return; + } + } + + // -------------------------------------------------------------------- + + /** + * Field attribute AUTO_INCREMENT + * + * @param array &$attributes + * @param array &$field + * @return void + */ + protected function _attr_auto_increment(&$attributes, &$field) + { + if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE && stripos($field['type'], 'int') !== FALSE) + { + $field['type'] = 'INTEGER PRIMARY KEY'; + $field['default'] = ''; + $field['null'] = ''; + $field['unique'] = ''; + $field['auto_increment'] = ' AUTOINCREMENT'; + + $this->primary_keys = array(); + } + } + +} diff --git a/src/system/database/drivers/pdo/subdrivers/pdo_sqlsrv_driver.php b/src/system/database/drivers/pdo/subdrivers/pdo_sqlsrv_driver.php new file mode 100644 index 00000000..dfccb7d6 --- /dev/null +++ b/src/system/database/drivers/pdo/subdrivers/pdo_sqlsrv_driver.php @@ -0,0 +1,369 @@ +dsn)) + { + $this->dsn = 'sqlsrv:Server='.(empty($this->hostname) ? '127.0.0.1' : $this->hostname); + + empty($this->port) OR $this->dsn .= ','.$this->port; + empty($this->database) OR $this->dsn .= ';Database='.$this->database; + + // Some custom options + + if (isset($this->QuotedId)) + { + $this->dsn .= ';QuotedId='.$this->QuotedId; + $this->_quoted_identifier = (bool) $this->QuotedId; + } + + if (isset($this->ConnectionPooling)) + { + $this->dsn .= ';ConnectionPooling='.$this->ConnectionPooling; + } + + if ($this->encrypt === TRUE) + { + $this->dsn .= ';Encrypt=1'; + } + + if (isset($this->TraceOn)) + { + $this->dsn .= ';TraceOn='.$this->TraceOn; + } + + if (isset($this->TrustServerCertificate)) + { + $this->dsn .= ';TrustServerCertificate='.$this->TrustServerCertificate; + } + + empty($this->APP) OR $this->dsn .= ';APP='.$this->APP; + empty($this->Failover_Partner) OR $this->dsn .= ';Failover_Partner='.$this->Failover_Partner; + empty($this->LoginTimeout) OR $this->dsn .= ';LoginTimeout='.$this->LoginTimeout; + empty($this->MultipleActiveResultSets) OR $this->dsn .= ';MultipleActiveResultSets='.$this->MultipleActiveResultSets; + empty($this->TraceFile) OR $this->dsn .= ';TraceFile='.$this->TraceFile; + empty($this->WSID) OR $this->dsn .= ';WSID='.$this->WSID; + } + elseif (preg_match('/QuotedId=(0|1)/', $this->dsn, $match)) + { + $this->_quoted_identifier = (bool) $match[1]; + } + } + + // -------------------------------------------------------------------- + + /** + * Database connection + * + * @param bool $persistent + * @return object + */ + public function db_connect($persistent = FALSE) + { + if ( ! empty($this->char_set) && preg_match('/utf[^8]*8/i', $this->char_set)) + { + $this->options[PDO::SQLSRV_ENCODING_UTF8] = 1; + } + + $this->conn_id = parent::db_connect($persistent); + + if ( ! is_object($this->conn_id) OR is_bool($this->_quoted_identifier)) + { + return $this->conn_id; + } + + // Determine how identifiers are escaped + $query = $this->query('SELECT CASE WHEN (@@OPTIONS | 256) = @@OPTIONS THEN 1 ELSE 0 END AS qi'); + $query = $query->row_array(); + $this->_quoted_identifier = empty($query) ? FALSE : (bool) $query['qi']; + $this->_escape_char = ($this->_quoted_identifier) ? '"' : array('[', ']'); + + return $this->conn_id; + } + + // -------------------------------------------------------------------- + + /** + * Show table query + * + * Generates a platform-specific query string so that the table names can be fetched + * + * @param bool $prefix_limit + * @return string + */ + protected function _list_tables($prefix_limit = FALSE) + { + $sql = 'SELECT '.$this->escape_identifiers('name') + .' FROM '.$this->escape_identifiers('sysobjects') + .' WHERE '.$this->escape_identifiers('type')." = 'U'"; + + if ($prefix_limit === TRUE && $this->dbprefix !== '') + { + $sql .= ' AND '.$this->escape_identifiers('name')." LIKE '".$this->escape_like_str($this->dbprefix)."%' " + .sprintf($this->_like_escape_str, $this->_like_escape_chr); + } + + return $sql.' ORDER BY '.$this->escape_identifiers('name'); + } + + // -------------------------------------------------------------------- + + /** + * Show column query + * + * Generates a platform-specific query string so that the column names can be fetched + * + * @param string $table + * @return string + */ + protected function _list_columns($table = '') + { + return 'SELECT COLUMN_NAME + FROM INFORMATION_SCHEMA.Columns + WHERE UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table)); + } + + // -------------------------------------------------------------------- + + /** + * Returns an object with field data + * + * @param string $table + * @return array + */ + public function field_data($table) + { + $sql = 'SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, COLUMN_DEFAULT + FROM INFORMATION_SCHEMA.Columns + WHERE UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table)); + + if (($query = $this->query($sql)) === FALSE) + { + return FALSE; + } + $query = $query->result_object(); + + $retval = array(); + for ($i = 0, $c = count($query); $i < $c; $i++) + { + $retval[$i] = new stdClass(); + $retval[$i]->name = $query[$i]->COLUMN_NAME; + $retval[$i]->type = $query[$i]->DATA_TYPE; + $retval[$i]->max_length = ($query[$i]->CHARACTER_MAXIMUM_LENGTH > 0) ? $query[$i]->CHARACTER_MAXIMUM_LENGTH : $query[$i]->NUMERIC_PRECISION; + $retval[$i]->default = $query[$i]->COLUMN_DEFAULT; + } + + return $retval; + } + + // -------------------------------------------------------------------- + + /** + * Update statement + * + * Generates a platform-specific update string from the supplied data + * + * @param string $table + * @param array $values + * @return string + */ + protected function _update($table, $values) + { + $this->qb_limit = FALSE; + $this->qb_orderby = array(); + return parent::_update($table, $values); + } + + // -------------------------------------------------------------------- + + /** + * Delete statement + * + * Generates a platform-specific delete string from the supplied data + * + * @param string $table + * @return string + */ + protected function _delete($table) + { + if ($this->qb_limit) + { + return 'WITH ci_delete AS (SELECT TOP '.$this->qb_limit.' * FROM '.$table.$this->_compile_wh('qb_where').') DELETE FROM ci_delete'; + } + + return parent::_delete($table); + } + + // -------------------------------------------------------------------- + + /** + * LIMIT + * + * Generates a platform-specific LIMIT clause + * + * @param string $sql SQL Query + * @return string + */ + protected function _limit($sql) + { + // As of SQL Server 2012 (11.0.*) OFFSET is supported + if (version_compare($this->version(), '11', '>=')) + { + // SQL Server OFFSET-FETCH can be used only with the ORDER BY clause + empty($this->qb_orderby) && $sql .= ' ORDER BY 1'; + + return $sql.' OFFSET '.(int) $this->qb_offset.' ROWS FETCH NEXT '.$this->qb_limit.' ROWS ONLY'; + } + + $limit = $this->qb_offset + $this->qb_limit; + + // An ORDER BY clause is required for ROW_NUMBER() to work + if ($this->qb_offset && ! empty($this->qb_orderby)) + { + $orderby = $this->_compile_order_by(); + + // We have to strip the ORDER BY clause + $sql = trim(substr($sql, 0, strrpos($sql, $orderby))); + + // Get the fields to select from our subquery, so that we can avoid CI_rownum appearing in the actual results + if (count($this->qb_select) === 0) + { + $select = '*'; // Inevitable + } + else + { + // Use only field names and their aliases, everything else is out of our scope. + $select = array(); + $field_regexp = ($this->_quoted_identifier) + ? '("[^\"]+")' : '(\[[^\]]+\])'; + for ($i = 0, $c = count($this->qb_select); $i < $c; $i++) + { + $select[] = preg_match('/(?:\s|\.)'.$field_regexp.'$/i', $this->qb_select[$i], $m) + ? $m[1] : $this->qb_select[$i]; + } + $select = implode(', ', $select); + } + + return 'SELECT '.$select." FROM (\n\n" + .preg_replace('/^(SELECT( DISTINCT)?)/i', '\\1 ROW_NUMBER() OVER('.trim($orderby).') AS '.$this->escape_identifiers('CI_rownum').', ', $sql) + ."\n\n) ".$this->escape_identifiers('CI_subquery') + ."\nWHERE ".$this->escape_identifiers('CI_rownum').' BETWEEN '.($this->qb_offset + 1).' AND '.$limit; + } + + return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.$limit.' ', $sql); + } + + // -------------------------------------------------------------------- + + /** + * Insert batch statement + * + * Generates a platform-specific insert string from the supplied data. + * + * @param string $table Table name + * @param array $keys INSERT keys + * @param array $values INSERT values + * @return string|bool + */ + protected function _insert_batch($table, $keys, $values) + { + // Multiple-value inserts are only supported as of SQL Server 2008 + if (version_compare($this->version(), '10', '>=')) + { + return parent::_insert_batch($table, $keys, $values); + } + + return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE; + } + +} diff --git a/src/system/database/drivers/pdo/subdrivers/pdo_sqlsrv_forge.php b/src/system/database/drivers/pdo/subdrivers/pdo_sqlsrv_forge.php new file mode 100644 index 00000000..602f895f --- /dev/null +++ b/src/system/database/drivers/pdo/subdrivers/pdo_sqlsrv_forge.php @@ -0,0 +1,144 @@ + 'SMALLINT', + 'SMALLINT' => 'INT', + 'INT' => 'BIGINT', + 'REAL' => 'FLOAT' + ); + + // -------------------------------------------------------------------- + + /** + * ALTER TABLE + * + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * @return string|string[] + */ + protected function _alter_table($alter_type, $table, $field) + { + if (in_array($alter_type, array('ADD', 'DROP'), TRUE)) + { + return parent::_alter_table($alter_type, $table, $field); + } + + $sql = 'ALTER TABLE '.$this->db->escape_identifiers($table).' ALTER COLUMN '; + $sqls = array(); + for ($i = 0, $c = count($field); $i < $c; $i++) + { + $sqls[] = $sql.$this->_process_column($field[$i]); + } + + return $sqls; + } + + // -------------------------------------------------------------------- + + /** + * Field attribute TYPE + * + * Performs a data type mapping between different databases. + * + * @param array &$attributes + * @return void + */ + protected function _attr_type(&$attributes) + { + switch (strtoupper($attributes['TYPE'])) + { + case 'MEDIUMINT': + $attributes['TYPE'] = 'INTEGER'; + $attributes['UNSIGNED'] = FALSE; + return; + case 'INTEGER': + $attributes['TYPE'] = 'INT'; + return; + default: return; + } + } + + // -------------------------------------------------------------------- + + /** + * Field attribute AUTO_INCREMENT + * + * @param array &$attributes + * @param array &$field + * @return void + */ + protected function _attr_auto_increment(&$attributes, &$field) + { + if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE && stripos($field['type'], 'int') !== FALSE) + { + $field['auto_increment'] = ' IDENTITY(1,1)'; + } + } + +} diff --git a/src/system/database/drivers/postgre/index.html b/src/system/database/drivers/postgre/index.html index c942a79c..b702fbc3 100644 --- a/src/system/database/drivers/postgre/index.html +++ b/src/system/database/drivers/postgre/index.html @@ -1,3 +1,4 @@ + 403 Forbidden @@ -7,4 +8,4 @@

Directory access is forbidden.

- \ No newline at end of file + diff --git a/src/system/database/drivers/postgre/postgre_driver.php b/src/system/database/drivers/postgre/postgre_driver.php index 9e4843f9..58d44518 100644 --- a/src/system/database/drivers/postgre/postgre_driver.php +++ b/src/system/database/drivers/postgre/postgre_driver.php @@ -1,103 +1,172 @@ - 'host', - 'port' => 'port', - 'database' => 'dbname', - 'username' => 'user', - 'password' => 'password' - ); + public $dbdriver = 'postgre'; - $connect_string = ""; - foreach ($components as $key => $val) + /** + * Database schema + * + * @var string + */ + public $schema = 'public'; + + // -------------------------------------------------------------------- + + /** + * ORDER BY random keyword + * + * @var array + */ + protected $_random_keyword = array('RANDOM()', 'RANDOM()'); + + // -------------------------------------------------------------------- + + /** + * Class constructor + * + * Creates a DSN string to be used for db_connect() and db_pconnect() + * + * @param array $params + * @return void + */ + public function __construct($params) + { + parent::__construct($params); + + if ( ! empty($this->dsn)) { - if (isset($this->$key) && $this->$key != '') + return; + } + + $this->dsn === '' OR $this->dsn = ''; + + if (strpos($this->hostname, '/') !== FALSE) + { + // If UNIX sockets are used, we shouldn't set a port + $this->port = ''; + } + + $this->hostname === '' OR $this->dsn = 'host='.$this->hostname.' '; + + if ( ! empty($this->port) && ctype_digit($this->port)) + { + $this->dsn .= 'port='.$this->port.' '; + } + + if ($this->username !== '') + { + $this->dsn .= 'user='.$this->username.' '; + + /* An empty password is valid! + * + * $db['password'] = NULL must be done in order to ignore it. + */ + $this->password === NULL OR $this->dsn .= "password='".$this->password."' "; + } + + $this->database === '' OR $this->dsn .= 'dbname='.$this->database.' '; + + /* We don't have these options as elements in our standard configuration + * array, but they might be set by parse_url() if the configuration was + * provided via string. Example: + * + * postgre://username:password@localhost:5432/database?connect_timeout=5&sslmode=1 + */ + foreach (array('connect_timeout', 'options', 'sslmode', 'service') as $key) + { + if (isset($this->$key) && is_string($this->key) && $this->key !== '') { - $connect_string .= " $val=".$this->$key; + $this->dsn .= $key."='".$this->key."' "; } } - return trim($connect_string); + + $this->dsn = rtrim($this->dsn); } // -------------------------------------------------------------------- /** - * Non-persistent database connection + * Database connection * - * @access private called by the base class + * @param bool $persistent * @return resource */ - function db_connect() + public function db_connect($persistent = FALSE) { - return @pg_connect($this->_connect_string()); - } + $this->conn_id = ($persistent === TRUE) + ? pg_pconnect($this->dsn) + : pg_connect($this->dsn); - // -------------------------------------------------------------------- + if ($this->conn_id !== FALSE) + { + if ($persistent === TRUE + && pg_connection_status($this->conn_id) === PGSQL_CONNECTION_BAD + && pg_ping($this->conn_id) === FALSE + ) + { + return FALSE; + } - /** - * Persistent database connection - * - * @access private called by the base class - * @return resource - */ - function db_pconnect() - { - return @pg_pconnect($this->_connect_string()); + empty($this->schema) OR $this->simple_query('SET search_path TO '.$this->schema.',public'); + } + + return $this->conn_id; } // -------------------------------------------------------------------- @@ -108,10 +177,9 @@ class CI_DB_postgre_driver extends CI_DB { * 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() + public function reconnect() { if (pg_ping($this->conn_id) === FALSE) { @@ -121,45 +189,44 @@ class CI_DB_postgre_driver extends CI_DB { // -------------------------------------------------------------------- - /** - * 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 + * @param string $charset + * @return bool */ - function db_set_charset($charset, $collation) + protected function _db_set_charset($charset) { - // @todo - add support if needed - return TRUE; + return (pg_set_client_encoding($this->conn_id, $charset) === 0); } // -------------------------------------------------------------------- /** - * Version number query string + * Database version number * - * @access public * @return string */ - function _version() + public function version() { - return "SELECT version() AS ver"; + if (isset($this->data_cache['version'])) + { + return $this->data_cache['version']; + } + + if ( ! $this->conn_id OR ($pg_version = pg_version($this->conn_id)) === FALSE) + { + return FALSE; + } + + /* If PHP was compiled with PostgreSQL lib versions earlier + * than 7.4, pg_version() won't return the server version + * and so we'll have to fall back to running a query in + * order to get it. + */ + return isset($pg_version['server']) + ? $this->data_cache['version'] = $pg_version['server'] + : parent::version(); } // -------------------------------------------------------------------- @@ -167,30 +234,12 @@ class CI_DB_postgre_driver extends CI_DB { /** * Execute the query * - * @access private called by the base class - * @param string an SQL query + * @param string $sql an SQL query * @return resource */ - function _execute($sql) + protected 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; + return pg_query($this->conn_id, $sql); } // -------------------------------------------------------------------- @@ -198,28 +247,11 @@ class CI_DB_postgre_driver extends CI_DB { /** * Begin Transaction * - * @access public * @return bool */ - function trans_begin($test_mode = FALSE) + protected function _trans_begin() { - 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"); + return (bool) pg_query($this->conn_id, 'BEGIN'); } // -------------------------------------------------------------------- @@ -227,23 +259,11 @@ class CI_DB_postgre_driver extends CI_DB { /** * Commit Transaction * - * @access public * @return bool */ - function trans_commit() + protected 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"); + return (bool) pg_query($this->conn_id, 'COMMIT'); } // -------------------------------------------------------------------- @@ -251,58 +271,66 @@ class CI_DB_postgre_driver extends CI_DB { /** * Rollback Transaction * - * @access public * @return bool */ - function trans_rollback() + protected 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"); + return (bool) pg_query($this->conn_id, 'ROLLBACK'); } // -------------------------------------------------------------------- /** - * Escape String + * Determines if a query is a "write" type. + * + * @param string An SQL query string + * @return bool + */ + public function is_write_type($sql) + { + if (preg_match('#^(INSERT|UPDATE).*RETURNING\s.+(\,\s?.+)*$#i', $sql)) + { + return FALSE; + } + + return parent::is_write_type($sql); + } + + // -------------------------------------------------------------------- + + /** + * Platform-dependant string escape * - * @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) + protected function _escape_str($str) { - if (is_array($str)) - { - foreach ($str as $key => $val) - { - $str[$key] = $this->escape_str($val, $like); - } + return pg_escape_string($this->conn_id, $str); + } - return $str; + // -------------------------------------------------------------------- + + /** + * "Smart" Escape String + * + * Escapes data based on type + * + * @param string $str + * @return mixed + */ + public function escape($str) + { + if (is_php('5.4.4') && (is_string($str) OR (is_object($str) && method_exists($str, '__toString')))) + { + return pg_escape_literal($this->conn_id, $str); + } + elseif (is_bool($str)) + { + return ($str) ? 'TRUE' : 'FALSE'; } - $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; + return parent::escape($str); } // -------------------------------------------------------------------- @@ -310,12 +338,11 @@ class CI_DB_postgre_driver extends CI_DB { /** * Affected Rows * - * @access public - * @return integer + * @return int */ - function affected_rows() + public function affected_rows() { - return @pg_affected_rows($this->result_id); + return pg_affected_rows($this->result_id); } // -------------------------------------------------------------------- @@ -323,71 +350,45 @@ class CI_DB_postgre_driver extends CI_DB { /** * Insert ID * - * @access public - * @return integer + * @return string */ - function insert_id() + public function insert_id() { - $v = $this->_version(); - $v = $v['server']; + $v = pg_version($this->conn_id); + $v = isset($v['server']) ? $v['server'] : 0; // 'server' key is only available since PosgreSQL 7.4 - $table = func_num_args() > 0 ? func_get_arg(0) : NULL; - $column = func_num_args() > 1 ? func_get_arg(1) : NULL; + $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') + if ($table === NULL && $v >= '8.1') { - $sql='SELECT LASTVAL() as ins_id'; + $sql = 'SELECT LASTVAL() AS ins_id'; } - elseif ($table != NULL && $column != NULL && $v >= '8.0') + elseif ($table !== NULL) { - $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); + if ($column !== NULL && $v >= '8.0') + { + $sql = 'SELECT pg_get_serial_sequence(\''.$table."', '".$column."') AS seq"; + $query = $this->query($sql); + $query = $query->row(); + $seq = $query->seq; + } + else + { + // seq_name passed in table parameter + $seq = $table; + } + + $sql = 'SELECT CURRVAL(\''.$seq."') AS ins_id"; } 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; + $query = $query->row(); + return (int) $query->ins_id; } // -------------------------------------------------------------------- @@ -397,17 +398,18 @@ class CI_DB_postgre_driver extends CI_DB { * * Generates a platform-specific query string so that the table names can be fetched * - * @access private - * @param boolean + * @param bool $prefix_limit * @return string */ - function _list_tables($prefix_limit = FALSE) + protected function _list_tables($prefix_limit = FALSE) { - $sql = "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'"; + $sql = 'SELECT "table_name" FROM "information_schema"."tables" WHERE "table_schema" = \''.$this->schema."'"; - if ($prefix_limit !== FALSE AND $this->dbprefix != '') + if ($prefix_limit !== FALSE && $this->dbprefix !== '') { - $sql .= " AND table_name LIKE '".$this->escape_like_str($this->dbprefix)."%' ".sprintf($this->_like_escape_str, $this->_like_escape_chr); + return $sql.' AND "table_name" LIKE \'' + .$this->escape_like_str($this->dbprefix)."%' " + .sprintf($this->_like_escape_str, $this->_like_escape_chr); } return $sql; @@ -416,159 +418,101 @@ class CI_DB_postgre_driver extends CI_DB { // -------------------------------------------------------------------- /** - * Show column query + * List column query * * Generates a platform-specific query string so that the column names can be fetched * - * @access public - * @param string the table name + * @param string $table * @return string */ - function _list_columns($table = '') + protected function _list_columns($table = '') { - return "SELECT column_name FROM information_schema.columns WHERE table_name ='".$table."'"; + return 'SELECT "column_name" + FROM "information_schema"."columns" + WHERE LOWER("table_name") = '.$this->escape(strtolower($table)); } // -------------------------------------------------------------------- /** - * Field data query + * Returns an object with field data * - * Generates a platform-specific query so that the column data can be retrieved + * @param string $table + * @return array + */ + public function field_data($table) + { + $sql = 'SELECT "column_name", "data_type", "character_maximum_length", "numeric_precision", "column_default" + FROM "information_schema"."columns" + WHERE LOWER("table_name") = '.$this->escape(strtolower($table)); + + if (($query = $this->query($sql)) === FALSE) + { + return FALSE; + } + $query = $query->result_object(); + + $retval = array(); + for ($i = 0, $c = count($query); $i < $c; $i++) + { + $retval[$i] = new stdClass(); + $retval[$i]->name = $query[$i]->column_name; + $retval[$i]->type = $query[$i]->data_type; + $retval[$i]->max_length = ($query[$i]->character_maximum_length > 0) ? $query[$i]->character_maximum_length : $query[$i]->numeric_precision; + $retval[$i]->default = $query[$i]->column_default; + } + + return $retval; + } + + // -------------------------------------------------------------------- + + /** + * Error * - * @access public - * @param string the table name + * Returns an array containing code and message of the last + * database error that has occured. + * + * @return array + */ + public function error() + { + return array('code' => '', 'message' => pg_last_error($this->conn_id)); + } + + // -------------------------------------------------------------------- + + /** + * ORDER BY + * + * @param string $orderby + * @param string $direction ASC, DESC or RANDOM + * @param bool $escape * @return object */ - function _field_data($table) + public function order_by($orderby, $direction = '', $escape = NULL) { - 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 == '') + $direction = strtoupper(trim($direction)); + if ($direction === 'RANDOM') { - return $item; - } - - foreach ($this->_reserved_identifiers as $id) - { - if (strpos($item, '.'.$id) !== FALSE) + if ( ! is_float($orderby) && ctype_digit((string) $orderby)) { - $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); + $orderby = ($orderby > 1) + ? (float) '0.'.$orderby + : (float) $orderby; } + + if (is_float($orderby)) + { + $this->simple_query('SET SEED '.$orderby); + } + + $orderby = $this->_random_keyword[0]; + $direction = ''; + $escape = FALSE; } - 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); + return parent::order_by($orderby, $direction, $escape); } // -------------------------------------------------------------------- @@ -578,50 +522,56 @@ class CI_DB_postgre_driver extends CI_DB { * * 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 + * @param string $table + * @param array $values * @return string */ - function _update($table, $values, $where, $orderby = array(), $limit = FALSE) + protected function _update($table, $values) { - 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; + $this->qb_limit = FALSE; + $this->qb_orderby = array(); + return parent::_update($table, $values); } // -------------------------------------------------------------------- /** - * Truncate statement + * Update_Batch 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" + * Generates a platform-specific batch update string from the supplied data * - * @access public - * @param string the table name + * @param string $table Table name + * @param array $values Update data + * @param string $index WHERE key * @return string */ - function _truncate($table) + protected function _update_batch($table, $values, $index) { - return "TRUNCATE ".$table; + $ids = array(); + foreach ($values as $key => $val) + { + $ids[] = $val[$index]; + + foreach (array_keys($val) as $field) + { + if ($field !== $index) + { + $final[$field][] = 'WHEN '.$val[$index].' THEN '.$val[$field]; + } + } + } + + $cases = ''; + foreach ($final as $k => $v) + { + $cases .= $k.' = (CASE '.$index."\n" + .implode("\n", $v)."\n" + .'ELSE '.$k.' END), '; + } + + $this->where($index.' IN('.implode(',', $ids).')', NULL, FALSE); + + return 'UPDATE '.$table.' SET '.substr($cases, 0, -2).$this->_compile_wh('qb_where'); } // -------------------------------------------------------------------- @@ -631,55 +581,28 @@ class CI_DB_postgre_driver extends CI_DB { * * 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 + * @param string $table * @return string */ - function _delete($table, $where = array(), $like = array(), $limit = FALSE) + protected function _delete($table) { - $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; + $this->qb_limit = FALSE; + return parent::_delete($table); } // -------------------------------------------------------------------- + /** - * Limit string + * LIMIT * * 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 + * @param string $sql SQL Query * @return string */ - function _limit($sql, $limit, $offset) + protected function _limit($sql) { - $sql .= "LIMIT ".$limit; - - if ($offset > 0) - { - $sql .= " OFFSET ".$offset; - } - - return $sql; + return $sql.' LIMIT '.$this->qb_limit.($this->qb_offset ? ' OFFSET '.$this->qb_offset : ''); } // -------------------------------------------------------------------- @@ -687,18 +610,11 @@ class CI_DB_postgre_driver extends CI_DB { /** * Close DB Connection * - * @access public - * @param resource * @return void */ - function _close($conn_id) + protected function _close() { - @pg_close($conn_id); + pg_close($this->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/src/system/database/drivers/postgre/postgre_forge.php b/src/system/database/drivers/postgre/postgre_forge.php index 9b72ed5c..8d985ba7 100644 --- a/src/system/database/drivers/postgre/postgre_forge.php +++ b/src/system/database/drivers/postgre/postgre_forge.php @@ -1,300 +1,205 @@ - 'INTEGER', + 'SMALLINT' => 'INTEGER', + 'INT' => 'BIGINT', + 'INT4' => 'BIGINT', + 'INTEGER' => 'BIGINT', + 'INT8' => 'NUMERIC', + 'BIGINT' => 'NUMERIC', + 'REAL' => 'DOUBLE PRECISION', + 'FLOAT' => 'DOUBLE PRECISION' + ); + + /** + * NULL value representation in CREATE/ALTER TABLE statements + * + * @var string + */ + protected $_null = 'NULL'; + + // -------------------------------------------------------------------- + + /** + * Class constructor + * + * @param object &$db Database object + * @return void + */ + public function __construct(&$db) { - return "CREATE DATABASE ".$name; + parent::__construct($db); + + if (version_compare($this->db->version(), '9.0', '>')) + { + $this->create_table_if = 'CREATE TABLE IF NOT EXISTS'; + } } // -------------------------------------------------------------------- /** - * Drop database + * ALTER TABLE * - * @access private - * @param string the database name - * @return bool + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * @return string|string[] */ - function _drop_database($name) + protected function _alter_table($alter_type, $table, $field) + { + if (in_array($alter_type, array('DROP', 'ADD'), TRUE)) + { + return parent::_alter_table($alter_type, $table, $field); + } + + $sql = 'ALTER TABLE '.$this->db->escape_identifiers($table); + $sqls = array(); + for ($i = 0, $c = count($field); $i < $c; $i++) + { + if ($field[$i]['_literal'] !== FALSE) + { + return FALSE; + } + + if (version_compare($this->db->version(), '8', '>=') && isset($field[$i]['type'])) + { + $sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identifiers($field[$i]['name']) + .' TYPE '.$field[$i]['type'].$field[$i]['length']; + } + + if ( ! empty($field[$i]['default'])) + { + $sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identifiers($field[$i]['name']) + .' SET DEFAULT '.$field[$i]['default']; + } + + if (isset($field[$i]['null'])) + { + $sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identifiers($field[$i]['name']) + .($field[$i]['null'] === TRUE ? ' DROP NOT NULL' : ' SET NOT NULL'); + } + + if ( ! empty($field[$i]['new_name'])) + { + $sqls[] = $sql.' RENAME COLUMN '.$this->db->escape_identifiers($field[$i]['name']) + .' TO '.$this->db->escape_identifiers($field[$i]['new_name']); + } + + if ( ! empty($field[$i]['comment'])) + { + $sqls[] = 'COMMENT ON COLUMN ' + .$this->db->escape_identifiers($table).'.'.$this->db->escape_identifiers($field[$i]['name']) + .' IS '.$field[$i]['comment']; + } + } + + return $sqls; + } + + // -------------------------------------------------------------------- + + /** + * Field attribute TYPE + * + * Performs a data type mapping between different databases. + * + * @param array &$attributes + * @return void + */ + protected function _attr_type(&$attributes) { - return "DROP DATABASE ".$name; + // Reset field lenghts for data types that don't support it + if (isset($attributes['CONSTRAINT']) && stripos($attributes['TYPE'], 'int') !== FALSE) + { + $attributes['CONSTRAINT'] = NULL; + } + + switch (strtoupper($attributes['TYPE'])) + { + case 'TINYINT': + $attributes['TYPE'] = 'SMALLINT'; + $attributes['UNSIGNED'] = FALSE; + return; + case 'MEDIUMINT': + $attributes['TYPE'] = 'INTEGER'; + $attributes['UNSIGNED'] = FALSE; + return; + default: return; + } } // -------------------------------------------------------------------- /** - * Create Table + * Field attribute AUTO_INCREMENT * - * @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 + * @param array &$attributes + * @param array &$field + * @return void */ - function _create_table($table, $fields, $primary_keys, $keys, $if_not_exists) + protected function _attr_auto_increment(&$attributes, &$field) { - $sql = 'CREATE TABLE '; - - if ($if_not_exists === TRUE) + if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE) { - if ($this->db->table_exists($table)) - { - return "SELECT * FROM $table"; // Needs to return innocous but valid SQL statement - } + $field['type'] = ($field['type'] === 'NUMERIC') + ? 'BIGSERIAL' + : 'SERIAL'; } - - $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/src/system/database/drivers/postgre/postgre_result.php b/src/system/database/drivers/postgre/postgre_result.php index 8e1680d5..354bb08d 100644 --- a/src/system/database/drivers/postgre/postgre_result.php +++ b/src/system/database/drivers/postgre/postgre_result.php @@ -1,41 +1,65 @@ -result_id); + return is_int($this->num_rows) + ? $this->num_rows + : $this->num_rows = pg_num_rows($this->result_id); } // -------------------------------------------------------------------- @@ -43,12 +67,11 @@ class CI_DB_postgre_result extends CI_DB_result { /** * Number of fields in the result set * - * @access public - * @return integer + * @return int */ - function num_fields() + public function num_fields() { - return @pg_num_fields($this->result_id); + return pg_num_fields($this->result_id); } // -------------------------------------------------------------------- @@ -58,13 +81,12 @@ class CI_DB_postgre_result extends CI_DB_result { * * Generates an array of column names * - * @access public * @return array */ - function list_fields() + public function list_fields() { $field_names = array(); - for ($i = 0; $i < $this->num_fields(); $i++) + for ($i = 0, $c = $this->num_fields(); $i < $c; $i++) { $field_names[] = pg_field_name($this->result_id, $i); } @@ -79,22 +101,17 @@ class CI_DB_postgre_result extends CI_DB_result { * * Generates an array of objects containing field meta-data * - * @access public * @return array */ - function field_data() + public function field_data() { $retval = array(); - for ($i = 0; $i < $this->num_fields(); $i++) + for ($i = 0, $c = $this->num_fields(); $i < $c; $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; + $retval[$i] = new stdClass(); + $retval[$i]->name = pg_field_name($this->result_id, $i); + $retval[$i]->type = pg_field_type($this->result_id, $i); + $retval[$i]->max_length = pg_field_size($this->result_id, $i); } return $retval; @@ -105,9 +122,9 @@ class CI_DB_postgre_result extends CI_DB_result { /** * Free the result * - * @return null + * @return void */ - function free_result() + public function free_result() { if (is_resource($this->result_id)) { @@ -121,14 +138,14 @@ class CI_DB_postgre_result extends CI_DB_result { /** * Data Seek * - * Moves the internal pointer to the desired offset. We call + * Moves the internal pointer to the desired offset. We call * this internally before fetching results to make sure the - * result set starts at zero + * result set starts at zero. * - * @access private - * @return array + * @param int $n + * @return bool */ - function _data_seek($n = 0) + public function data_seek($n = 0) { return pg_result_seek($this->result_id, $n); } @@ -140,10 +157,9 @@ class CI_DB_postgre_result extends CI_DB_result { * * Returns the result set as an array * - * @access private * @return array */ - function _fetch_assoc() + protected function _fetch_assoc() { return pg_fetch_assoc($this->result_id); } @@ -155,16 +171,12 @@ class CI_DB_postgre_result extends CI_DB_result { * * Returns the result set as an object * - * @access private + * @param string $class_name * @return object */ - function _fetch_object() + protected function _fetch_object($class_name = 'stdClass') { - return pg_fetch_object($this->result_id); + return pg_fetch_object($this->result_id, NULL, $class_name); } } - - -/* End of file postgre_result.php */ -/* Location: ./system/database/drivers/postgre/postgre_result.php */ \ No newline at end of file diff --git a/src/system/database/drivers/postgre/postgre_utility.php b/src/system/database/drivers/postgre/postgre_utility.php index e84333f7..bb5e6e04 100644 --- a/src/system/database/drivers/postgre/postgre_utility.php +++ b/src/system/database/drivers/postgre/postgre_utility.php @@ -1,89 +1,78 @@ -db->display_error('db_unsuported_feature'); + return $this->db->display_error('db_unsupported_feature'); } } - - -/* End of file postgre_utility.php */ -/* Location: ./system/database/drivers/postgre/postgre_utility.php */ \ No newline at end of file diff --git a/src/system/database/drivers/sqlite/index.html b/src/system/database/drivers/sqlite/index.html index c942a79c..b702fbc3 100644 --- a/src/system/database/drivers/sqlite/index.html +++ b/src/system/database/drivers/sqlite/index.html @@ -1,3 +1,4 @@ + 403 Forbidden @@ -7,4 +8,4 @@

Directory access is forbidden.

- \ No newline at end of file + diff --git a/src/system/database/drivers/sqlite/sqlite_driver.php b/src/system/database/drivers/sqlite/sqlite_driver.php index 26230138..16b8c29c 100644 --- a/src/system/database/drivers/sqlite/sqlite_driver.php +++ b/src/system/database/drivers/sqlite/sqlite_driver.php @@ -1,74 +1,89 @@ -database, FILE_WRITE_MODE, $error)) - { - log_message('error', $error); + $error = NULL; + $conn_id = ($persistent === TRUE) + ? sqlite_popen($this->database, 0666, $error) + : sqlite_open($this->database, 0666, $error); - if ($this->db_debug) - { - $this->display_error($error, '', TRUE); - } - - return FALSE; - } + isset($error) && log_message('error', $error); return $conn_id; } @@ -76,84 +91,15 @@ class CI_DB_sqlite_driver extends CI_DB { // -------------------------------------------------------------------- /** - * Persistent database connection + * Database version number * - * @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() + public function version() { - return sqlite_libversion(); + return isset($this->data_cache['version']) + ? $this->data_cache['version'] + : $this->data_cache['version'] = sqlite_libversion(); } // -------------------------------------------------------------------- @@ -161,30 +107,14 @@ class CI_DB_sqlite_driver extends CI_DB { /** * Execute the query * - * @access private called by the base class - * @param string an SQL query + * @param string $sql an SQL query * @return resource */ - function _execute($sql) + protected 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; + return $this->is_write_type($sql) + ? sqlite_exec($this->conn_id, $sql) + : sqlite_query($this->conn_id, $sql); } // -------------------------------------------------------------------- @@ -192,29 +122,11 @@ class CI_DB_sqlite_driver extends CI_DB { /** * Begin Transaction * - * @access public * @return bool */ - function trans_begin($test_mode = FALSE) + protected function _trans_begin() { - 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; + return $this->simple_query('BEGIN TRANSACTION'); } // -------------------------------------------------------------------- @@ -222,24 +134,11 @@ class CI_DB_sqlite_driver extends CI_DB { /** * Commit Transaction * - * @access public * @return bool */ - function trans_commit() + protected 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; + return $this->simple_query('COMMIT'); } // -------------------------------------------------------------------- @@ -247,59 +146,24 @@ class CI_DB_sqlite_driver extends CI_DB { /** * Rollback Transaction * - * @access public * @return bool */ - function trans_rollback() + protected 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; + return $this->simple_query('ROLLBACK'); } // -------------------------------------------------------------------- /** - * Escape String + * Platform-dependant string escape * - * @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) + protected function _escape_str($str) { - 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; + return sqlite_escape_string($str); } // -------------------------------------------------------------------- @@ -307,10 +171,9 @@ class CI_DB_sqlite_driver extends CI_DB { /** * Affected Rows * - * @access public - * @return integer + * @return int */ - function affected_rows() + public function affected_rows() { return sqlite_changes($this->conn_id); } @@ -320,43 +183,11 @@ class CI_DB_sqlite_driver extends CI_DB { /** * Insert ID * - * @access public - * @return integer + * @return int */ - function insert_id() + public 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; + return sqlite_last_insert_rowid($this->conn_id); } // -------------------------------------------------------------------- @@ -366,18 +197,18 @@ class CI_DB_sqlite_driver extends CI_DB { * * Generates a platform-specific query string so that the table names can be fetched * - * @access private - * @param boolean + * @param bool $prefix_limit * @return string */ - function _list_tables($prefix_limit = FALSE) + protected function _list_tables($prefix_limit = FALSE) { - $sql = "SELECT name from sqlite_master WHERE type='table'"; + $sql = "SELECT name FROM sqlite_master WHERE type='table'"; - if ($prefix_limit !== FALSE AND $this->dbprefix != '') + if ($prefix_limit !== FALSE && $this->dbprefix != '') { - $sql .= " AND 'name' LIKE '".$this->escape_like_str($this->dbprefix)."%' ".sprintf($this->_like_escape_str, $this->_like_escape_chr); + return $sql." AND 'name' LIKE '".$this->escape_like_str($this->dbprefix)."%' ".sprintf($this->_like_escape_str, $this->_like_escape_chr); } + return $sql; } @@ -388,11 +219,10 @@ class CI_DB_sqlite_driver extends CI_DB { * * Generates a platform-specific query string so that the column names can be fetched * - * @access public - * @param string the table name - * @return string + * @param string $table + * @return bool */ - function _list_columns($table = '') + protected function _list_columns($table = '') { // Not supported return FALSE; @@ -401,240 +231,88 @@ class CI_DB_sqlite_driver extends CI_DB { // -------------------------------------------------------------------- /** - * Field data query + * Returns an object with field data * - * Generates a platform-specific query so that the column data can be retrieved - * - * @access public - * @param string the table name - * @return object + * @param string $table + * @return array */ - function _field_data($table) + public function field_data($table) { - return "SELECT * FROM ".$table." LIMIT 1"; + if (($query = $this->query('PRAGMA TABLE_INFO('.$this->protect_identifiers($table, TRUE, NULL, FALSE).')')) === FALSE) + { + return FALSE; + } + + $query = $query->result_array(); + if (empty($query)) + { + return FALSE; + } + + $retval = array(); + for ($i = 0, $c = count($query); $i < $c; $i++) + { + $retval[$i] = new stdClass(); + $retval[$i]->name = $query[$i]['name']; + $retval[$i]->type = $query[$i]['type']; + $retval[$i]->max_length = NULL; + $retval[$i]->default = $query[$i]['dflt_value']; + $retval[$i]->primary_key = isset($query[$i]['pk']) ? (int) $query[$i]['pk'] : 0; + } + + return $retval; } // -------------------------------------------------------------------- /** - * The error message string + * Error * - * @access private + * Returns an array containing code and message of the last + * database error that has occured. + * + * @return array + */ + public function error() + { + $error = array('code' => sqlite_last_error($this->conn_id)); + $error['message'] = sqlite_error_string($error['code']); + return $error; + } + + // -------------------------------------------------------------------- + + /** + * Replace statement + * + * Generates a platform-specific replace string from the supplied data + * + * @param string $table Table name + * @param array $keys INSERT keys + * @param array $values INSERT values * @return string */ - function _error_message() + protected function _replace($table, $keys, $values) { - return sqlite_error_string(sqlite_last_error($this->conn_id)); + return 'INSERT OR '.parent::_replace($table, $keys, $values); } - // -------------------------------------------------------------------- - - /** - * 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 + * If the database does not support the TRUNCATE statement, + * then this function maps to 'DELETE FROM table' + * + * @param string $table * @return string */ - function _truncate($table) + protected 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; + return 'DELETE FROM '.$table; } // -------------------------------------------------------------------- @@ -642,18 +320,11 @@ class CI_DB_sqlite_driver extends CI_DB { /** * Close DB Connection * - * @access public - * @param resource * @return void */ - function _close($conn_id) + protected function _close() { - @sqlite_close($conn_id); + sqlite_close($this->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/src/system/database/drivers/sqlite/sqlite_forge.php b/src/system/database/drivers/sqlite/sqlite_forge.php index 67f7246e..8a165943 100644 --- a/src/system/database/drivers/sqlite/sqlite_forge.php +++ b/src/system/database/drivers/sqlite/sqlite_forge.php @@ -1,38 +1,81 @@ -db->database) OR ! @unlink($this->db->database)) + if ( ! file_exists($this->db->database) OR ! @unlink($this->db->database)) { - if ($this->db->db_debug) - { - return $this->db->display_error('db_unable_to_drop'); - } - return FALSE; + return ($this->db->db_debug) ? $this->db->display_error('db_unable_to_drop') : FALSE; } + elseif ( ! empty($this->db->data_cache['db_names'])) + { + $key = array_search(strtolower($this->db->database), array_map('strtolower', $this->db->data_cache['db_names']), TRUE); + if ($key !== FALSE) + { + unset($this->db->data_cache['db_names'][$key]); + } + } + return TRUE; } - // -------------------------------------------------------------------- - - /** - * 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 + * ALTER TABLE * - * Unsupported feature in SQLite - * - * @access private - * @return bool + * @todo implement drop_column(), modify_column() + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * @return string|string[] */ - function _drop_table($table) + protected function _alter_table($alter_type, $table, $field) { - if ($this->db->db_debug) + if ($alter_type === 'DROP' OR $alter_type === 'CHANGE') { - return $this->db->display_error('db_unsuported_feature'); - } - return array(); - } + // drop_column(): + // BEGIN TRANSACTION; + // CREATE TEMPORARY TABLE t1_backup(a,b); + // INSERT INTO t1_backup SELECT a,b FROM t1; + // DROP TABLE t1; + // CREATE TABLE t1(a,b); + // INSERT INTO t1 SELECT a,b FROM t1_backup; + // DROP TABLE t1_backup; + // COMMIT; - // -------------------------------------------------------------------- - - /** - * 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; - + return parent::_alter_table($alter_type, $table, $field); } // -------------------------------------------------------------------- /** - * Rename a table + * Process column * - * 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 + * @param array $field * @return string */ - function _rename_table($table_name, $new_table_name) + protected function _process_column($field) { - $sql = 'ALTER TABLE '.$this->db->_protect_identifiers($table_name)." RENAME TO ".$this->db->_protect_identifiers($new_table_name); - return $sql; + return $this->db->escape_identifiers($field['name']) + .' '.$field['type'] + .$field['auto_increment'] + .$field['null'] + .$field['unique'] + .$field['default']; } -} -/* End of file sqlite_forge.php */ -/* Location: ./system/database/drivers/sqlite/sqlite_forge.php */ \ No newline at end of file + // -------------------------------------------------------------------- + + /** + * Field attribute TYPE + * + * Performs a data type mapping between different databases. + * + * @param array &$attributes + * @return void + */ + protected function _attr_type(&$attributes) + { + switch (strtoupper($attributes['TYPE'])) + { + case 'ENUM': + case 'SET': + $attributes['TYPE'] = 'TEXT'; + return; + default: return; + } + } + + // -------------------------------------------------------------------- + + /** + * Field attribute AUTO_INCREMENT + * + * @param array &$attributes + * @param array &$field + * @return void + */ + protected function _attr_auto_increment(&$attributes, &$field) + { + if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE && stripos($field['type'], 'int') !== FALSE) + { + $field['type'] = 'INTEGER PRIMARY KEY'; + $field['default'] = ''; + $field['null'] = ''; + $field['unique'] = ''; + $field['auto_increment'] = ' AUTOINCREMENT'; + + $this->primary_keys = array(); + } + } + +} diff --git a/src/system/database/drivers/sqlite/sqlite_result.php b/src/system/database/drivers/sqlite/sqlite_result.php index a6e88526..d40b98aa 100644 --- a/src/system/database/drivers/sqlite/sqlite_result.php +++ b/src/system/database/drivers/sqlite/sqlite_result.php @@ -1,20 +1,41 @@ -result_id); + return is_int($this->num_rows) + ? $this->num_rows + : $this->num_rows = @sqlite_num_rows($this->result_id); } // -------------------------------------------------------------------- @@ -43,10 +65,9 @@ class CI_DB_sqlite_result extends CI_DB_result { /** * Number of fields in the result set * - * @access public - * @return integer + * @return int */ - function num_fields() + public function num_fields() { return @sqlite_num_fields($this->result_id); } @@ -58,15 +79,14 @@ class CI_DB_sqlite_result extends CI_DB_result { * * Generates an array of column names * - * @access public * @return array */ - function list_fields() + public function list_fields() { $field_names = array(); - for ($i = 0; $i < $this->num_fields(); $i++) + for ($i = 0, $c = $this->num_fields(); $i < $c; $i++) { - $field_names[] = sqlite_field_name($this->result_id, $i); + $field_names[$i] = sqlite_field_name($this->result_id, $i); } return $field_names; @@ -79,22 +99,17 @@ class CI_DB_sqlite_result extends CI_DB_result { * * Generates an array of objects containing field meta-data * - * @access public * @return array */ - function field_data() + public function field_data() { $retval = array(); - for ($i = 0; $i < $this->num_fields(); $i++) + for ($i = 0, $c = $this->num_fields(); $i < $c; $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; + $retval[$i] = new stdClass(); + $retval[$i]->name = sqlite_field_name($this->result_id, $i); + $retval[$i]->type = NULL; + $retval[$i]->max_length = NULL; } return $retval; @@ -102,29 +117,17 @@ class CI_DB_sqlite_result extends CI_DB_result { // -------------------------------------------------------------------- - /** - * Free the result - * - * @return null - */ - function free_result() - { - // Not implemented in SQLite - } - - // -------------------------------------------------------------------- - /** * Data Seek * - * Moves the internal pointer to the desired offset. We call + * Moves the internal pointer to the desired offset. We call * this internally before fetching results to make sure the - * result set starts at zero + * result set starts at zero. * - * @access private - * @return array + * @param int $n + * @return bool */ - function _data_seek($n = 0) + public function data_seek($n = 0) { return sqlite_seek($this->result_id, $n); } @@ -136,10 +139,9 @@ class CI_DB_sqlite_result extends CI_DB_result { * * Returns the result set as an array * - * @access private * @return array */ - function _fetch_assoc() + protected function _fetch_assoc() { return sqlite_fetch_array($this->result_id); } @@ -151,30 +153,12 @@ class CI_DB_sqlite_result extends CI_DB_result { * * Returns the result set as an object * - * @access private + * @param string $class_name * @return object */ - function _fetch_object() + protected function _fetch_object($class_name = 'stdClass') { - 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; - } - } + return sqlite_fetch_object($this->result_id, $class_name); } } - - -/* End of file sqlite_result.php */ -/* Location: ./system/database/drivers/sqlite/sqlite_result.php */ \ No newline at end of file diff --git a/src/system/database/drivers/sqlite/sqlite_utility.php b/src/system/database/drivers/sqlite/sqlite_utility.php index 5c8a9dea..59c46f9e 100644 --- a/src/system/database/drivers/sqlite/sqlite_utility.php +++ b/src/system/database/drivers/sqlite/sqlite_utility.php @@ -1,97 +1,61 @@ -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 + * @param array $params Preferences * @return mixed */ - function _backup($params = array()) + protected function _backup($params = array()) { // Currently unsupported - return $this->db->display_error('db_unsuported_feature'); + return $this->db->display_error('db_unsupported_feature'); } -} -/* End of file sqlite_utility.php */ -/* Location: ./system/database/drivers/sqlite/sqlite_utility.php */ \ No newline at end of file +} diff --git a/src/system/database/drivers/sqlite3/index.html b/src/system/database/drivers/sqlite3/index.html new file mode 100644 index 00000000..b702fbc3 --- /dev/null +++ b/src/system/database/drivers/sqlite3/index.html @@ -0,0 +1,11 @@ + + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + diff --git a/src/system/database/drivers/sqlite3/sqlite3_driver.php b/src/system/database/drivers/sqlite3/sqlite3_driver.php new file mode 100644 index 00000000..9743499b --- /dev/null +++ b/src/system/database/drivers/sqlite3/sqlite3_driver.php @@ -0,0 +1,350 @@ +password) + ? new SQLite3($this->database) + : new SQLite3($this->database, SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE, $this->password); + } + catch (Exception $e) + { + return FALSE; + } + } + + // -------------------------------------------------------------------- + + /** + * Database version number + * + * @return string + */ + public function version() + { + if (isset($this->data_cache['version'])) + { + return $this->data_cache['version']; + } + + $version = SQLite3::version(); + return $this->data_cache['version'] = $version['versionString']; + } + + // -------------------------------------------------------------------- + + /** + * Execute the query + * + * @todo Implement use of SQLite3::querySingle(), if needed + * @param string $sql + * @return mixed SQLite3Result object or bool + */ + protected function _execute($sql) + { + return $this->is_write_type($sql) + ? $this->conn_id->exec($sql) + : $this->conn_id->query($sql); + } + + // -------------------------------------------------------------------- + + /** + * Begin Transaction + * + * @return bool + */ + protected function _trans_begin() + { + return $this->conn_id->exec('BEGIN TRANSACTION'); + } + + // -------------------------------------------------------------------- + + /** + * Commit Transaction + * + * @return bool + */ + protected function _trans_commit() + { + return $this->conn_id->exec('END TRANSACTION'); + } + + // -------------------------------------------------------------------- + + /** + * Rollback Transaction + * + * @return bool + */ + protected function _trans_rollback() + { + return $this->conn_id->exec('ROLLBACK'); + } + + // -------------------------------------------------------------------- + + /** + * Platform-dependant string escape + * + * @param string + * @return string + */ + protected function _escape_str($str) + { + return $this->conn_id->escapeString($str); + } + + // -------------------------------------------------------------------- + + /** + * Affected Rows + * + * @return int + */ + public function affected_rows() + { + return $this->conn_id->changes(); + } + + // -------------------------------------------------------------------- + + /** + * Insert ID + * + * @return int + */ + public function insert_id() + { + return $this->conn_id->lastInsertRowID(); + } + + // -------------------------------------------------------------------- + + /** + * Show table query + * + * Generates a platform-specific query string so that the table names can be fetched + * + * @param bool $prefix_limit + * @return string + */ + protected function _list_tables($prefix_limit = FALSE) + { + return 'SELECT "NAME" FROM "SQLITE_MASTER" WHERE "TYPE" = \'table\'' + .(($prefix_limit !== FALSE && $this->dbprefix != '') + ? ' AND "NAME" LIKE \''.$this->escape_like_str($this->dbprefix).'%\' '.sprintf($this->_like_escape_str, $this->_like_escape_chr) + : ''); + } + + // -------------------------------------------------------------------- + + /** + * Fetch Field Names + * + * @param string $table Table name + * @return array + */ + public function list_fields($table) + { + // Is there a cached result? + if (isset($this->data_cache['field_names'][$table])) + { + return $this->data_cache['field_names'][$table]; + } + + if (($result = $this->query('PRAGMA TABLE_INFO('.$this->protect_identifiers($table, TRUE, NULL, FALSE).')')) === FALSE) + { + return FALSE; + } + + $this->data_cache['field_names'][$table] = array(); + foreach ($result->result_array() as $row) + { + $this->data_cache['field_names'][$table][] = $row['name']; + } + + return $this->data_cache['field_names'][$table]; + } + + // -------------------------------------------------------------------- + + /** + * Returns an object with field data + * + * @param string $table + * @return array + */ + public function field_data($table) + { + if (($query = $this->query('PRAGMA TABLE_INFO('.$this->protect_identifiers($table, TRUE, NULL, FALSE).')')) === FALSE) + { + return FALSE; + } + + $query = $query->result_array(); + if (empty($query)) + { + return FALSE; + } + + $retval = array(); + for ($i = 0, $c = count($query); $i < $c; $i++) + { + $retval[$i] = new stdClass(); + $retval[$i]->name = $query[$i]['name']; + $retval[$i]->type = $query[$i]['type']; + $retval[$i]->max_length = NULL; + $retval[$i]->default = $query[$i]['dflt_value']; + $retval[$i]->primary_key = isset($query[$i]['pk']) ? (int) $query[$i]['pk'] : 0; + } + + return $retval; + } + + // -------------------------------------------------------------------- + + /** + * Error + * + * Returns an array containing code and message of the last + * database error that has occured. + * + * @return array + */ + public function error() + { + return array('code' => $this->conn_id->lastErrorCode(), 'message' => $this->conn_id->lastErrorMsg()); + } + + // -------------------------------------------------------------------- + + /** + * Replace statement + * + * Generates a platform-specific replace string from the supplied data + * + * @param string $table Table name + * @param array $keys INSERT keys + * @param array $values INSERT values + * @return string + */ + protected function _replace($table, $keys, $values) + { + return 'INSERT OR '.parent::_replace($table, $keys, $values); + } + + // -------------------------------------------------------------------- + + /** + * Truncate statement + * + * Generates a platform-specific truncate string from the supplied data + * + * If the database does not support the TRUNCATE statement, + * then this method maps to 'DELETE FROM table' + * + * @param string $table + * @return string + */ + protected function _truncate($table) + { + return 'DELETE FROM '.$table; + } + + // -------------------------------------------------------------------- + + /** + * Close DB Connection + * + * @return void + */ + protected function _close() + { + $this->conn_id->close(); + } + +} diff --git a/src/system/database/drivers/sqlite3/sqlite3_forge.php b/src/system/database/drivers/sqlite3/sqlite3_forge.php new file mode 100644 index 00000000..43cbe33e --- /dev/null +++ b/src/system/database/drivers/sqlite3/sqlite3_forge.php @@ -0,0 +1,225 @@ +db->version(), '3.3', '<')) + { + $this->_create_table_if = FALSE; + $this->_drop_table_if = FALSE; + } + } + + // -------------------------------------------------------------------- + + /** + * Create database + * + * @param string $db_name + * @return bool + */ + public function create_database($db_name = '') + { + // In SQLite, a database is created when you connect to the database. + // We'll return TRUE so that an error isn't generated + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Drop database + * + * @param string $db_name (ignored) + * @return bool + */ + public function drop_database($db_name = '') + { + // In SQLite, a database is dropped when we delete a file + if (file_exists($this->db->database)) + { + // We need to close the pseudo-connection first + $this->db->close(); + if ( ! @unlink($this->db->database)) + { + return $this->db->db_debug ? $this->db->display_error('db_unable_to_drop') : FALSE; + } + elseif ( ! empty($this->db->data_cache['db_names'])) + { + $key = array_search(strtolower($this->db->database), array_map('strtolower', $this->db->data_cache['db_names']), TRUE); + if ($key !== FALSE) + { + unset($this->db->data_cache['db_names'][$key]); + } + } + + return TRUE; + } + + return $this->db->db_debug ? $this->db->display_error('db_unable_to_drop') : FALSE; + } + + // -------------------------------------------------------------------- + + /** + * ALTER TABLE + * + * @todo implement drop_column(), modify_column() + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * @return string|string[] + */ + protected function _alter_table($alter_type, $table, $field) + { + if ($alter_type === 'DROP' OR $alter_type === 'CHANGE') + { + // drop_column(): + // BEGIN TRANSACTION; + // CREATE TEMPORARY TABLE t1_backup(a,b); + // INSERT INTO t1_backup SELECT a,b FROM t1; + // DROP TABLE t1; + // CREATE TABLE t1(a,b); + // INSERT INTO t1 SELECT a,b FROM t1_backup; + // DROP TABLE t1_backup; + // COMMIT; + + return FALSE; + } + + return parent::_alter_table($alter_type, $table, $field); + } + + // -------------------------------------------------------------------- + + /** + * Process column + * + * @param array $field + * @return string + */ + protected function _process_column($field) + { + return $this->db->escape_identifiers($field['name']) + .' '.$field['type'] + .$field['auto_increment'] + .$field['null'] + .$field['unique'] + .$field['default']; + } + + // -------------------------------------------------------------------- + + /** + * Field attribute TYPE + * + * Performs a data type mapping between different databases. + * + * @param array &$attributes + * @return void + */ + protected function _attr_type(&$attributes) + { + switch (strtoupper($attributes['TYPE'])) + { + case 'ENUM': + case 'SET': + $attributes['TYPE'] = 'TEXT'; + return; + default: return; + } + } + + // -------------------------------------------------------------------- + + /** + * Field attribute AUTO_INCREMENT + * + * @param array &$attributes + * @param array &$field + * @return void + */ + protected function _attr_auto_increment(&$attributes, &$field) + { + if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE && stripos($field['type'], 'int') !== FALSE) + { + $field['type'] = 'INTEGER PRIMARY KEY'; + $field['default'] = ''; + $field['null'] = ''; + $field['unique'] = ''; + $field['auto_increment'] = ' AUTOINCREMENT'; + + $this->primary_keys = array(); + } + } + +} diff --git a/src/system/database/drivers/sqlite3/sqlite3_result.php b/src/system/database/drivers/sqlite3/sqlite3_result.php new file mode 100644 index 00000000..aa559eef --- /dev/null +++ b/src/system/database/drivers/sqlite3/sqlite3_result.php @@ -0,0 +1,194 @@ +result_id->numColumns(); + } + + // -------------------------------------------------------------------- + + /** + * Fetch Field Names + * + * Generates an array of column names + * + * @return array + */ + public function list_fields() + { + $field_names = array(); + for ($i = 0, $c = $this->num_fields(); $i < $c; $i++) + { + $field_names[] = $this->result_id->columnName($i); + } + + return $field_names; + } + + // -------------------------------------------------------------------- + + /** + * Field data + * + * Generates an array of objects containing field meta-data + * + * @return array + */ + public function field_data() + { + static $data_types = array( + SQLITE3_INTEGER => 'integer', + SQLITE3_FLOAT => 'float', + SQLITE3_TEXT => 'text', + SQLITE3_BLOB => 'blob', + SQLITE3_NULL => 'null' + ); + + $retval = array(); + for ($i = 0, $c = $this->num_fields(); $i < $c; $i++) + { + $retval[$i] = new stdClass(); + $retval[$i]->name = $this->result_id->columnName($i); + + $type = $this->result_id->columnType($i); + $retval[$i]->type = isset($data_types[$type]) ? $data_types[$type] : $type; + + $retval[$i]->max_length = NULL; + } + + return $retval; + } + + // -------------------------------------------------------------------- + + /** + * Free the result + * + * @return void + */ + public function free_result() + { + if (is_object($this->result_id)) + { + $this->result_id->finalize(); + $this->result_id = NULL; + } + } + + // -------------------------------------------------------------------- + + /** + * Result - associative array + * + * Returns the result set as an array + * + * @return array + */ + protected function _fetch_assoc() + { + return $this->result_id->fetchArray(SQLITE3_ASSOC); + } + + // -------------------------------------------------------------------- + + /** + * Result - object + * + * Returns the result set as an object + * + * @param string $class_name + * @return object + */ + protected function _fetch_object($class_name = 'stdClass') + { + // No native support for fetching rows as objects + if (($row = $this->result_id->fetchArray(SQLITE3_ASSOC)) === FALSE) + { + return FALSE; + } + elseif ($class_name === 'stdClass') + { + return (object) $row; + } + + $class_name = new $class_name(); + foreach (array_keys($row) as $key) + { + $class_name->$key = $row[$key]; + } + + return $class_name; + } + + // -------------------------------------------------------------------- + + /** + * Data Seek + * + * Moves the internal pointer to the desired offset. We call + * this internally before fetching results to make sure the + * result set starts at zero. + * + * @param int $n (ignored) + * @return array + */ + public function data_seek($n = 0) + { + // Only resetting to the start of the result set is supported + return ($n > 0) ? FALSE : $this->result_id->reset(); + } + +} diff --git a/src/system/database/drivers/sqlite3/sqlite3_utility.php b/src/system/database/drivers/sqlite3/sqlite3_utility.php new file mode 100644 index 00000000..b47c086f --- /dev/null +++ b/src/system/database/drivers/sqlite3/sqlite3_utility.php @@ -0,0 +1,61 @@ +db->display_error('db_unsupported_feature'); + } + +} diff --git a/src/system/database/drivers/sqlsrv/index.html b/src/system/database/drivers/sqlsrv/index.html index c942a79c..b702fbc3 100644 --- a/src/system/database/drivers/sqlsrv/index.html +++ b/src/system/database/drivers/sqlsrv/index.html @@ -1,3 +1,4 @@ + 403 Forbidden @@ -7,4 +8,4 @@

Directory access is forbidden.

- \ No newline at end of file + diff --git a/src/system/database/drivers/sqlsrv/sqlsrv_driver.php b/src/system/database/drivers/sqlsrv/sqlsrv_driver.php index a06f504e..0cd9ce16 100644 --- a/src/system/database/drivers/sqlsrv/sqlsrv_driver.php +++ b/src/system/database/drivers/sqlsrv/sqlsrv_driver.php @@ -1,109 +1,156 @@ -scrollable === NULL) + { + $this->scrollable = defined('SQLSRV_CURSOR_CLIENT_BUFFERED') + ? SQLSRV_CURSOR_CLIENT_BUFFERED + : FALSE; + } + } + + // -------------------------------------------------------------------- + + /** + * Database connection + * + * @param bool $pooling * @return resource */ - function db_connect($pooling = false) + public function db_connect($pooling = FALSE) { - // Check for a UTF-8 charset being passed as CI's default 'utf8'. - $character_set = (0 === strcasecmp('utf8', $this->char_set)) ? 'UTF-8' : $this->char_set; + $charset = in_array(strtolower($this->char_set), array('utf-8', 'utf8'), TRUE) + ? 'UTF-8' : SQLSRV_ENC_CHAR; $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 + 'UID' => empty($this->username) ? '' : $this->username, + 'PWD' => empty($this->password) ? '' : $this->password, + 'Database' => $this->database, + 'ConnectionPooling' => ($pooling === TRUE) ? 1 : 0, + 'CharacterSet' => $charset, + 'Encrypt' => ($this->encrypt === TRUE) ? 1 : 0, + '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'])) { + if (empty($connection['UID']) && empty($connection['PWD'])) + { unset($connection['UID'], $connection['PWD']); } - return sqlsrv_connect($this->hostname, $connection); - } + if (FALSE !== ($this->conn_id = sqlsrv_connect($this->hostname, $connection))) + { + // Determine how identifiers are escaped + $query = $this->query('SELECT CASE WHEN (@@OPTIONS | 256) = @@OPTIONS THEN 1 ELSE 0 END AS qi'); + $query = $query->row_array(); + $this->_quoted_identifier = empty($query) ? FALSE : (bool) $query['qi']; + $this->_escape_char = ($this->_quoted_identifier) ? '"' : array('[', ']'); + } - // -------------------------------------------------------------------- - - /** - * 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 + return $this->conn_id; } // -------------------------------------------------------------------- @@ -111,28 +158,23 @@ class CI_DB_sqlsrv_driver extends CI_DB { /** * Select the database * - * @access private called by the base class - * @return resource + * @param string $database + * @return bool */ - function db_select() + public function db_select($database = '') { - return $this->_execute('USE ' . $this->database); - } + if ($database === '') + { + $database = $this->database; + } - // -------------------------------------------------------------------- + if ($this->_execute('USE '.$this->escape_identifiers($database))) + { + $this->database = $database; + 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; + return FALSE; } // -------------------------------------------------------------------- @@ -140,33 +182,14 @@ class CI_DB_sqlsrv_driver extends CI_DB { /** * Execute the query * - * @access private called by the base class - * @param string an SQL query + * @param string $sql an SQL query * @return resource */ - function _execute($sql) + protected 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; + return ($this->scrollable === FALSE OR $this->is_write_type($sql)) + ? sqlsrv_query($this->conn_id, $sql) + : sqlsrv_query($this->conn_id, $sql, NULL, array('Scrollable' => $this->scrollable)); } // -------------------------------------------------------------------- @@ -174,27 +197,10 @@ class CI_DB_sqlsrv_driver extends CI_DB { /** * Begin Transaction * - * @access public * @return bool */ - function trans_begin($test_mode = FALSE) + protected function _trans_begin() { - 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); } @@ -203,22 +209,10 @@ class CI_DB_sqlsrv_driver extends CI_DB { /** * Commit Transaction * - * @access public * @return bool */ - function trans_commit() + protected 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); } @@ -227,130 +221,59 @@ class CI_DB_sqlsrv_driver extends CI_DB { /** * Rollback Transaction * - * @access public * @return bool */ - function trans_rollback() + protected 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 + * @return int */ - function affected_rows() + public function affected_rows() { - return @sqlrv_rows_affected($this->conn_id); + return sqlsrv_rows_affected($this->result_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 + * Insert ID * - * Generates a platform-specific query string that counts all records in - * the specified database + * Returns the last id created in the Identity column. * - * @access public - * @param string * @return string */ - function count_all($table = '') + public function insert_id() { - if ($table == '') + return $this->query('SELECT SCOPE_IDENTITY() AS insert_id')->row()->insert_id; + } + + // -------------------------------------------------------------------- + + /** + * Database version number + * + * @return string + */ + public function version() + { + if (isset($this->data_cache['version'])) { - return 0; + return $this->data_cache['version']; } - $query = $this->query($this->_count_string . $this->_protect_identifiers('numrows') . " FROM " . $this->_protect_identifiers($table, TRUE, NULL, FALSE)); - - if ($query->num_rows() == 0) + if ( ! $this->conn_id OR ($info = sqlsrv_server_info($this->conn_id)) === FALSE) { - return 0; + return FALSE; } - $row = $query->row(); - $this->_reset_select(); - return (int) $row->numrows; + return $this->data_cache['version'] = $info['SQLServerVersion']; } // -------------------------------------------------------------------- @@ -360,13 +283,22 @@ class CI_DB_sqlsrv_driver extends CI_DB { * * Generates a platform-specific query string so that the table names can be fetched * - * @access private - * @param boolean - * @return string + * @param bool + * @return string $prefix_limit */ - function _list_tables($prefix_limit = FALSE) + protected function _list_tables($prefix_limit = FALSE) { - return "SELECT name FROM sysobjects WHERE type = 'U' ORDER BY name"; + $sql = 'SELECT '.$this->escape_identifiers('name') + .' FROM '.$this->escape_identifiers('sysobjects') + .' WHERE '.$this->escape_identifiers('type')." = 'U'"; + + if ($prefix_limit === TRUE && $this->dbprefix !== '') + { + $sql .= ' AND '.$this->escape_identifiers('name')." LIKE '".$this->escape_like_str($this->dbprefix)."%' " + .sprintf($this->_escape_like_str, $this->_escape_like_chr); + } + + return $sql.' ORDER BY '.$this->escape_identifiers('name'); } // -------------------------------------------------------------------- @@ -376,129 +308,85 @@ class CI_DB_sqlsrv_driver extends CI_DB { * * Generates a platform-specific query string so that the column names can be fetched * - * @access private - * @param string the table name + * @param string $table * @return string */ - function _list_columns($table = '') + protected function _list_columns($table = '') { - return "SELECT * FROM INFORMATION_SCHEMA.Columns WHERE TABLE_NAME = '".$this->_escape_table($table)."'"; + return 'SELECT COLUMN_NAME + FROM INFORMATION_SCHEMA.Columns + WHERE UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table)); } // -------------------------------------------------------------------- /** - * Field data query + * Returns an object with field data * - * Generates a platform-specific query so that the column data can be retrieved - * - * @access public - * @param string the table name - * @return object + * @param string $table + * @return array */ - function _field_data($table) + public function field_data($table) { - return "SELECT TOP 1 * FROM " . $this->_escape_table($table); - } + $sql = 'SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, COLUMN_DEFAULT + FROM INFORMATION_SCHEMA.Columns + WHERE UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table)); - // -------------------------------------------------------------------- - - /** - * 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)) + if (($query = $this->query($sql)) === FALSE) { - $tables = array($tables); + return FALSE; + } + $query = $query->result_object(); + + $retval = array(); + for ($i = 0, $c = count($query); $i < $c; $i++) + { + $retval[$i] = new stdClass(); + $retval[$i]->name = $query[$i]->COLUMN_NAME; + $retval[$i]->type = $query[$i]->DATA_TYPE; + $retval[$i]->max_length = ($query[$i]->CHARACTER_MAXIMUM_LENGTH > 0) ? $query[$i]->CHARACTER_MAXIMUM_LENGTH : $query[$i]->NUMERIC_PRECISION; + $retval[$i]->default = $query[$i]->COLUMN_DEFAULT; } - return implode(', ', $tables); + return $retval; } // -------------------------------------------------------------------- /** - * Insert statement + * Error * - * Generates a platform-specific insert string from the supplied data + * Returns an array containing code and message of the last + * database error that has occured. * - * @access public - * @param string the table name - * @param array the insert keys - * @param array the insert values - * @return string + * @return array */ - function _insert($table, $keys, $values) + public function error() { - return "INSERT INTO ".$this->_escape_table($table)." (".implode(', ', $keys).") VALUES (".implode(', ', $values).")"; + $error = array('code' => '00000', 'message' => ''); + $sqlsrv_errors = sqlsrv_errors(SQLSRV_ERR_ERRORS); + + if ( ! is_array($sqlsrv_errors)) + { + return $error; + } + + $sqlsrv_error = array_shift($sqlsrv_errors); + if (isset($sqlsrv_error['SQLSTATE'])) + { + $error['code'] = isset($sqlsrv_error['code']) ? $sqlsrv_error['SQLSTATE'].'/'.$sqlsrv_error['code'] : $sqlsrv_error['SQLSTATE']; + } + elseif (isset($sqlsrv_error['code'])) + { + $error['code'] = $sqlsrv_error['code']; + } + + if (isset($sqlsrv_error['message'])) + { + $error['message'] = $sqlsrv_error['message']; + } + + return $error; } // -------------------------------------------------------------------- @@ -508,22 +396,15 @@ class CI_DB_sqlsrv_driver extends CI_DB { * * 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 + * @param string $table + * @param array $values * @return string */ - function _update($table, $values, $where) + protected function _update($table, $values) { - foreach($values as $key => $val) - { - $valstr[] = $key." = ".$val; - } - - return "UPDATE ".$this->_escape_table($table)." SET ".implode(', ', $valstr)." WHERE ".implode(" ", $where); + $this->qb_limit = FALSE; + $this->qb_orderby = array(); + return parent::_update($table, $values); } // -------------------------------------------------------------------- @@ -532,16 +413,16 @@ class CI_DB_sqlsrv_driver extends CI_DB { * 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 + * If the database does not support the TRUNCATE statement, + * then this method maps to 'DELETE FROM table' + * + * @param string $table * @return string */ - function _truncate($table) + protected function _truncate($table) { - return "TRUNCATE TABLE ".$table; + return 'TRUNCATE TABLE '.$table; } // -------------------------------------------------------------------- @@ -551,35 +432,99 @@ class CI_DB_sqlsrv_driver extends CI_DB { * * 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 + * @param string $table * @return string */ - function _delete($table, $where) + protected function _delete($table) { - return "DELETE FROM ".$this->_escape_table($table)." WHERE ".implode(" ", $where); + if ($this->qb_limit) + { + return 'WITH ci_delete AS (SELECT TOP '.$this->qb_limit.' * FROM '.$table.$this->_compile_wh('qb_where').') DELETE FROM ci_delete'; + } + + return parent::_delete($table); } // -------------------------------------------------------------------- /** - * Limit string + * LIMIT * * 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 + * @param string $sql SQL Query * @return string */ - function _limit($sql, $limit, $offset) + protected function _limit($sql) { - $i = $limit + $offset; + // As of SQL Server 2012 (11.0.*) OFFSET is supported + if (version_compare($this->version(), '11', '>=')) + { + // SQL Server OFFSET-FETCH can be used only with the ORDER BY clause + empty($this->qb_orderby) && $sql .= ' ORDER BY 1'; - return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.$i.' ', $sql); + return $sql.' OFFSET '.(int) $this->qb_offset.' ROWS FETCH NEXT '.$this->qb_limit.' ROWS ONLY'; + } + + $limit = $this->qb_offset + $this->qb_limit; + + // An ORDER BY clause is required for ROW_NUMBER() to work + if ($this->qb_offset && ! empty($this->qb_orderby)) + { + $orderby = $this->_compile_order_by(); + + // We have to strip the ORDER BY clause + $sql = trim(substr($sql, 0, strrpos($sql, $orderby))); + + // Get the fields to select from our subquery, so that we can avoid CI_rownum appearing in the actual results + if (count($this->qb_select) === 0) + { + $select = '*'; // Inevitable + } + else + { + // Use only field names and their aliases, everything else is out of our scope. + $select = array(); + $field_regexp = ($this->_quoted_identifier) + ? '("[^\"]+")' : '(\[[^\]]+\])'; + for ($i = 0, $c = count($this->qb_select); $i < $c; $i++) + { + $select[] = preg_match('/(?:\s|\.)'.$field_regexp.'$/i', $this->qb_select[$i], $m) + ? $m[1] : $this->qb_select[$i]; + } + $select = implode(', ', $select); + } + + return 'SELECT '.$select." FROM (\n\n" + .preg_replace('/^(SELECT( DISTINCT)?)/i', '\\1 ROW_NUMBER() OVER('.trim($orderby).') AS '.$this->escape_identifiers('CI_rownum').', ', $sql) + ."\n\n) ".$this->escape_identifiers('CI_subquery') + ."\nWHERE ".$this->escape_identifiers('CI_rownum').' BETWEEN '.($this->qb_offset + 1).' AND '.$limit; + } + + return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.$limit.' ', $sql); + } + + // -------------------------------------------------------------------- + + /** + * Insert batch statement + * + * Generates a platform-specific insert string from the supplied data. + * + * @param string $table Table name + * @param array $keys INSERT keys + * @param array $values INSERT values + * @return string|bool + */ + protected function _insert_batch($table, $keys, $values) + { + // Multiple-value inserts are only supported as of SQL Server 2008 + if (version_compare($this->version(), '10', '>=')) + { + return parent::_insert_batch($table, $keys, $values); + } + + return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE; } // -------------------------------------------------------------------- @@ -587,18 +532,11 @@ class CI_DB_sqlsrv_driver extends CI_DB { /** * Close DB Connection * - * @access public - * @param resource * @return void */ - function _close($conn_id) + protected function _close() { - @sqlsrv_close($conn_id); + sqlsrv_close($this->conn_id); } } - - - -/* End of file sqlsrv_driver.php */ -/* Location: ./system/database/drivers/sqlsrv/sqlsrv_driver.php */ \ No newline at end of file diff --git a/src/system/database/drivers/sqlsrv/sqlsrv_forge.php b/src/system/database/drivers/sqlsrv/sqlsrv_forge.php index d3a55498..f915dad3 100644 --- a/src/system/database/drivers/sqlsrv/sqlsrv_forge.php +++ b/src/system/database/drivers/sqlsrv/sqlsrv_forge.php @@ -1,246 +1,144 @@ - 'SMALLINT', + 'SMALLINT' => 'INT', + 'INT' => 'BIGINT', + 'REAL' => 'FLOAT' + ); + + // -------------------------------------------------------------------- + + /** + * ALTER TABLE + * + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * @return string|string[] + */ + protected function _alter_table($alter_type, $table, $field) { - return "CREATE DATABASE ".$name; + if (in_array($alter_type, array('ADD', 'DROP'), TRUE)) + { + return parent::_alter_table($alter_type, $table, $field); + } + + $sql = 'ALTER TABLE '.$this->db->escape_identifiers($table).' ALTER COLUMN '; + $sqls = array(); + for ($i = 0, $c = count($field); $i < $c; $i++) + { + $sqls[] = $sql.$this->_process_column($field[$i]); + } + + return $sqls; } // -------------------------------------------------------------------- /** - * Drop database + * Field attribute TYPE * - * @access private - * @param string the database name - * @return bool + * Performs a data type mapping between different databases. + * + * @param array &$attributes + * @return void */ - function _drop_database($name) + protected function _attr_type(&$attributes) { - return "DROP DATABASE ".$name; + switch (strtoupper($attributes['TYPE'])) + { + case 'MEDIUMINT': + $attributes['TYPE'] = 'INTEGER'; + $attributes['UNSIGNED'] = FALSE; + return; + case 'INTEGER': + $attributes['TYPE'] = 'INT'; + return; + default: return; + } } // -------------------------------------------------------------------- /** - * Drop Table + * Field attribute AUTO_INCREMENT * - * @access private - * @return bool + * @param array &$attributes + * @param array &$field + * @return void */ - function _drop_table($table) + protected function _attr_auto_increment(&$attributes, &$field) { - return "IF (EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = '" - .$table."')) DROP TABLE [dbo].[".$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 = ''; - if ($if_not_exists === TRUE) + if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE && stripos($field['type'], 'int') !== FALSE) { - $sql = "IF (NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = "; + $field['auto_increment'] = ' IDENTITY(1,1)'; } - $sql .= $this->db->_escape_identifiers($table).")) CREATE TABLE ".$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 .= ' IDENTITY(1,1)'; - } - } - - // 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) - { - return 'EXEC sp_rename '.$this->db->_protect_identifiers($table_name).", ".$this->db->_protect_identifiers($new_table_name); } } - -/* End of file sqlsrv_forge.php */ -/* Location: ./system/database/drivers/sqlsrv/sqlsrv_forge.php */ \ No newline at end of file diff --git a/src/system/database/drivers/sqlsrv/sqlsrv_result.php b/src/system/database/drivers/sqlsrv/sqlsrv_result.php index 0d2dbf53..fde7264b 100644 --- a/src/system/database/drivers/sqlsrv/sqlsrv_result.php +++ b/src/system/database/drivers/sqlsrv/sqlsrv_result.php @@ -1,20 +1,41 @@ -scrollable = $driver_object->scrollable; + } + + // -------------------------------------------------------------------- + /** * Number of rows in the result set * - * @access public - * @return integer + * @return int */ - function num_rows() + public function num_rows() { - return @sqlsrv_num_rows($this->result_id); + // sqlsrv_num_rows() doesn't work with the FORWARD and DYNAMIC cursors (FALSE is the same as FORWARD) + if ( ! in_array($this->scrollable, array(FALSE, SQLSRV_CURSOR_FORWARD, SQLSRV_CURSOR_DYNAMIC), TRUE)) + { + return parent::num_rows(); + } + + return is_int($this->num_rows) + ? $this->num_rows + : $this->num_rows = sqlsrv_num_rows($this->result_id); } // -------------------------------------------------------------------- @@ -43,10 +95,9 @@ class CI_DB_sqlsrv_result extends CI_DB_result { /** * Number of fields in the result set * - * @access public - * @return integer + * @return int */ - function num_fields() + public function num_fields() { return @sqlsrv_num_fields($this->result_id); } @@ -58,17 +109,16 @@ class CI_DB_sqlsrv_result extends CI_DB_result { * * Generates an array of column names * - * @access public * @return array */ - function list_fields() + public function list_fields() { $field_names = array(); - foreach(sqlsrv_field_metadata($this->result_id) as $offset => $field) + foreach (sqlsrv_field_metadata($this->result_id) as $offset => $field) { $field_names[] = $field['Name']; } - + return $field_names; } @@ -79,24 +129,19 @@ class CI_DB_sqlsrv_result extends CI_DB_result { * * Generates an array of objects containing field meta-data * - * @access public * @return array */ - function field_data() + public function field_data() { $retval = array(); - foreach(sqlsrv_field_metadata($this->result_id) as $offset => $field) + foreach (sqlsrv_field_metadata($this->result_id) as $i => $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; + $retval[$i] = new stdClass(); + $retval[$i]->name = $field['Name']; + $retval[$i]->type = $field['Type']; + $retval[$i]->max_length = $field['Size']; } - + return $retval; } @@ -105,9 +150,9 @@ class CI_DB_sqlsrv_result extends CI_DB_result { /** * Free the result * - * @return null + * @return void */ - function free_result() + public function free_result() { if (is_resource($this->result_id)) { @@ -118,32 +163,14 @@ class CI_DB_sqlsrv_result extends CI_DB_result { // -------------------------------------------------------------------- - /** - * 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() + protected function _fetch_assoc() { return sqlsrv_fetch_array($this->result_id, SQLSRV_FETCH_ASSOC); } @@ -155,16 +182,12 @@ class CI_DB_sqlsrv_result extends CI_DB_result { * * Returns the result set as an object * - * @access private + * @param string $class_name * @return object */ - function _fetch_object() + protected function _fetch_object($class_name = 'stdClass') { - return sqlsrv_fetch_object($this->result_id); + return sqlsrv_fetch_object($this->result_id, $class_name); } } - - -/* End of file mssql_result.php */ -/* Location: ./system/database/drivers/mssql/mssql_result.php */ \ No newline at end of file diff --git a/src/system/database/drivers/sqlsrv/sqlsrv_utility.php b/src/system/database/drivers/sqlsrv/sqlsrv_utility.php index 3975e85c..726fe3ea 100644 --- a/src/system/database/drivers/sqlsrv/sqlsrv_utility.php +++ b/src/system/database/drivers/sqlsrv/sqlsrv_utility.php @@ -1,89 +1,77 @@ -db->display_error('db_unsuported_feature'); + return $this->db->display_error('db_unsupported_feature'); } } - -/* End of file mssql_utility.php */ -/* Location: ./system/database/drivers/mssql/mssql_utility.php */ \ No newline at end of file diff --git a/src/system/database/index.html b/src/system/database/index.html index c942a79c..b702fbc3 100644 --- a/src/system/database/index.html +++ b/src/system/database/index.html @@ -1,3 +1,4 @@ + 403 Forbidden @@ -7,4 +8,4 @@

Directory access is forbidden.

- \ No newline at end of file + diff --git a/src/system/fonts/index.html b/src/system/fonts/index.html index c942a79c..b702fbc3 100644 --- a/src/system/fonts/index.html +++ b/src/system/fonts/index.html @@ -1,3 +1,4 @@ + 403 Forbidden @@ -7,4 +8,4 @@

Directory access is forbidden.

- \ No newline at end of file + diff --git a/src/system/helpers/array_helper.php b/src/system/helpers/array_helper.php index 04c68734..3fdccf90 100644 --- a/src/system/helpers/array_helper.php +++ b/src/system/helpers/array_helper.php @@ -1,20 +1,41 @@ - '', 'img_path' => '', 'img_url' => '', 'img_width' => '150', 'img_height' => '30', 'font_path' => '', 'expiration' => 7200); + $defaults = array( + 'word' => '', + 'img_path' => '', + 'img_url' => '', + 'img_width' => '150', + 'img_height' => '30', + 'font_path' => '', + 'expiration' => 7200, + 'word_length' => 8, + 'font_size' => 16, + 'img_id' => '', + 'pool' => '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', + 'colors' => array( + 'background' => array(255,255,255), + 'border' => array(153,102,102), + 'text' => array(204,153,153), + 'grid' => array(255,182,182) + ) + ); foreach ($defaults as $key => $val) { - if ( ! is_array($data)) + if ( ! is_array($data) && empty($$key)) { - if ( ! isset($$key) OR $$key == '') - { - $$key = $val; - } + $$key = $val; } else { - $$key = ( ! isset($data[$key])) ? $val : $data[$key]; + $$key = isset($data[$key]) ? $data[$key] : $val; } } - 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')) + if ($img_path === '' OR $img_url === '' + OR ! is_dir($img_path) OR ! is_really_writable($img_path) + OR ! extension_loaded('gd')) { return FALSE; } @@ -83,21 +105,14 @@ if ( ! function_exists('create_captcha')) // Remove old images // ----------------------------------- - list($usec, $sec) = explode(" ", microtime()); - $now = ((float)$usec + (float)$sec); + $now = microtime(TRUE); $current_dir = @opendir($img_path); - while ($filename = @readdir($current_dir)) { - if ($filename != "." and $filename != ".." and $filename != "index.html") + if (substr($filename, -4) === '.jpg' && (str_replace('.jpg', '', $filename) + $expiration) < $now) { - $name = str_replace(".jpg", "", $filename); - - if (($name + $expiration) < $now) - { - @unlink($img_path.$filename); - } + @unlink($img_path.$filename); } } @@ -107,141 +122,219 @@ if ( ! function_exists('create_captcha')) // Do we have a "word" yet? // ----------------------------------- - if ($word == '') - { - $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + if (empty($word)) + { + $word = ''; + $pool_length = strlen($pool); + $rand_max = $pool_length - 1; - $str = ''; - for ($i = 0; $i < 8; $i++) + // PHP7 or a suitable polyfill + if (function_exists('random_int')) { - $str .= substr($pool, mt_rand(0, strlen($pool) -1), 1); + try + { + for ($i = 0; $i < $word_length; $i++) + { + $word .= $pool[random_int(0, $rand_max)]; + } + } + catch (Exception $e) + { + // This means fallback to the next possible + // alternative to random_int() + $word = ''; + } + } + } + + if (empty($word)) + { + // Nobody will have a larger character pool than + // 256 characters, but let's handle it just in case ... + // + // No, I do not care that the fallback to mt_rand() can + // handle it; if you trigger this, you're very obviously + // trying to break it. -- Narf + if ($pool_length > 256) + { + return FALSE; } - $word = $str; - } + // We'll try using the operating system's PRNG first, + // which we can access through CI_Security::get_random_bytes() + $security = get_instance()->security; + + // To avoid numerous get_random_bytes() calls, we'll + // just try fetching as much bytes as we need at once. + if (($bytes = $security->get_random_bytes($pool_length)) !== FALSE) + { + $byte_index = $word_index = 0; + while ($word_index < $word_length) + { + // Do we have more random data to use? + // It could be exhausted by previous iterations + // ignoring bytes higher than $rand_max. + if ($byte_index === $pool_length) + { + // No failures should be possible if the + // first get_random_bytes() call didn't + // return FALSE, but still ... + for ($i = 0; $i < 5; $i++) + { + if (($bytes = $security->get_random_bytes($pool_length)) === FALSE) + { + continue; + } + + $byte_index = 0; + break; + } + + if ($bytes === FALSE) + { + // Sadly, this means fallback to mt_rand() + $word = ''; + break; + } + } + + list(, $rand_index) = unpack('C', $bytes[$byte_index++]); + if ($rand_index > $rand_max) + { + continue; + } + + $word .= $pool[$rand_index]; + $word_index++; + } + } + } + + if (empty($word)) + { + for ($i = 0; $i < $word_length; $i++) + { + $word .= $pool[mt_rand(0, $rand_max)]; + } + } + elseif ( ! is_string($word)) + { + $word = (string) $word; + } // ----------------------------------- // 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); + $angle = ($length >= 6) ? mt_rand(-($length-6), ($length-6)) : 0; + $x_axis = mt_rand(6, (360/$length)-16); + $y_axis = ($angle >= 0) ? mt_rand($img_height, $img_width) : mt_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); - } + $im = function_exists('imagecreatetruecolor') + ? imagecreatetruecolor($img_width, $img_height) + : 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); + is_array($colors) OR $colors = $defaults['colors']; - // ----------------------------------- - // Create the rectangle - // ----------------------------------- + foreach (array_keys($defaults['colors']) as $key) + { + // Check for a possible missing value + is_array($colors[$key]) OR $colors[$key] = $defaults['colors'][$key]; + $colors[$key] = imagecolorallocate($im, $colors[$key][0], $colors[$key][1], $colors[$key][2]); + } - ImageFilledRectangle($im, 0, 0, $img_width, $img_height, $bg_color); + // Create the rectangle + ImageFilledRectangle($im, 0, 0, $img_width, $img_height, $colors['background']); // ----------------------------------- // Create the spiral pattern // ----------------------------------- - $theta = 1; $thetac = 7; $radius = 16; $circles = 20; $points = 32; - for ($i = 0; $i < ($circles * $points) - 1; $i++) + for ($i = 0, $cp = ($circles * $points) - 1; $i < $cp; $i++) { - $theta = $theta + $thetac; - $rad = $radius * ($i / $points ); + $theta += $thetac; + $rad = $radius * ($i / $points); $x = ($rad * cos($theta)) + $x_axis; $y = ($rad * sin($theta)) + $y_axis; - $theta = $theta + $thetac; + $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; + $y1 = ($rad1 * sin($theta)) + $y_axis; + imageline($im, $x, $y, $x1, $y1, $colors['grid']); + $theta -= $thetac; } // ----------------------------------- // Write the text // ----------------------------------- - $use_font = ($font_path != '' AND file_exists($font_path) AND function_exists('imagettftext')) ? TRUE : FALSE; - - if ($use_font == FALSE) + $use_font = ($font_path !== '' && file_exists($font_path) && function_exists('imagettftext')); + if ($use_font === FALSE) { - $font_size = 5; - $x = rand(0, $img_width/($length/3)); + ($font_size > 5) && $font_size = 5; + $x = mt_rand(0, $img_width / ($length / 3)); $y = 0; } else { - $font_size = 16; - $x = rand(0, $img_width/($length/1.5)); - $y = $font_size+2; + ($font_size > 30) && $font_size = 30; + $x = mt_rand(0, $img_width / ($length / 1.5)); + $y = $font_size + 2; } - for ($i = 0; $i < strlen($word); $i++) + for ($i = 0; $i < $length; $i++) { - if ($use_font == FALSE) + 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); + $y = mt_rand(0 , $img_height / 2); + imagestring($im, $font_size, $x, $y, $word[$i], $colors['text']); + $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)); + $y = mt_rand($img_height / 2, $img_height - 3); + imagettftext($im, $font_size, $angle, $x, $y, $colors['text'], $font_path, $word[$i]); $x += $font_size; } } - - // ----------------------------------- - // Create the border - // ----------------------------------- - - imagerectangle($im, 0, 0, $img_width-1, $img_height-1, $border_color); + // Create the border + imagerectangle($im, 0, 0, $img_width - 1, $img_height - 1, $colors['border']); // ----------------------------------- // Generate the image // ----------------------------------- + $img_url = rtrim($img_url, '/').'/'; - $img_name = $now.'.jpg'; - - ImageJPEG($im, $img_path.$img_name); - - $img = "\""; + if (function_exists('imagejpeg')) + { + $img_filename = $now.'.jpg'; + imagejpeg($im, $img_path.$img_filename); + } + elseif (function_exists('imagepng')) + { + $img_filename = $now.'.png'; + imagepng($im, $img_path.$img_filename); + } + else + { + return FALSE; + } + $img = ' '; ImageDestroy($im); - return array('word' => $word, 'time' => $now, 'image' => $img); + return array('word' => $word, 'time' => $now, 'image' => $img, 'filename' => $img_filename); } } - -// ------------------------------------------------------------------------ - -/* End of file captcha_helper.php */ -/* Location: ./system/heleprs/captcha_helper.php */ \ No newline at end of file diff --git a/src/system/helpers/cookie_helper.php b/src/system/helpers/cookie_helper.php index e8f5d083..ca432449 100644 --- a/src/system/helpers/cookie_helper.php +++ b/src/system/helpers/cookie_helper.php @@ -1,20 +1,41 @@ -input->set_cookie($name, $value, $expire, $domain, $path, $prefix, $secure); + get_instance()->input->set_cookie($name, $value, $expire, $domain, $path, $prefix, $secure, $httponly); } } // -------------------------------------------------------------------- -/** - * 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) + /** + * Fetch an item from the COOKIE array + * + * @param string + * @param bool + * @return mixed + */ + function get_cookie($index, $xss_clean = NULL) { - $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); + is_bool($xss_clean) OR $xss_clean = (config_item('global_xss_filtering') === TRUE); + $prefix = isset($_COOKIE[$index]) ? '' : config_item('cookie_prefix'); + return get_instance()->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 = '') + /** + * 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 + */ + 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/src/system/helpers/date_helper.php b/src/system/helpers/date_helper.php index 1de01320..c43209f0 100644 --- a/src/system/helpers/date_helper.php +++ b/src/system/helpers/date_helper.php @@ -1,20 +1,41 @@ -config->item('time_reference')) == 'gmt') + if (empty($timezone)) { - $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; + $timezone = config_item('time_reference'); } - else + + if ($timezone === 'local' OR $timezone === date_default_timezone_get()) { return time(); } + + $datetime = new DateTime('now', new DateTimeZone($timezone)); + sscanf($datetime->format('j-n-Y G:i:s'), '%d-%d-%d %d:%d:%d', $day, $month, $year, $hour, $minute, $second); + + return mktime($hour, $minute, $second, $month, $day, $year); } } // ------------------------------------------------------------------------ -/** - * 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')) { + /** + * 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. + * + * @param string + * @param int + * @return int + */ function mdate($datestr = '', $time = '') { - if ($datestr == '') + if ($datestr === '') + { return ''; - - if ($time == '') + } + elseif (empty($time)) + { $time = now(); + } + + $datestr = str_replace( + '%\\', + '', + preg_replace('/([a-z]+?){1}/i', '\\\\\\1', $datestr) + ); - $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 = '') + /** + * Standard Date + * + * Returns a date formatted according to the submitted standard. + * + * As of PHP 5.2, the DateTime extension provides constants that + * serve for the exact same purpose and are used with date(). + * + * @todo Remove in version 3.1+. + * @deprecated 3.0.0 Use PHP's native date() instead. + * @link http://www.php.net/manual/en/class.datetime.php#datetime.constants.types + * + * @example date(DATE_RFC822, now()); // default + * @example date(DATE_W3C, $time); // a different format and time + * + * @param string $fmt = 'DATE_RFC822' the chosen format + * @param int $time = NULL Unix timestamp + * @return string + */ + function standard_date($fmt = 'DATE_RFC822', $time = NULL) { - $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 (empty($time)) + { + $time = now(); + } - if ( ! isset($formats[$fmt])) + // Procedural style pre-defined constants from the DateTime extension + if (strpos($fmt, 'DATE_') !== 0 OR defined($fmt) === FALSE) { return FALSE; } - return mdate($formats[$fmt], $time); + return date(constant($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 = '') + /** + * Timespan + * + * Returns a span of seconds in this format: + * 10 days 14 hours 36 minutes 47 seconds + * + * @param int a number of seconds + * @param int Unix timestamp + * @param int a number of display units + * @return string + */ + function timespan($seconds = 1, $time = '', $units = 7) { $CI =& get_instance(); $CI->lang->load('date'); - if ( ! is_numeric($seconds)) - { - $seconds = 1; - } + is_numeric($seconds) OR $seconds = 1; + is_numeric($time) OR $time = time(); + is_numeric($units) OR $units = 7; - if ( ! is_numeric($time)) - { - $time = time(); - } + $seconds = ($time <= $seconds) ? 1 : $time - $seconds; - if ($time <= $seconds) - { - $seconds = 1; - } - else - { - $seconds = $time - $seconds; - } - - $str = ''; - $years = floor($seconds / 31536000); + $str = array(); + $years = floor($seconds / 31557600); if ($years > 0) { - $str .= $years.' '.$CI->lang->line((($years > 1) ? 'date_years' : 'date_year')).', '; + $str[] = $years.' '.$CI->lang->line($years > 1 ? 'date_years' : 'date_year'); } - $seconds -= $years * 31536000; - $months = floor($seconds / 2628000); + $seconds -= $years * 31557600; + $months = floor($seconds / 2629743); - if ($years > 0 OR $months > 0) + if (count($str) < $units && ($years > 0 OR $months > 0)) { if ($months > 0) { - $str .= $months.' '.$CI->lang->line((($months > 1) ? 'date_months' : 'date_month')).', '; + $str[] = $months.' '.$CI->lang->line($months > 1 ? 'date_months' : 'date_month'); } - $seconds -= $months * 2628000; + $seconds -= $months * 2629743; } $weeks = floor($seconds / 604800); - if ($years > 0 OR $months > 0 OR $weeks > 0) + if (count($str) < $units && ($years > 0 OR $months > 0 OR $weeks > 0)) { if ($weeks > 0) { - $str .= $weeks.' '.$CI->lang->line((($weeks > 1) ? 'date_weeks' : 'date_week')).', '; + $str[] = $weeks.' '.$CI->lang->line($weeks > 1 ? 'date_weeks' : 'date_week'); } $seconds -= $weeks * 604800; @@ -207,11 +221,11 @@ if ( ! function_exists('timespan')) $days = floor($seconds / 86400); - if ($months > 0 OR $weeks > 0 OR $days > 0) + if (count($str) < $units && ($months > 0 OR $weeks > 0 OR $days > 0)) { if ($days > 0) { - $str .= $days.' '.$CI->lang->line((($days > 1) ? 'date_days' : 'date_day')).', '; + $str[] = $days.' '.$CI->lang->line($days > 1 ? 'date_days' : 'date_day'); } $seconds -= $days * 86400; @@ -219,11 +233,11 @@ if ( ! function_exists('timespan')) $hours = floor($seconds / 3600); - if ($days > 0 OR $hours > 0) + if (count($str) < $units && ($days > 0 OR $hours > 0)) { if ($hours > 0) { - $str .= $hours.' '.$CI->lang->line((($hours > 1) ? 'date_hours' : 'date_hour')).', '; + $str[] = $hours.' '.$CI->lang->line($hours > 1 ? 'date_hours' : 'date_hour'); } $seconds -= $hours * 3600; @@ -231,55 +245,63 @@ if ( ! function_exists('timespan')) $minutes = floor($seconds / 60); - if ($days > 0 OR $hours > 0 OR $minutes > 0) + if (count($str) < $units && ($days > 0 OR $hours > 0 OR $minutes > 0)) { if ($minutes > 0) { - $str .= $minutes.' '.$CI->lang->line((($minutes > 1) ? 'date_minutes' : 'date_minute')).', '; + $str[] = $minutes.' '.$CI->lang->line($minutes > 1 ? 'date_minutes' : 'date_minute'); } $seconds -= $minutes * 60; } - if ($str == '') + if (count($str) === 0) { - $str .= $seconds.' '.$CI->lang->line((($seconds > 1) ? 'date_seconds' : 'date_second')).', '; + $str[] = $seconds.' '.$CI->lang->line($seconds > 1 ? 'date_seconds' : 'date_second'); } - return substr(trim($str), 0, -1); + return implode(', ', $str); } } // ------------------------------------------------------------------------ -/** - * 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')) { + /** + * 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. + * + * @param int a numeric month + * @param int a numeric year + * @return int + */ function days_in_month($month = 0, $year = '') { if ($month < 1 OR $month > 12) { return 0; } - - if ( ! is_numeric($year) OR strlen($year) != 4) + elseif ( ! is_numeric($year) OR strlen($year) !== 4) { $year = date('Y'); } + if (defined('CAL_GREGORIAN')) + { + return cal_days_in_month(CAL_GREGORIAN, $month, $year); + } + + if ($year >= 1970) + { + return (int) date('t', mktime(12, 0, 0, $month, 1, $year)); + } + if ($month == 2) { - if ($year % 400 == 0 OR ($year % 4 == 0 AND $year % 100 != 0)) + if ($year % 400 === 0 OR ($year % 4 === 0 && $year % 100 !== 0)) { return 29; } @@ -292,112 +314,110 @@ if ( ! function_exists('days_in_month')) // ------------------------------------------------------------------------ -/** - * Converts a local Unix timestamp to GMT - * - * @access public - * @param integer Unix timestamp - * @return integer - */ if ( ! function_exists('local_to_gmt')) { + /** + * Converts a local Unix timestamp to GMT + * + * @param int Unix timestamp + * @return int + */ function local_to_gmt($time = '') { - if ($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)); + return mktime( + gmdate('G', $time), + gmdate('i', $time), + gmdate('s', $time), + gmdate('n', $time), + gmdate('j', $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')) { + /** + * 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 + * + * @param int Unix timestamp + * @param string timezone + * @param bool whether DST is active + * @return int + */ function gmt_to_local($time = '', $timezone = 'UTC', $dst = FALSE) { - if ($time == '') + if ($time === '') { return now(); } $time += timezones($timezone) * 3600; - if ($dst == TRUE) - { - $time += 3600; - } - - return $time; + return ($dst === TRUE) ? $time + 3600 : $time; } } // ------------------------------------------------------------------------ -/** - * Converts a MySQL Timestamp to Unix - * - * @access public - * @param integer Unix timestamp - * @return integer - */ if ( ! function_exists('mysql_to_unix')) { + /** + * Converts a MySQL Timestamp to Unix + * + * @param int MySQL timestamp YYYY-MM-DD HH:MM:SS + * @return int Unix timstamp + */ 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); + $time = str_replace(array('-', ':', ' '), '', $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) - ); + 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')) { + /** + * Unix to "Human" + * + * Formats Unix timestamp to the following prototype: 2006-08-21 11:35 PM + * + * @param int Unix timestamp + * @param bool whether to show seconds + * @param string format: us or euro + * @return string + */ function unix_to_human($time = '', $seconds = FALSE, $fmt = 'us') { - $r = date('Y', $time).'-'.date('m', $time).'-'.date('d', $time).' '; + $r = date('Y', $time).'-'.date('m', $time).'-'.date('d', $time).' '; - if ($fmt == 'us') + if ($fmt === 'us') { $r .= date('h', $time).':'.date('i', $time); } @@ -411,9 +431,9 @@ if ( ! function_exists('unix_to_human')) $r .= ':'.date('s', $time); } - if ($fmt == 'us') + if ($fmt === 'us') { - $r .= ' '.date('A', $time); + return $r.' '.date('A', $time); } return $r; @@ -422,67 +442,46 @@ if ( ! function_exists('unix_to_human')) // ------------------------------------------------------------------------ -/** - * 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')) { + /** + * Convert "human" date to GMT + * + * Reverses the above process + * + * @param string format: us or euro + * @return int + */ function human_to_unix($datestr = '') { - if ($datestr == '') + if ($datestr === '') { return FALSE; } - $datestr = trim($datestr); - $datestr = preg_replace("/\040+/", ' ', $datestr); + $datestr = preg_replace('/\040+/', ' ', trim($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)) + if ( ! preg_match('/^(\d{2}|\d{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); + sscanf($datestr, '%d-%d-%d %s %s', $year, $month, $day, $time, $ampm); + sscanf($time, '%d:%d:%d', $hour, $min, $sec); + isset($sec) OR $sec = 0; - $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'])) + if (isset($ampm)) { - $sec = (strlen($ex['2']) == 1) ? '0'.$ex['2'] : $ex['2']; - } - else - { - // Unless specified, seconds get set to zero. - $sec = '00'; - } + $ampm = strtolower($ampm); - 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; + if ($ampm[0] === 'p' && $hour < 12) + { + $hour += 12; + } + elseif ($ampm[0] === 'a' && $hour === 12) + { + $hour = 0; + } } return mktime($hour, $min, $sec, $month, $day, $year); @@ -491,122 +490,307 @@ if ( ! function_exists('human_to_unix')) // ------------------------------------------------------------------------ -/** - * 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')) +if ( ! function_exists('nice_date')) { - function timezone_menu($default = 'UTC', $class = "", $name = 'timezones') + /** + * Turns many "reasonably-date-like" strings into something + * that is actually useful. This only works for dates after unix epoch. + * + * @param string The terribly formatted date-like string + * @param string Date format to return (same as php date function) + * @return string + */ + function nice_date($bad_date = '', $format = FALSE) { - $CI =& get_instance(); - $CI->lang->load('date'); - - if ($default == 'GMT') - $default = 'UTC'; - - $menu = '"; + // Date Like: YYYYMMDD + if (preg_match('/^(\d{2})\d{2}(\d{4})$/i', $bad_date, $matches)) + { + return date($format, strtotime($matches[1].'/01/'.$matches[2])); + } - return $menu; + // Date Like: MM-DD-YYYY __or__ M-D-YYYY (or anything in between) + if (preg_match('/^(\d{1,2})-(\d{1,2})-(\d{4})$/i', $bad_date, $matches)) + { + return date($format, strtotime($matches[3].'-'.$matches[1].'-'.$matches[2])); + } + + // Any other kind of string, when converted into UNIX time, + // produces "0 seconds after epoc..." is probably bad... + // return "Invalid Date". + if (date('U', strtotime($bad_date)) === '0') + { + return 'Invalid Date'; + } + + // It's probably a valid-ish date format already + return date($format, strtotime($bad_date)); + } +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('timezone_menu')) +{ + /** + * Timezone Menu + * + * Generates a drop-down menu of timezones. + * + * @param string timezone + * @param string classname + * @param string menu name + * @param mixed attributes + * @return string + */ + function timezone_menu($default = 'UTC', $class = '', $name = 'timezones', $attributes = '') + { + $CI =& get_instance(); + $CI->lang->load('date'); + + $default = ($default === 'GMT') ? 'UTC' : $default; + + $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')) { + /** + * Timezones + * + * Returns an array of timezones. This is a helper function + * for various other ones in this library + * + * @param string timezone + * @return string + */ 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 - ); + '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 == '') + if ($tz === '') { return $zones; } - if ($tz == 'GMT') - $tz = 'UTC'; - - return ( ! isset($zones[$tz])) ? 0 : $zones[$tz]; + return isset($zones[$tz]) ? $zones[$tz] : 0; } } +// ------------------------------------------------------------------------ -/* End of file date_helper.php */ -/* Location: ./system/helpers/date_helper.php */ \ No newline at end of file +if ( ! function_exists('date_range')) +{ + /** + * Date range + * + * Returns a list of dates within a specified period. + * + * @param int unix_start UNIX timestamp of period start date + * @param int unix_end|days UNIX timestamp of period end date + * or interval in days. + * @param mixed is_unix Specifies whether the second parameter + * is a UNIX timestamp or a day interval + * - TRUE or 'unix' for a timestamp + * - FALSE or 'days' for an interval + * @param string date_format Output date format, same as in date() + * @return array + */ + function date_range($unix_start = '', $mixed = '', $is_unix = TRUE, $format = 'Y-m-d') + { + if ($unix_start == '' OR $mixed == '' OR $format == '') + { + return FALSE; + } + + $is_unix = ! ( ! $is_unix OR $is_unix === 'days'); + + // Validate input and try strtotime() on invalid timestamps/intervals, just in case + if ( ( ! ctype_digit((string) $unix_start) && ($unix_start = @strtotime($unix_start)) === FALSE) + OR ( ! ctype_digit((string) $mixed) && ($is_unix === FALSE OR ($mixed = @strtotime($mixed)) === FALSE)) + OR ($is_unix === TRUE && $mixed < $unix_start)) + { + return FALSE; + } + + if ($is_unix && ($unix_start == $mixed OR date($format, $unix_start) === date($format, $mixed))) + { + return array(date($format, $unix_start)); + } + + $range = array(); + + /* NOTE: Even though the DateTime object has many useful features, it appears that + * it doesn't always handle properly timezones, when timestamps are passed + * directly to its constructor. Neither of the following gave proper results: + * + * new DateTime('') + * new DateTime('', '') + * + * --- available in PHP 5.3: + * + * DateTime::createFromFormat('', '') + * DateTime::createFromFormat('', '', 'setTimestamp($unix_start); + if ($is_unix) + { + $arg = new DateTime(); + $arg->setTimestamp($mixed); + } + else + { + $arg = (int) $mixed; + } + + $period = new DatePeriod($from, new DateInterval('P1D'), $arg); + foreach ($period as $date) + { + $range[] = $date->format($format); + } + + /* If a period end date was passed to the DatePeriod constructor, it might not + * be in our results. Not sure if this is a bug or it's just possible because + * the end date might actually be less than 24 hours away from the previously + * generated DateTime object, but either way - we have to append it manually. + */ + if ( ! is_int($arg) && $range[count($range) - 1] !== $arg->format($format)) + { + $range[] = $arg->format($format); + } + + return $range; + } + + $from->setDate(date('Y', $unix_start), date('n', $unix_start), date('j', $unix_start)); + $from->setTime(date('G', $unix_start), date('i', $unix_start), date('s', $unix_start)); + if ($is_unix) + { + $arg = new DateTime(); + $arg->setDate(date('Y', $mixed), date('n', $mixed), date('j', $mixed)); + $arg->setTime(date('G', $mixed), date('i', $mixed), date('s', $mixed)); + } + else + { + $arg = (int) $mixed; + } + $range[] = $from->format($format); + + if (is_int($arg)) // Day intervals + { + do + { + $from->modify('+1 day'); + $range[] = $from->format($format); + } + while (--$arg > 0); + } + else // end date UNIX timestamp + { + for ($from->modify('+1 day'), $end_check = $arg->format('Ymd'); $from->format('Ymd') < $end_check; $from->modify('+1 day')) + { + $range[] = $from->format($format); + } + + // Our loop only appended dates prior to our end date + $range[] = $arg->format($format); + } + + return $range; + } +} diff --git a/src/system/helpers/directory_helper.php b/src/system/helpers/directory_helper.php index 1f04eec8..cdc4c16b 100644 --- a/src/system/helpers/directory_helper.php +++ b/src/system/helpers/directory_helper.php @@ -1,20 +1,41 @@ - 0) && @is_dir($source_dir.$file)) + is_dir($source_dir.$file) && $file .= DIRECTORY_SEPARATOR; + + if (($directory_depth < 1 OR $new_depth > 0) && is_dir($source_dir.$file)) { - $filedata[$file] = directory_map($source_dir.$file.DIRECTORY_SEPARATOR, $new_depth, $hidden); + $filedata[$file] = directory_map($source_dir.$file, $new_depth, $hidden); } else { @@ -75,7 +99,3 @@ if ( ! function_exists('directory_map')) return FALSE; } } - - -/* End of file directory_helper.php */ -/* Location: ./system/helpers/directory_helper.php */ \ No newline at end of file diff --git a/src/system/helpers/download_helper.php b/src/system/helpers/download_helper.php index 4739d5c8..a6463dfd 100644 --- a/src/system/helpers/download_helper.php +++ b/src/system/helpers/download_helper.php @@ -1,20 +1,41 @@ - 0) - { - $data =& fread($fp, filesize($file)); - } - - flock($fp, LOCK_UN); - fclose($fp); - - return $data; + return @file_get_contents($file); } } // ------------------------------------------------------------------------ -/** - * 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) + /** + * Write File + * + * Writes data to the file specified in the path. + * Creates a new file if non-existent. + * + * @param string $path File path + * @param string $data Data to write + * @param string $mode fopen() mode (default: 'wb') + * @return bool + */ + function write_file($path, $data, $mode = 'wb') { if ( ! $fp = @fopen($path, $mode)) { @@ -94,35 +90,44 @@ if ( ! function_exists('write_file')) } flock($fp, LOCK_EX); - fwrite($fp, $data); + + for ($result = $written = 0, $length = strlen($data); $written < $length; $written += $result) + { + if (($result = fwrite($fp, substr($data, $written))) === FALSE) + { + break; + } + } + flock($fp, LOCK_UN); fclose($fp); - return TRUE; + return is_int($result); } } // ------------------------------------------------------------------------ -/** - * 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) + /** + * 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. + * + * @param string $path File path + * @param bool $del_dir Whether to delete any directories found in the path + * @param bool $htdocs Whether to skip deleting .htaccess and index page files + * @param int $_level Current directory depth level (default: 0; internal use only) + * @return bool + */ + function delete_files($path, $del_dir = FALSE, $htdocs = FALSE, $_level = 0) { // Trim the trailing slash - $path = rtrim($path, DIRECTORY_SEPARATOR); + $path = rtrim($path, '/\\'); if ( ! $current_dir = @opendir($path)) { @@ -131,49 +136,42 @@ if ( ! function_exists('delete_files')) while (FALSE !== ($filename = @readdir($current_dir))) { - if ($filename != "." and $filename != "..") + if ($filename !== '.' && $filename !== '..') { - if (is_dir($path.DIRECTORY_SEPARATOR.$filename)) + if (is_dir($path.DIRECTORY_SEPARATOR.$filename) && $filename[0] !== '.') { - // Ignore empty folders - if (substr($filename, 0, 1) != '.') - { - delete_files($path.DIRECTORY_SEPARATOR.$filename, $del_dir, $level + 1); - } + delete_files($path.DIRECTORY_SEPARATOR.$filename, $del_dir, $htdocs, $_level + 1); } - else + elseif ($htdocs !== TRUE OR ! preg_match('/^(\.htaccess|index\.(html|htm|php)|web\.config)$/i', $filename)) { - unlink($path.DIRECTORY_SEPARATOR.$filename); + @unlink($path.DIRECTORY_SEPARATOR.$filename); } } } - @closedir($current_dir); - if ($del_dir == TRUE AND $level > 0) - { - return @rmdir($path); - } + closedir($current_dir); - return TRUE; + return ($del_dir === TRUE && $_level > 0) + ? @rmdir($path) + : 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')) { + /** + * 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. + * + * @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 + */ function get_filenames($source_dir, $include_path = FALSE, $_recursion = FALSE) { static $_filedata = array(); @@ -189,42 +187,41 @@ if ( ! function_exists('get_filenames')) while (FALSE !== ($file = readdir($fp))) { - if (@is_dir($source_dir.$file) && strncmp($file, '.', 1) !== 0) + if (is_dir($source_dir.$file) && $file[0] !== '.') { get_filenames($source_dir.$file.DIRECTORY_SEPARATOR, $include_path, TRUE); } - elseif (strncmp($file, '.', 1) !== 0) + elseif ($file[0] !== '.') { - $_filedata[] = ($include_path == TRUE) ? $source_dir.$file : $file; + $_filedata[] = ($include_path === TRUE) ? $source_dir.$file : $file; } } + + closedir($fp); return $_filedata; } - else - { - return FALSE; - } + + 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')) { + /** + * 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. + * + * @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 + */ function get_dir_file_info($source_dir, $top_level_only = TRUE, $_recursion = FALSE) { static $_filedata = array(); @@ -242,46 +239,43 @@ if ( ! function_exists('get_dir_file_info')) // Used to be foreach (scandir($source_dir, 1) as $file), but 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) + if (is_dir($source_dir.$file) && $file[0] !== '.' && $top_level_only === FALSE) { get_dir_file_info($source_dir.$file.DIRECTORY_SEPARATOR, $top_level_only, TRUE); } - elseif (strncmp($file, '.', 1) !== 0) + elseif ($file[0] !== '.') { $_filedata[$file] = get_file_info($source_dir.$file); $_filedata[$file]['relative_path'] = $relative_path; } } + closedir($fp); return $_filedata; } - else - { - return FALSE; - } + + 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')) { + /** + * 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. + * + * @param string path to file + * @param mixed array or comma separated string of information returned + * @return array + */ function get_file_info($file, $returned_values = array('name', 'server_path', 'size', 'date')) { - if ( ! file_exists($file)) { return FALSE; @@ -297,7 +291,7 @@ if ( ! function_exists('get_file_info')) switch ($key) { case 'name': - $fileinfo['name'] = substr(strrchr($file, DIRECTORY_SEPARATOR), 1); + $fileinfo['name'] = basename($file); break; case 'server_path': $fileinfo['server_path'] = $file; @@ -312,8 +306,7 @@ if ( ! function_exists('get_file_info')) $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); + $fileinfo['writable'] = is_really_writable($file); break; case 'executable': $fileinfo['executable'] = is_executable($file); @@ -330,104 +323,87 @@ if ( ! function_exists('get_file_info')) // -------------------------------------------------------------------- -/** - * 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) + /** + * 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 + * + * @param string $filename File name + * @return string + */ + function get_mime_by_extension($filename) { - $extension = strtolower(substr(strrchr($file, '.'), 1)); - - global $mimes; + static $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'); - } + $mimes = get_mimes(); - if ( ! is_array($mimes)) + if (empty($mimes)) { return FALSE; } } - if (array_key_exists($extension, $mimes)) + $extension = strtolower(substr(strrchr($filename, '.'), 1)); + + if (isset($mimes[$extension])) { - if (is_array($mimes[$extension])) - { - // Multiple mime types, just give the first one - return current($mimes[$extension]); - } - else - { - return $mimes[$extension]; - } - } - else - { - return FALSE; + return is_array($mimes[$extension]) + ? current($mimes[$extension]) // Multiple mime types, just give the first one + : $mimes[$extension]; } + + 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')) { + /** + * Symbolic Permissions + * + * Takes a numeric value representing a file's permissions and returns + * standard symbolic notation representing that value + * + * @param int $perms Permissions + * @return string + */ function symbolic_permissions($perms) { - if (($perms & 0xC000) == 0xC000) + if (($perms & 0xC000) === 0xC000) { $symbolic = 's'; // Socket } - elseif (($perms & 0xA000) == 0xA000) + elseif (($perms & 0xA000) === 0xA000) { $symbolic = 'l'; // Symbolic Link } - elseif (($perms & 0x8000) == 0x8000) + elseif (($perms & 0x8000) === 0x8000) { $symbolic = '-'; // Regular } - elseif (($perms & 0x6000) == 0x6000) + elseif (($perms & 0x6000) === 0x6000) { $symbolic = 'b'; // Block special } - elseif (($perms & 0x4000) == 0x4000) + elseif (($perms & 0x4000) === 0x4000) { $symbolic = 'd'; // Directory } - elseif (($perms & 0x2000) == 0x2000) + elseif (($perms & 0x2000) === 0x2000) { $symbolic = 'c'; // Character special } - elseif (($perms & 0x1000) == 0x1000) + elseif (($perms & 0x1000) === 0x1000) { $symbolic = 'p'; // FIFO pipe } @@ -437,19 +413,19 @@ if ( ! function_exists('symbolic_permissions')) } // Owner - $symbolic .= (($perms & 0x0100) ? 'r' : '-'); - $symbolic .= (($perms & 0x0080) ? 'w' : '-'); - $symbolic .= (($perms & 0x0040) ? (($perms & 0x0800) ? 's' : 'x' ) : (($perms & 0x0800) ? 'S' : '-')); + $symbolic .= (($perms & 0x0100) ? 'r' : '-') + .(($perms & 0x0080) ? 'w' : '-') + .(($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' : '-')); + $symbolic .= (($perms & 0x0020) ? 'r' : '-') + .(($perms & 0x0010) ? 'w' : '-') + .(($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' : '-')); + $symbolic .= (($perms & 0x0004) ? 'r' : '-') + .(($perms & 0x0002) ? 'w' : '-') + .(($perms & 0x0001) ? (($perms & 0x0200) ? 't' : 'x' ) : (($perms & 0x0200) ? 'T' : '-')); return $symbolic; } @@ -457,24 +433,19 @@ if ( ! function_exists('symbolic_permissions')) // -------------------------------------------------------------------- -/** - * 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')) { + /** + * Octal Permissions + * + * Takes a numeric value representing a file's permissions and returns + * a three character string representing the file's octal permissions + * + * @param int $perms Permissions + * @return string + */ 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/src/system/helpers/form_helper.php b/src/system/helpers/form_helper.php index f07c45cd..3e103952 100644 --- a/src/system/helpers/form_helper.php +++ b/src/system/helpers/form_helper.php @@ -1,20 +1,41 @@ -config->site_url($CI->uri->uri_string()); } - // If an action is not a full URL then turn it into one - if ($action && strpos($action, '://') === FALSE) + elseif (strpos($action, '://') === FALSE) { $action = $CI->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()); + $attributes = _attributes_to_string($attributes); - $form = '
\n"; - // Add CSRF field if enabled, but leave it out for GET requests and requests to external websites - if ($CI->config->item('csrf_protection') === TRUE AND ! (strpos($action, $CI->config->base_url()) === FALSE OR strpos($form, 'method="get"'))) + // Add CSRF field if enabled, but leave it out for GET requests and requests to external websites + if ($CI->config->item('csrf_protection') === TRUE && strpos($action, $CI->config->base_url()) !== FALSE && ! stripos($form, 'method="get"')) { $hidden[$CI->security->get_csrf_token_name()] = $CI->security->get_csrf_hash(); } - if (is_array($hidden) AND count($hidden) > 0) + if (is_array($hidden)) { - $form .= sprintf("
%s
", form_hidden($hidden)); + foreach ($hidden as $name => $value) + { + $form .= ''."\n"; + } } return $form; @@ -82,19 +110,18 @@ if ( ! function_exists('form_open')) // ------------------------------------------------------------------------ -/** - * 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')) { + /** + * Form Declaration - Multipart type + * + * Creates the opening portion of the form, but with "multipart/form-data". + * + * @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 + */ function form_open_multipart($action = '', $attributes = array(), $hidden = array()) { if (is_string($attributes)) @@ -112,19 +139,19 @@ if ( ! function_exists('form_open_multipart')) // ------------------------------------------------------------------------ -/** - * 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')) { + /** + * Hidden Input Field + * + * Generates hidden fields. You can pass a simple key/value string or + * an associative array with multiple values. + * + * @param mixed $name Field name + * @param string $value Field value + * @param bool $recursing + * @return string + */ function form_hidden($name, $value = '', $recursing = FALSE) { static $form; @@ -140,18 +167,19 @@ if ( ! function_exists('form_hidden')) { form_hidden($key, $val, TRUE); } + return $form; } if ( ! is_array($value)) { - $form .= ''."\n"; + $form .= '\n"; } else { foreach ($value as $k => $v) { - $k = (is_int($k)) ? '' : $k; + $k = is_int($k) ? '' : $k; form_hidden($name.'['.$k.']', $v, TRUE); } } @@ -162,47 +190,45 @@ if ( ! function_exists('form_hidden')) // ------------------------------------------------------------------------ -/** - * Text Input Field - * - * @access public - * @param mixed - * @param string - * @param string - * @return string - */ if ( ! function_exists('form_input')) { + /** + * Text Input Field + * + * @param mixed + * @param string + * @param mixed + * @return string + */ function form_input($data = '', $value = '', $extra = '') { - $defaults = array('type' => 'text', 'name' => (( ! is_array($data)) ? $data : ''), 'value' => $value); + $defaults = array( + 'type' => 'text', + 'name' => is_array($data) ? '' : $data, + 'value' => $value + ); - return ""; + return '\n"; } } // ------------------------------------------------------------------------ -/** - * 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')) { + /** + * Password Field + * + * Identical to the input function but adds the "password" type + * + * @param mixed + * @param string + * @param mixed + * @return string + */ function form_password($data = '', $value = '', $extra = '') { - if ( ! is_array($data)) - { - $data = array('name' => $data); - } - + is_array($data) OR $data = array('name' => $data); $data['type'] = 'password'; return form_input($data, $value, $extra); } @@ -210,47 +236,47 @@ if ( ! function_exists('form_password')) // ------------------------------------------------------------------------ -/** - * 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')) { + /** + * Upload Field + * + * Identical to the input function but adds the "file" type + * + * @param mixed + * @param string + * @param mixed + * @return string + */ function form_upload($data = '', $value = '', $extra = '') { - if ( ! is_array($data)) - { - $data = array('name' => $data); - } - + $defaults = array('type' => 'file', 'name' => ''); + is_array($data) OR $data = array('name' => $data); $data['type'] = 'file'; - return form_input($data, $value, $extra); + + return '\n"; } } // ------------------------------------------------------------------------ -/** - * Textarea field - * - * @access public - * @param mixed - * @param string - * @param string - * @return string - */ if ( ! function_exists('form_textarea')) { + /** + * Textarea field + * + * @param mixed $data + * @param string $value + * @param mixed $extra + * @return string + */ function form_textarea($data = '', $value = '', $extra = '') { - $defaults = array('name' => (( ! is_array($data)) ? $data : ''), 'cols' => '40', 'rows' => '10'); + $defaults = array( + 'name' => is_array($data) ? '' : $data, + 'cols' => '40', + 'rows' => '10' + ); if ( ! is_array($data) OR ! isset($data['value'])) { @@ -262,28 +288,29 @@ if ( ! function_exists('form_textarea')) unset($data['value']); // textareas don't use the value attribute } - $name = (is_array($data)) ? $data['name'] : $data; - return ""; + return '\n"; } } // ------------------------------------------------------------------------ -/** - * Multi-select menu - * - * @access public - * @param string - * @param array - * @param mixed - * @param string - * @return type - */ if ( ! function_exists('form_multiselect')) { + /** + * Multi-select menu + * + * @param string + * @param array + * @param mixed + * @param mixed + * @return string + */ function form_multiselect($name = '', $options = array(), $selected = array(), $extra = '') { - if ( ! strpos($extra, 'multiple')) + $extra = _attributes_to_string($extra); + if (stripos($extra, 'multiple') === FALSE) { $extra .= ' multiple="multiple"'; } @@ -294,91 +321,117 @@ if ( ! function_exists('form_multiselect')) // -------------------------------------------------------------------- -/** - * 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 = '') + /** + * Drop-down Menu + * + * @param mixed $data + * @param mixed $options + * @param mixed $selected + * @param mixed $extra + * @return string + */ + function form_dropdown($data = '', $options = array(), $selected = array(), $extra = '') { - if ( ! is_array($selected)) + $defaults = array(); + + if (is_array($data)) { - $selected = array($selected); + if (isset($data['selected'])) + { + $selected = $data['selected']; + unset($data['selected']); // select tags don't have a selected attribute + } + + if (isset($data['options'])) + { + $options = $data['options']; + unset($data['options']); // select tags don't use an options attribute + } + } + else + { + $defaults = array('name' => $data); } + is_array($selected) OR $selected = array($selected); + is_array($options) OR $options = array($options); + // If no selected state was submitted we will attempt to set it automatically - if (count($selected) === 0) + if (empty($selected)) { - // If the form name appears in the $_POST array we have a winner! - if (isset($_POST[$name])) + if (is_array($data)) { - $selected = array($_POST[$name]); + if (isset($data['name'], $_POST[$data['name']])) + { + $selected = array($_POST[$data['name']]); + } + } + elseif (isset($_POST[$data])) + { + $selected = array($_POST[$data]); } } - if ($extra != '') $extra = ' '.$extra; + $extra = _attributes_to_string($extra); - $multiple = (count($selected) > 1 && strpos($extra, 'multiple') === FALSE) ? ' multiple="multiple"' : ''; + $multiple = (count($selected) > 1 && stripos($extra, 'multiple') === FALSE) ? ' multiple="multiple"' : ''; - $form = '\n"; foreach ($options as $key => $val) { $key = (string) $key; - if (is_array($val) && ! empty($val)) + if (is_array($val)) { - $form .= ''."\n"; + if (empty($val)) + { + continue; + } + + $form .= '\n"; foreach ($val as $optgroup_key => $optgroup_val) { - $sel = (in_array($optgroup_key, $selected)) ? ' selected="selected"' : ''; - - $form .= '\n"; + $sel = in_array($optgroup_key, $selected) ? ' selected="selected"' : ''; + $form .= '\n"; } - $form .= ''."\n"; + $form .= "\n"; } else { - $sel = (in_array($key, $selected)) ? ' selected="selected"' : ''; - - $form .= '\n"; + $form .= '\n"; } } - $form .= ''; - - return $form; + return $form."\n"; } } // ------------------------------------------------------------------------ -/** - * Checkbox Field - * - * @access public - * @param mixed - * @param string - * @param bool - * @param string - * @return string - */ if ( ! function_exists('form_checkbox')) { + /** + * Checkbox Field + * + * @param mixed + * @param string + * @param bool + * @param mixed + * @return string + */ function form_checkbox($data = '', $value = '', $checked = FALSE, $extra = '') { - $defaults = array('type' => 'checkbox', 'name' => (( ! is_array($data)) ? $data : ''), 'value' => $value); + $defaults = array('type' => 'checkbox', 'name' => ( ! is_array($data) ? $data : ''), 'value' => $value); - if (is_array($data) AND array_key_exists('checked', $data)) + if (is_array($data) && array_key_exists('checked', $data)) { $checked = $data['checked']; @@ -401,129 +454,134 @@ if ( ! function_exists('form_checkbox')) unset($defaults['checked']); } - return ""; + return '\n"; } } // ------------------------------------------------------------------------ -/** - * Radio Button - * - * @access public - * @param mixed - * @param string - * @param bool - * @param string - * @return string - */ if ( ! function_exists('form_radio')) { + /** + * Radio Button + * + * @param mixed + * @param string + * @param bool + * @param mixed + * @return string + */ function form_radio($data = '', $value = '', $checked = FALSE, $extra = '') { - if ( ! is_array($data)) - { - $data = array('name' => $data); - } - + is_array($data) OR $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')) { + /** + * Submit Button + * + * @param mixed + * @param string + * @param mixed + * @return string + */ function form_submit($data = '', $value = '', $extra = '') { - $defaults = array('type' => 'submit', 'name' => (( ! is_array($data)) ? $data : ''), 'value' => $value); + $defaults = array( + 'type' => 'submit', + 'name' => is_array($data) ? '' : $data, + 'value' => $value + ); - return ""; + return '\n"; } } // ------------------------------------------------------------------------ -/** - * Reset Button - * - * @access public - * @param mixed - * @param string - * @param string - * @return string - */ if ( ! function_exists('form_reset')) { + /** + * Reset Button + * + * @param mixed + * @param string + * @param mixed + * @return string + */ function form_reset($data = '', $value = '', $extra = '') { - $defaults = array('type' => 'reset', 'name' => (( ! is_array($data)) ? $data : ''), 'value' => $value); + $defaults = array( + 'type' => 'reset', + 'name' => is_array($data) ? '' : $data, + 'value' => $value + ); - return ""; + return '\n"; } } // ------------------------------------------------------------------------ -/** - * Form Button - * - * @access public - * @param mixed - * @param string - * @param string - * @return string - */ if ( ! function_exists('form_button')) { + /** + * Form Button + * + * @param mixed + * @param string + * @param mixed + * @return string + */ function form_button($data = '', $content = '', $extra = '') { - $defaults = array('name' => (( ! is_array($data)) ? $data : ''), 'type' => 'button'); + $defaults = array( + 'name' => is_array($data) ? '' : $data, + 'type' => 'button' + ); - if ( is_array($data) AND isset($data['content'])) + if (is_array($data) && isset($data['content'])) { $content = $data['content']; unset($data['content']); // content is not an attribute } - return ""; + return '\n"; } } // ------------------------------------------------------------------------ -/** - * 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')) { + /** + * Form Label Tag + * + * @param string The text to appear onscreen + * @param string The id the label applies to + * @param string Additional attributes + * @return string + */ function form_label($label_text = '', $id = '', $attributes = array()) { $label = ' 0) + if (is_array($attributes) && count($attributes) > 0) { foreach ($attributes as $key => $val) { @@ -531,37 +589,30 @@ if ( ! function_exists('form_label')) } } - $label .= ">$label_text"; - - return $label; + return $label.'>'.$label_text.''; } } // ------------------------------------------------------------------------ -/** - * 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')) { + /** + * Fieldset Tag + * + * Used to produce
text. To close fieldset + * use form_fieldset_close() + * + * @param string The legend text + * @param array Additional attributes + * @return string + */ function form_fieldset($legend_text = '', $attributes = array()) { - $fieldset = "\n"; + if ($legend_text !== '') { - $fieldset .= "$legend_text\n"; + return $fieldset.''.$legend_text."\n"; } return $fieldset; @@ -570,306 +621,250 @@ if ( ! function_exists('form_fieldset')) // ------------------------------------------------------------------------ -/** - * Fieldset Close Tag - * - * @access public - * @param string - * @return string - */ if ( ! function_exists('form_fieldset_close')) { + /** + * Fieldset Close Tag + * + * @param string + * @return string + */ function form_fieldset_close($extra = '') { - return "
".$extra; + return '
'.$extra; } } // ------------------------------------------------------------------------ -/** - * Form Close Tag - * - * @access public - * @param string - * @return string - */ if ( ! function_exists('form_close')) { + /** + * Form Close Tag + * + * @param string + * @return string + */ function form_close($extra = '') { - return "".$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 = '') + /** + * Form Prep + * + * Formats text so that it can be safely placed in a form field in the event it has HTML tags. + * + * @deprecated 3.0.0 An alias for html_escape() + * @param string|string[] $str Value to escape + * @return string|string[] Escaped values + */ + function form_prep($str) { - 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; + return html_escape($str, TRUE); } } // ------------------------------------------------------------------------ -/** - * 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 = '') + /** + * 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 + * + * @param string $field Field name + * @param string $default Default value + * @param bool $html_escape Whether to escape HTML special characters or not + * @return string + */ + function set_value($field, $default = '', $html_escape = TRUE) { - if (FALSE === ($OBJ =& _get_validation_object())) - { - if ( ! isset($_POST[$field])) - { - return $default; - } + $CI =& get_instance(); - return form_prep($_POST[$field], $field); - } + $value = (isset($CI->form_validation) && is_object($CI->form_validation) && $CI->form_validation->has_rule($field)) + ? $CI->form_validation->set_value($field, $default) + : $CI->input->post($field, FALSE); - return form_prep($OBJ->set_value($field, $default), $field); + isset($value) OR $value = $default; + return ($html_escape) ? html_escape($value) : $value; } } // ------------------------------------------------------------------------ -/** - * Set Select - * - * Let's you set the selected value of a menu via data in the POST array. + * If Form Validation is active it retrieves the info from the validation class + * + * @param string + * @param string + * @param bool + * @return string + */ + function set_select($field, $value = '', $default = FALSE) { - $OBJ =& _get_validation_object(); + $CI =& get_instance(); - if ($OBJ === FALSE) + if (isset($CI->form_validation) && is_object($CI->form_validation) && $CI->form_validation->has_rule($field)) { - if ( ! isset($_POST[$field])) + return $CI->form_validation->set_select($field, $value, $default); + } + elseif (($input = $CI->input->post($field, FALSE)) === NULL) + { + return ($default === TRUE) ? ' selected="selected"' : ''; + } + + $value = (string) $value; + if (is_array($input)) + { + // Note: in_array('', array(0)) returns TRUE, do not use it + foreach ($input as &$v) { - if (count($_POST) === 0 AND $default == TRUE) + if ($value === $v) { return ' selected="selected"'; } - return ''; } - $field = $_POST[$field]; - - if (is_array($field)) - { - if ( ! in_array($value, $field)) - { - return ''; - } - } - else - { - if (($field == '' OR $value == '') OR ($field != $value)) - { - return ''; - } - } - - return ' selected="selected"'; + return ''; } - return $OBJ->set_select($field, $value, $default); + return ($input === $value) ? ' selected="selected"' : ''; } } // ------------------------------------------------------------------------ -/** - * Set Checkbox - * - * Let's you set the selected value of a checkbox via the value in the POST array. - * If Form Validation is active it retrieves the info from the validation class - * - * @access public - * @param string - * @param string - * @param bool - * @return string - */ if ( ! function_exists('set_checkbox')) { - function set_checkbox($field = '', $value = '', $default = FALSE) + /** + * Set Checkbox + * + * Let's you set the selected value of a checkbox via the value in the POST array. + * If Form Validation is active it retrieves the info from the validation class + * + * @param string + * @param string + * @param bool + * @return string + */ + function set_checkbox($field, $value = '', $default = FALSE) { - $OBJ =& _get_validation_object(); + $CI =& get_instance(); - if ($OBJ === FALSE) + if (isset($CI->form_validation) && is_object($CI->form_validation) && $CI->form_validation->has_rule($field)) { - if ( ! isset($_POST[$field])) + return $CI->form_validation->set_checkbox($field, $value, $default); + } + + // Form inputs are always strings ... + $value = (string) $value; + $input = $CI->input->post($field, FALSE); + + if (is_array($input)) + { + // Note: in_array('', array(0)) returns TRUE, do not use it + foreach ($input as &$v) { - if (count($_POST) === 0 AND $default == TRUE) + if ($value === $v) { return ' checked="checked"'; } - return ''; } - $field = $_POST[$field]; - - if (is_array($field)) - { - if ( ! in_array($value, $field)) - { - return ''; - } - } - else - { - if (($field == '' OR $value == '') OR ($field != $value)) - { - return ''; - } - } - - return ' checked="checked"'; + return ''; } - return $OBJ->set_checkbox($field, $value, $default); + // Unchecked checkbox and radio inputs are not even submitted by browsers ... + if ($CI->input->method() === 'post') + { + return ($input === $value) ? ' checked="checked"' : ''; + } + + return ($default === TRUE) ? ' checked="checked"' : ''; } } // ------------------------------------------------------------------------ -/** - * Set Radio - * - * Let's you set the selected value of a radio field via info in the POST array. - * If Form Validation is active it retrieves the info from the validation class - * - * @access public - * @param string - * @param string - * @param bool - * @return string - */ if ( ! function_exists('set_radio')) { - function set_radio($field = '', $value = '', $default = FALSE) + /** + * Set Radio + * + * Let's you set the selected value of a radio field via info in the POST array. + * If Form Validation is active it retrieves the info from the validation class + * + * @param string $field + * @param string $value + * @param bool $default + * @return string + */ + function set_radio($field, $value = '', $default = FALSE) { - $OBJ =& _get_validation_object(); + $CI =& get_instance(); - if ($OBJ === FALSE) + if (isset($CI->form_validation) && is_object($CI->form_validation) && $CI->form_validation->has_rule($field)) { - if ( ! isset($_POST[$field])) + return $CI->form_validation->set_radio($field, $value, $default); + } + + // Form inputs are always strings ... + $value = (string) $value; + $input = $CI->input->post($field, FALSE); + + if (is_array($input)) + { + // Note: in_array('', array(0)) returns TRUE, do not use it + foreach ($input as &$v) { - if (count($_POST) === 0 AND $default == TRUE) + if ($value === $v) { return ' checked="checked"'; } - return ''; } - $field = $_POST[$field]; - - if (is_array($field)) - { - if ( ! in_array($value, $field)) - { - return ''; - } - } - else - { - if (($field == '' OR $value == '') OR ($field != $value)) - { - return ''; - } - } - - return ' checked="checked"'; + return ''; } - return $OBJ->set_radio($field, $value, $default); + // Unchecked checkbox and radio inputs are not even submitted by browsers ... + if ($CI->input->method() === 'post') + { + return ($input === $value) ? ' checked="checked"' : ''; + } + + return ($default === TRUE) ? ' checked="checked"' : ''; } } // ------------------------------------------------------------------------ -/** - * Form Error - * - * Returns the error for a specific form field. This is a helper for the - * form validation class. - * - * @access public - * @param string - * @param string - * @param string - * @return string - */ if ( ! function_exists('form_error')) { + /** + * Form Error + * + * Returns the error for a specific form field. This is a helper for the + * form validation class. + * + * @param string + * @param string + * @param string + * @return string + */ function form_error($field = '', $prefix = '', $suffix = '') { if (FALSE === ($OBJ =& _get_validation_object())) @@ -883,19 +878,18 @@ if ( ! function_exists('form_error')) // ------------------------------------------------------------------------ -/** - * Validation Error String - * - * Returns all the errors associated with a form submission. This is a helper - * function for the form validation class. - * - * @access public - * @param string - * @param string - * @return string - */ if ( ! function_exists('validation_errors')) { + /** + * Validation Error String + * + * Returns all the errors associated with a form submission. This is a helper + * function for the form validation class. + * + * @param string + * @param string + * @return string + */ function validation_errors($prefix = '', $suffix = '') { if (FALSE === ($OBJ =& _get_validation_object())) @@ -909,18 +903,17 @@ if ( ! function_exists('validation_errors')) // ------------------------------------------------------------------------ -/** - * Parse the form attributes - * - * Helper function used by some of the form helpers - * - * @access private - * @param array - * @param array - * @return string - */ if ( ! function_exists('_parse_form_attributes')) { + /** + * Parse the form attributes + * + * Helper function used by some of the form helpers + * + * @param array $attributes List of attributes + * @param array $default Default values + * @return string + */ function _parse_form_attributes($attributes, $default) { if (is_array($attributes)) @@ -944,12 +937,16 @@ if ( ! function_exists('_parse_form_attributes')) foreach ($default as $key => $val) { - if ($key == 'value') + if ($key === 'value') { - $val = form_prep($val, $default['name']); + $val = html_escape($val); + } + elseif ($key === 'name' && ! strlen($default['name'])) + { + continue; } - $att .= $key . '="' . $val . '" '; + $att .= $key.'="'.$val.'" '; } return $att; @@ -958,54 +955,32 @@ if ( ! function_exists('_parse_form_attributes')) // ------------------------------------------------------------------------ -/** - * Attributes To String - * - * Helper function used by some of the form helpers - * - * @access private - * @param mixed - * @param bool - * @return string - */ if ( ! function_exists('_attributes_to_string')) { - function _attributes_to_string($attributes, $formtag = FALSE) + /** + * Attributes To String + * + * Helper function used by some of the form helpers + * + * @param mixed + * @return string + */ + function _attributes_to_string($attributes) { - if (is_string($attributes) AND strlen($attributes) > 0) + if (empty($attributes)) { - if ($formtag == TRUE AND strpos($attributes, 'method=') === FALSE) - { - $attributes .= ' method="post"'; - } - - if ($formtag == TRUE AND strpos($attributes, 'accept-charset=') === FALSE) - { - $attributes .= ' accept-charset="'.strtolower(config_item('charset')).'"'; - } - - return ' '.$attributes; + return ''; } - if (is_object($attributes) AND count($attributes) > 0) + if (is_object($attributes)) { - $attributes = (array)$attributes; + $attributes = (array) $attributes; } - if (is_array($attributes) AND count($attributes) > 0) + if (is_array($attributes)) { $atts = ''; - if ( ! isset($attributes['method']) AND $formtag === TRUE) - { - $atts .= ' method="post"'; - } - - if ( ! isset($attributes['accept-charset']) AND $formtag === TRUE) - { - $atts .= ' accept-charset="'.strtolower(config_item('charset')).'"'; - } - foreach ($attributes as $key => $val) { $atts .= ' '.$key.'="'.$val.'"'; @@ -1013,43 +988,45 @@ if ( ! function_exists('_attributes_to_string')) return $atts; } + + if (is_string($attributes)) + { + return ' '.$attributes; + } + + return FALSE; } } // ------------------------------------------------------------------------ -/** - * Validation Object - * - * Determines what the form validation class was instantiated as, fetches - * the object and returns it. - * - * @access private - * @return mixed - */ if ( ! function_exists('_get_validation_object')) { + /** + * Validation Object + * + * Determines what the form validation class was instantiated as, fetches + * the object and returns it. + * + * @return mixed + */ function &_get_validation_object() { $CI =& get_instance(); // We set this as a variable since we're returning by reference. $return = FALSE; - - if (FALSE !== ($object = $CI->load->is_loaded('form_validation'))) + + if (FALSE !== ($object = $CI->load->is_loaded('Form_validation'))) { if ( ! isset($CI->$object) OR ! is_object($CI->$object)) { return $return; } - + return $CI->$object; } - + return $return; } } - - -/* End of file form_helper.php */ -/* Location: ./system/helpers/form_helper.php */ diff --git a/src/system/helpers/html_helper.php b/src/system/helpers/html_helper.php index aef8ae7b..fdc463fc 100644 --- a/src/system/helpers/html_helper.php +++ b/src/system/helpers/html_helper.php @@ -1,20 +1,41 @@ -".$data.""; + return ''.$data.''; } } // ------------------------------------------------------------------------ -/** - * Unordered List - * - * Generates an HTML unordered list from an single or multi-dimensional array. - * - * @access public - * @param array - * @param mixed - * @return string - */ if ( ! function_exists('ul')) { + /** + * Unordered List + * + * Generates an HTML unordered list from an single or multi-dimensional array. + * + * @param array + * @param mixed + * @return string + */ function ul($list, $attributes = '') { return _list('ul', $list, $attributes); @@ -70,18 +88,17 @@ if ( ! function_exists('ul')) // ------------------------------------------------------------------------ -/** - * Ordered List - * - * Generates an HTML ordered list from an single or multi-dimensional array. - * - * @access public - * @param array - * @param mixed - * @return string - */ if ( ! function_exists('ol')) { + /** + * Ordered List + * + * Generates an HTML ordered list from an single or multi-dimensional array. + * + * @param array + * @param mixed + * @return string + */ function ol($list, $attributes = '') { return _list('ol', $list, $attributes); @@ -90,21 +107,20 @@ if ( ! function_exists('ol')) // ------------------------------------------------------------------------ -/** - * Generates the list - * - * Generates an HTML ordered list from an single or multi-dimensional array. - * - * @access private - * @param string - * @param mixed - * @param mixed - * @param integer - * @return string - */ if ( ! function_exists('_list')) { - function _list($type = 'ul', $list, $attributes = '', $depth = 0) + /** + * Generates the list + * + * Generates an HTML ordered list from an single or multi-dimensional array. + * + * @param string + * @param mixed + * @param mixed + * @param int + * @return string + */ + function _list($type = 'ul', $list = array(), $attributes = '', $depth = 0) { // If an array wasn't submitted there's nothing to do... if ( ! is_array($list)) @@ -113,25 +129,10 @@ if ( ! function_exists('_list')) } // Set the indentation based on the depth - $out = str_repeat(" ", $depth); + $out = str_repeat(' ', $depth) + // Write the opening list tag + .'<'.$type._stringify_attributes($attributes).">\n"; - // Were any attributes submitted? If so generate a string - if (is_array($attributes)) - { - $atts = ''; - foreach ($attributes as $key => $val) - { - $atts .= ' ' . $key . '="' . $val . '"'; - } - $attributes = $atts; - } - elseif (is_string($attributes) AND strlen($attributes) > 0) - { - $attributes = ' '. $attributes; - } - - // Write the opening list tag - $out .= "<".$type.$attributes.">\n"; // Cycle through the list elements. If an array is // encountered we will recursively call _list() @@ -141,8 +142,7 @@ if ( ! function_exists('_list')) { $_last_list_item = $key; - $out .= str_repeat(" ", $depth + 2); - $out .= "
  • "; + $out .= str_repeat(' ', $depth + 2).'
  • '; if ( ! is_array($val)) { @@ -150,55 +150,32 @@ if ( ! function_exists('_list')) } else { - $out .= $_last_list_item."\n"; - $out .= _list($type, $val, '', $depth + 4); - $out .= str_repeat(" ", $depth + 2); + $out .= $_last_list_item."\n"._list($type, $val, '', $depth + 4).str_repeat(' ', $depth + 2); } $out .= "
  • \n"; } - // Set the indentation for the closing tag - $out .= str_repeat(" ", $depth); - - // Write the closing list tag - $out .= "\n"; - - return $out; + // Set the indentation for the closing tag and apply it + return $out.str_repeat(' ', $depth).'\n"; } } // ------------------------------------------------------------------------ -/** - * Generates HTML BR tags based on number supplied - * - * @access public - * @param integer - * @return string - */ -if ( ! function_exists('br')) -{ - function br($num = 1) - { - return str_repeat("
    ", $num); - } -} - -// ------------------------------------------------------------------------ - -/** - * Image - * - * Generates an element - * - * @access public - * @param mixed - * @return string - */ if ( ! function_exists('img')) { - function img($src = '', $index_page = FALSE) + /** + * Image + * + * Generates an element + * + * @param mixed + * @param bool + * @param mixed + * @return string + */ + function img($src = '', $index_page = FALSE, $attributes = '') { if ( ! is_array($src) ) { @@ -213,112 +190,101 @@ if ( ! function_exists('img')) $img = '$v) + foreach ($src as $k => $v) { - - if ($k == 'src' AND strpos($v, '://') === FALSE) + if ($k === 'src' && ! preg_match('#^([a-z]+:)?//#i', $v)) { - $CI =& get_instance(); - if ($index_page === TRUE) { - $img .= ' src="'.$CI->config->site_url($v).'"'; + $img .= ' src="'.get_instance()->config->site_url($v).'"'; } else { - $img .= ' src="'.$CI->config->slash_item('base_url').$v.'"'; + $img .= ' src="'.get_instance()->config->slash_item('base_url').$v.'"'; } } else { - $img .= " $k=\"$v\""; + $img .= ' '.$k.'="'.$v.'"'; } } - $img .= '/>'; - - return $img; + return $img._stringify_attributes($attributes).' />'; } } // ------------------------------------------------------------------------ -/** - * Doctype - * - * Generates a page document type declaration - * - * Valid options are xhtml-11, xhtml-strict, xhtml-trans, xhtml-frame, - * html4-strict, html4-trans, and html4-frame. Values are saved in the - * doctypes config file. - * - * @access public - * @param string type The doctype to be generated - * @return string - */ if ( ! function_exists('doctype')) { + /** + * Doctype + * + * Generates a page document type declaration + * + * Examples of valid options: html5, xhtml-11, xhtml-strict, xhtml-trans, + * xhtml-frame, html4-strict, html4-trans, and html4-frame. + * All values are saved in the doctypes config file. + * + * @param string type The doctype to be generated + * @return string + */ function doctype($type = 'xhtml1-strict') { - global $_doctypes; + static $doctypes; - if ( ! is_array($_doctypes)) + if ( ! is_array($doctypes)) { - if (defined('ENVIRONMENT') AND is_file(APPPATH.'config/'.ENVIRONMENT.'/doctypes.php')) - { - include(APPPATH.'config/'.ENVIRONMENT.'/doctypes.php'); - } - elseif (is_file(APPPATH.'config/doctypes.php')) + if (file_exists(APPPATH.'config/doctypes.php')) { include(APPPATH.'config/doctypes.php'); } - if ( ! is_array($_doctypes)) + if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/doctypes.php')) { + include(APPPATH.'config/'.ENVIRONMENT.'/doctypes.php'); + } + + if (empty($_doctypes) OR ! is_array($_doctypes)) + { + $doctypes = array(); return FALSE; } + + $doctypes = $_doctypes; } - if (isset($_doctypes[$type])) - { - return $_doctypes[$type]; - } - else - { - return FALSE; - } + return isset($doctypes[$type]) ? $doctypes[$type] : FALSE; } } // ------------------------------------------------------------------------ -/** - * Link - * - * Generates link to a CSS file - * - * @access public - * @param mixed stylesheet hrefs or an array - * @param string rel - * @param string type - * @param string title - * @param string media - * @param boolean should index_page be added to the css path - * @return string - */ if ( ! function_exists('link_tag')) { + /** + * Link + * + * Generates link to a CSS file + * + * @param mixed stylesheet hrefs or an array + * @param string rel + * @param string type + * @param string title + * @param string media + * @param bool should index_page be added to the css path + * @return string + */ function link_tag($href = '', $rel = 'stylesheet', $type = 'text/css', $title = '', $media = '', $index_page = FALSE) { $CI =& get_instance(); - $link = '$v) + foreach ($href as $k => $v) { - if ($k == 'href' AND strpos($v, '://') === FALSE) + if ($k === 'href' && ! preg_match('#^([a-z]+:)?//#i', $v)) { if ($index_page === TRUE) { @@ -331,15 +297,13 @@ if ( ! function_exists('link_tag')) } else { - $link .= "$k=\"$v\" "; + $link .= $k.'="'.$v.'" '; } } - - $link .= "/>"; } else { - if ( strpos($href, '://') !== FALSE) + if (preg_match('#^([a-z]+:)?//#i', $href)) { $link .= 'href="'.$href.'" '; } @@ -354,35 +318,34 @@ if ( ! function_exists('link_tag')) $link .= 'rel="'.$rel.'" type="'.$type.'" '; - if ($media != '') + if ($media !== '') { $link .= 'media="'.$media.'" '; } - if ($title != '') + if ($title !== '') { $link .= 'title="'.$title.'" '; } - - $link .= '/>'; } - - return $link; + return $link."/>\n"; } } // ------------------------------------------------------------------------ -/** - * Generates meta tags from an array of key/values - * - * @access public - * @param array - * @return string - */ if ( ! function_exists('meta')) { + /** + * Generates meta tags from an array of key/values + * + * @param array + * @param string + * @param string + * @param string + * @return string + */ function meta($name = '', $content = '', $type = 'name', $newline = "\n") { // Since we allow the data to be passes as a string, a simple array @@ -391,22 +354,19 @@ if ( ! function_exists('meta')) { $name = array(array('name' => $name, 'content' => $content, 'type' => $type, 'newline' => $newline)); } - else + elseif (isset($name['name'])) { // Turn single array into multidimensional - if (isset($name['name'])) - { - $name = array($name); - } + $name = array($name); } $str = ''; foreach ($name as $meta) { - $type = ( ! isset($meta['type']) OR $meta['type'] == 'name') ? 'name' : 'http-equiv'; - $name = ( ! isset($meta['name'])) ? '' : $meta['name']; - $content = ( ! isset($meta['content'])) ? '' : $meta['content']; - $newline = ( ! isset($meta['newline'])) ? "\n" : $meta['newline']; + $type = (isset($meta['type']) && $meta['type'] !== 'name') ? 'http-equiv' : 'name'; + $name = isset($meta['name']) ? $meta['name'] : ''; + $content = isset($meta['content']) ? $meta['content'] : ''; + $newline = isset($meta['newline']) ? $meta['newline'] : "\n"; $str .= ''.$newline; } @@ -417,21 +377,34 @@ if ( ! function_exists('meta')) // ------------------------------------------------------------------------ -/** - * Generates non-breaking space entities based on number supplied - * - * @access public - * @param integer - * @return string - */ -if ( ! function_exists('nbs')) +if ( ! function_exists('br')) { - function nbs($num = 1) + /** + * Generates HTML BR tags based on number supplied + * + * @deprecated 3.0.0 Use str_repeat() instead + * @param int $count Number of times to repeat the tag + * @return string + */ + function br($count = 1) { - return str_repeat(" ", $num); + return str_repeat('
    ', $count); } } +// ------------------------------------------------------------------------ -/* End of file html_helper.php */ -/* Location: ./system/helpers/html_helper.php */ \ No newline at end of file +if ( ! function_exists('nbs')) +{ + /** + * Generates non-breaking space entities based on number supplied + * + * @deprecated 3.0.0 Use str_repeat() instead + * @param int + * @return string + */ + function nbs($num = 1) + { + return str_repeat(' ', $num); + } +} diff --git a/src/system/helpers/index.html b/src/system/helpers/index.html index c942a79c..b702fbc3 100644 --- a/src/system/helpers/index.html +++ b/src/system/helpers/index.html @@ -1,3 +1,4 @@ + 403 Forbidden @@ -7,4 +8,4 @@

    Directory access is forbidden.

    - \ No newline at end of file + diff --git a/src/system/helpers/inflector_helper.php b/src/system/helpers/inflector_helper.php index dd01c0ef..c064d8de 100644 --- a/src/system/helpers/inflector_helper.php +++ b/src/system/helpers/inflector_helper.php @@ -1,20 +1,41 @@ - '\1ix', - '/(vert|ind)ices$/' => '\1ex', - '/^(ox)en/' => '\1', - '/(alias)es$/' => '\1', - '/([octop|vir])i$/' => '\1us', - '/(cris|ax|test)es$/' => '\1is', - '/(shoe)s$/' => '\1', - '/(o)es$/' => '\1', - '/(bus|campus)es$/' => '\1', - '/([m|l])ice$/' => '\1ouse', - '/(x|ch|ss|sh)es$/' => '\1', - '/(m)ovies$/' => '\1\2ovie', - '/(s)eries$/' => '\1\2eries', - '/([^aeiouy]|qu)ies$/' => '\1y', - '/([lr])ves$/' => '\1f', - '/(tive)s$/' => '\1', - '/(hive)s$/' => '\1', - '/([^f])ves$/' => '\1fe', - '/(^analy)ses$/' => '\1sis', + '/(matr)ices$/' => '\1ix', + '/(vert|ind)ices$/' => '\1ex', + '/^(ox)en/' => '\1', + '/(alias)es$/' => '\1', + '/([octop|vir])i$/' => '\1us', + '/(cris|ax|test)es$/' => '\1is', + '/(shoe)s$/' => '\1', + '/(o)es$/' => '\1', + '/(bus|campus)es$/' => '\1', + '/([m|l])ice$/' => '\1ouse', + '/(x|ch|ss|sh)es$/' => '\1', + '/(m)ovies$/' => '\1\2ovie', + '/(s)eries$/' => '\1\2eries', + '/([^aeiouy]|qu)ies$/' => '\1y', + '/([lr])ves$/' => '\1f', + '/(tive)s$/' => '\1', + '/(hive)s$/' => '\1', + '/([^f])ves$/' => '\1fe', + '/(^analy)ses$/' => '\1sis', '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/' => '\1\2sis', - '/([ti])a$/' => '\1um', - '/(p)eople$/' => '\1\2erson', - '/(m)en$/' => '\1an', - '/(s)tatuses$/' => '\1\2tatus', - '/(c)hildren$/' => '\1\2hild', - '/(n)ews$/' => '\1\2ews', - '/([^u])s$/' => '\1', + '/([ti])a$/' => '\1um', + '/(p)eople$/' => '\1\2erson', + '/(m)en$/' => '\1an', + '/(s)tatuses$/' => '\1\2tatus', + '/(c)hildren$/' => '\1\2hild', + '/(n)ews$/' => '\1\2ews', + '/([^us])s$/' => '\1' ); - + foreach ($singular_rules as $rule => $replacement) { if (preg_match($rule, $result)) @@ -89,23 +113,27 @@ if ( ! function_exists('singular')) // -------------------------------------------------------------------- -/** - * Plural - * - * Takes a singular word and makes it plural - * - * @access public - * @param string - * @param bool - * @return str - */ if ( ! function_exists('plural')) { - function plural($str, $force = FALSE) + /** + * Plural + * + * Takes a singular word and makes it plural + * + * @param string $str Input string + * @return string + */ + function plural($str) { $result = strval($str); - + + if ( ! is_countable($result)) + { + return $result; + } + $plural_rules = array( + '/(quiz)$/' => '\1zes', // quizzes '/^(ox)$/' => '\1\2en', // ox '/([m|l])ouse$/' => '\1ice', // mouse, louse '/(matr|vert|ind)ix|ex$/' => '\1ices', // matrix, vertex, index @@ -120,7 +148,7 @@ if ( ! function_exists('plural')) '/(c)hild$/' => '\1hildren', // child '/(buffal|tomat)o$/' => '\1\2oes', // buffalo, tomato '/(bu|campu)s$/' => '\1\2ses', // bus, campus - '/(alias|status|virus)/' => '\1es', // alias + '/(alias|status|virus)$/' => '\1es', // alias '/(octop)us$/' => '\1i', // octopus '/(ax|cris|test)is$/' => '\1es', // axis, crisis '/s$/' => 's', // no change (compatibility) @@ -142,63 +170,77 @@ if ( ! function_exists('plural')) // -------------------------------------------------------------------- -/** - * Camelize - * - * Takes multiple words separated by spaces or underscores and camelizes them - * - * @access public - * @param string - * @return str - */ if ( ! function_exists('camelize')) { + /** + * Camelize + * + * Takes multiple words separated by spaces or underscores and camelizes them + * + * @param string $str Input string + * @return string + */ function camelize($str) { - $str = 'x'.strtolower(trim($str)); - $str = ucwords(preg_replace('/[\s_]+/', ' ', $str)); - return substr(str_replace(' ', '', $str), 1); + return strtolower($str[0]).substr(str_replace(' ', '', ucwords(preg_replace('/[\s_]+/', ' ', $str))), 1); } } // -------------------------------------------------------------------- -/** - * Underscore - * - * Takes multiple words separated by spaces and underscores them - * - * @access public - * @param string - * @return str - */ if ( ! function_exists('underscore')) { + /** + * Underscore + * + * Takes multiple words separated by spaces and underscores them + * + * @param string $str Input string + * @return string + */ function underscore($str) { - return preg_replace('/[\s]+/', '_', strtolower(trim($str))); + return preg_replace('/[\s]+/', '_', trim(MB_ENABLED ? mb_strtolower($str) : strtolower($str))); } } // -------------------------------------------------------------------- -/** - * Humanize - * - * Takes multiple words separated by underscores and changes them to spaces - * - * @access public - * @param string - * @return str - */ if ( ! function_exists('humanize')) { - function humanize($str) + /** + * Humanize + * + * Takes multiple words separated by the separator and changes them to spaces + * + * @param string $str Input string + * @param string $separator Input separator + * @return string + */ + function humanize($str, $separator = '_') { - return ucwords(preg_replace('/[_]+/', ' ', strtolower(trim($str)))); + return ucwords(preg_replace('/['.preg_quote($separator).']+/', ' ', trim(MB_ENABLED ? mb_strtolower($str) : strtolower($str)))); } } +// -------------------------------------------------------------------- -/* End of file inflector_helper.php */ -/* Location: ./system/helpers/inflector_helper.php */ \ No newline at end of file +if ( ! function_exists('is_countable')) +{ + /** + * Checks if the given word has a plural version. + * + * @param string $word Word to check + * @return bool + */ + function is_countable($word) + { + return ! in_array( + strtolower($word), + array( + 'equipment', 'information', 'rice', 'money', + 'species', 'series', 'fish', 'meta' + ) + ); + } +} diff --git a/src/system/helpers/language_helper.php b/src/system/helpers/language_helper.php index d8abb0ec..3721164b 100644 --- a/src/system/helpers/language_helper.php +++ b/src/system/helpers/language_helper.php @@ -1,20 +1,41 @@ -lang->line($line); + $line = get_instance()->lang->line($line); - if ($id != '') + if ($for !== '') { - $line = '"; + $line = ''; } return $line; } } - -// ------------------------------------------------------------------------ -/* End of file language_helper.php */ -/* Location: ./system/helpers/language_helper.php */ \ No newline at end of file diff --git a/src/system/helpers/number_helper.php b/src/system/helpers/number_helper.php index b04b1047..e7810c70 100644 --- a/src/system/helpers/number_helper.php +++ b/src/system/helpers/number_helper.php @@ -1,20 +1,41 @@ -security->xss_clean($str, $is_image); + return get_instance()->security->xss_clean($str, $is_image); } } // ------------------------------------------------------------------------ -/** - * Sanitize Filename - * - * @access public - * @param string - * @return string - */ if ( ! function_exists('sanitize_filename')) { + /** + * Sanitize Filename + * + * @param string + * @return string + */ function sanitize_filename($filename) { - $CI =& get_instance(); - return $CI->security->sanitize_filename($filename); + return get_instance()->security->sanitize_filename($filename); } } // -------------------------------------------------------------------- -/** - * Hash encode a string - * - * @access public - * @param string - * @return string - */ if ( ! function_exists('do_hash')) { + /** + * Hash encode a string + * + * @todo Remove in version 3.1+. + * @deprecated 3.0.0 Use PHP's native hash() instead. + * @param string $str + * @param string $type = 'sha1' + * @return string + */ function do_hash($str, $type = 'sha1') { - if ($type == 'sha1') + if ( ! in_array(strtolower($type), hash_algos())) { - return sha1($str); - } - else - { - return md5($str); + $type = 'md5'; } + + return hash($type, $str); } } // ------------------------------------------------------------------------ -/** - * Strip Image Tags - * - * @access public - * @param string - * @return string - */ if ( ! function_exists('strip_image_tags')) { + /** + * Strip Image Tags + * + * @param string + * @return string + */ function strip_image_tags($str) { - $str = preg_replace("##", "\\1", $str); - $str = preg_replace("##", "\\1", $str); - - return $str; + return get_instance()->security->strip_image_tags($str); } } // ------------------------------------------------------------------------ -/** - * Convert PHP tags to entities - * - * @access public - * @param string - * @return string - */ if ( ! function_exists('encode_php_tags')) { + /** + * Convert PHP tags to entities + * + * @param string + * @return string + */ function encode_php_tags($str) { - return str_replace(array(''), array('<?php', '<?PHP', '<?', '?>'), $str); + return str_replace(array(''), array('<?', '?>'), $str); } } - - -/* End of file security_helper.php */ -/* Location: ./system/helpers/security_helper.php */ \ No newline at end of file diff --git a/src/system/helpers/smiley_helper.php b/src/system/helpers/smiley_helper.php index b05d9dc4..688ca24c 100644 --- a/src/system/helpers/smiley_helper.php +++ b/src/system/helpers/smiley_helper.php @@ -1,20 +1,41 @@ -field_id pairs - * @param string field_id if alias name was passed in - * @return array - */ if ( ! function_exists('smiley_js')) { + /** + * Smiley Javascript + * + * Returns the javascript required for the smiley insertion. Optionally takes + * an array of aliases to loosely couple the smiley array to the view. + * + * @param mixed alias name or array of alias->field_id pairs + * @param string field_id if alias name was passed in + * @param bool + * @return array + */ function smiley_js($alias = '', $field_id = '', $inline = TRUE) { static $do_setup = TRUE; - $r = ''; - if ($alias != '' && ! is_array($alias)) + if ($alias !== '' && ! is_array($alias)) { $alias = array($alias => $field_id); } if ($do_setup === TRUE) { - $do_setup = FALSE; + $do_setup = FALSE; + $m = array(); - $m = array(); - - if (is_array($alias)) - { - foreach ($alias as $name => $id) - { - $m[] = '"'.$name.'" : "'.$id.'"'; - } - } - - $m = '{'.implode(',', $m).'}'; - - $r .= << $id) { - $r .= 'smiley_map["'.$name.'"] = "'.$id.'";'."\n"; + $m[] = '"'.$name.'" : "'.$id.'"'; } } + + $m = '{'.implode(',', $m).'}'; + + $r .= << $id) + { + $r .= 'smiley_map["'.$name.'"] = "'.$id."\";\n"; + } } - if ($inline) - { - return ''; - } - else - { - return $r; - } + return ($inline) + ? '' + : $r; } } // ------------------------------------------------------------------------ -/** - * Get Clickable Smileys - * - * Returns an array of image tag links that can be clicked to be inserted - * into a form field. - * - * @access public - * @param string the URL to the folder containing the smiley images - * @return array - */ if ( ! function_exists('get_clickable_smileys')) { - function get_clickable_smileys($image_url, $alias = '', $smileys = NULL) + /** + * Get Clickable Smileys + * + * Returns an array of image tag links that can be clicked to be inserted + * into a form field. + * + * @param string the URL to the folder containing the smiley images + * @param array + * @return array + */ + function get_clickable_smileys($image_url, $alias = '') { // For backward compatibility with js_insert_smiley - if (is_array($alias)) { $smileys = $alias; } - - if ( ! is_array($smileys)) + elseif (FALSE === ($smileys = _get_smiley_array())) { - if (FALSE === ($smileys = _get_smiley_array())) - { - return $smileys; - } + return FALSE; } // Add a trailing slash to the file path if needed @@ -158,7 +165,7 @@ if ( ! function_exists('get_clickable_smileys')) foreach ($smileys as $key => $val) { // Keep duplicates from being used, which can happen if the - // mapping array contains multiple identical replacements. For example: + // mapping array contains multiple identical replacements. For example: // :-) and :) might be replaced with the same image so both smileys // will be in the array. if (isset($used[$smileys[$key][0]])) @@ -166,8 +173,7 @@ if ( ! function_exists('get_clickable_smileys')) continue; } - $link[] = "
    \"".$smileys[$key][3]."\""; - + $link[] = ''.$smileys[$key][3].''; $used[$smileys[$key][0]] = TRUE; } @@ -177,39 +183,31 @@ if ( ! function_exists('get_clickable_smileys')) // ------------------------------------------------------------------------ -/** - * Parse Smileys - * - * Takes a string as input and swaps any contained smileys for the actual image - * - * @access public - * @param string the text to be parsed - * @param string the URL to the folder containing the smiley images - * @return string - */ if ( ! function_exists('parse_smileys')) { + /** + * Parse Smileys + * + * Takes a string as input and swaps any contained smileys for the actual image + * + * @param string the text to be parsed + * @param string the URL to the folder containing the smiley images + * @param array + * @return string + */ function parse_smileys($str = '', $image_url = '', $smileys = NULL) { - if ($image_url == '') + if ($image_url === '' OR ( ! is_array($smileys) && FALSE === ($smileys = _get_smiley_array()))) { return $str; } - if ( ! is_array($smileys)) - { - if (FALSE === ($smileys = _get_smiley_array())) - { - return $str; - } - } - // Add a trailing slash to the file path if needed - $image_url = preg_replace("/(.+?)\/*$/", "\\1/", $image_url); + $image_url = rtrim($image_url, '/').'/'; foreach ($smileys as $key => $val) { - $str = str_replace($key, "\"".$smileys[$key][3]."\"", $str); + $str = str_replace($key, ''.$smileys[$key][3].'', $str); } return $str; @@ -218,65 +216,40 @@ if ( ! function_exists('parse_smileys')) // ------------------------------------------------------------------------ -/** - * Get Smiley Array - * - * Fetches the config/smiley.php file - * - * @access private - * @return mixed - */ if ( ! function_exists('_get_smiley_array')) { + /** + * Get Smiley Array + * + * Fetches the config/smiley.php file + * + * @return mixed + */ function _get_smiley_array() { - if (defined('ENVIRONMENT') AND file_exists(APPPATH.'config/'.ENVIRONMENT.'/smileys.php')) + static $_smileys; + + if ( ! is_array($_smileys)) { - include(APPPATH.'config/'.ENVIRONMENT.'/smileys.php'); - } - elseif (file_exists(APPPATH.'config/smileys.php')) - { - include(APPPATH.'config/smileys.php'); - } - - if (isset($smileys) AND is_array($smileys)) - { - return $smileys; + if (file_exists(APPPATH.'config/smileys.php')) + { + include(APPPATH.'config/smileys.php'); + } + + if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/smileys.php')) + { + include(APPPATH.'config/'.ENVIRONMENT.'/smileys.php'); + } + + if (empty($smileys) OR ! is_array($smileys)) + { + $_smileys = array(); + return FALSE; + } + + $_smileys = $smileys; } - return FALSE; + return $_smileys; } } - -// ------------------------------------------------------------------------ - -/** - * JS Insert Smiley - * - * Generates the javascript function needed to insert smileys into a form field - * - * DEPRECATED as of version 1.7.2, use smiley_js instead - * - * @access public - * @param string form name - * @param string field name - * @return string - */ -if ( ! function_exists('js_insert_smiley')) -{ - function js_insert_smiley($form_name = '', $form_field = '') - { - return << - function insert_smiley(smiley) - { - document.{$form_name}.{$form_field}.value += " " + smiley; - } - -EOF; - } -} - - -/* End of file smiley_helper.php */ -/* Location: ./system/helpers/smiley_helper.php */ \ No newline at end of file diff --git a/src/system/helpers/string_helper.php b/src/system/helpers/string_helper.php index aeeba995..db531fa9 100644 --- a/src/system/helpers/string_helper.php +++ b/src/system/helpers/string_helper.php @@ -1,20 +1,41 @@ - $val) - { - $str[$key] = strip_slashes($val); - } + return stripslashes($str); } - else + + foreach ($str as $key => $val) { - $str = stripslashes($str); + $str[$key] = strip_slashes($val); } return $str; @@ -84,17 +104,16 @@ if ( ! function_exists('strip_slashes')) // ------------------------------------------------------------------------ -/** - * Strip Quotes - * - * Removes single and double quotes from a string - * - * @access public - * @param string - * @return string - */ if ( ! function_exists('strip_quotes')) { + /** + * Strip Quotes + * + * Removes single and double quotes from a string + * + * @param string + * @return string + */ function strip_quotes($str) { return str_replace(array('"', "'"), '', $str); @@ -103,17 +122,16 @@ if ( ! function_exists('strip_quotes')) // ------------------------------------------------------------------------ -/** - * Quotes to Entities - * - * Converts single and double quotes to entities - * - * @access public - * @param string - * @return string - */ if ( ! function_exists('quotes_to_entities')) { + /** + * Quotes to Entities + * + * Converts single and double quotes to entities + * + * @param string + * @return string + */ function quotes_to_entities($str) { return str_replace(array("\'","\"","'",'"'), array("'",""","'","""), $str); @@ -122,164 +140,146 @@ if ( ! function_exists('quotes_to_entities')) // ------------------------------------------------------------------------ -/** - * Reduce Double Slashes - * - * Converts double slashes in a string to a single slash, - * except those found in http:// - * - * http://www.some-site.com//index.php - * - * becomes: - * - * http://www.some-site.com/index.php - * - * @access public - * @param string - * @return string - */ if ( ! function_exists('reduce_double_slashes')) { + /** + * Reduce Double Slashes + * + * Converts double slashes in a string to a single slash, + * except those found in http:// + * + * http://www.some-site.com//index.php + * + * becomes: + * + * http://www.some-site.com/index.php + * + * @param string + * @return string + */ function reduce_double_slashes($str) { - return preg_replace("#(^|[^:])//+#", "\\1/", $str); + return preg_replace('#(^|[^:])//+#', '\\1/', $str); } } // ------------------------------------------------------------------------ -/** - * Reduce Multiples - * - * Reduces multiple instances of a particular character. Example: - * - * Fred, Bill,, Joe, Jimmy - * - * becomes: - * - * Fred, Bill, Joe, Jimmy - * - * @access public - * @param string - * @param string the character you wish to reduce - * @param bool TRUE/FALSE - whether to trim the character from the beginning/end - * @return string - */ if ( ! function_exists('reduce_multiples')) { + /** + * Reduce Multiples + * + * Reduces multiple instances of a particular character. Example: + * + * Fred, Bill,, Joe, Jimmy + * + * becomes: + * + * Fred, Bill, Joe, Jimmy + * + * @param string + * @param string the character you wish to reduce + * @param bool TRUE/FALSE - whether to trim the character from the beginning/end + * @return string + */ function reduce_multiples($str, $character = ',', $trim = FALSE) { $str = preg_replace('#'.preg_quote($character, '#').'{2,}#', $character, $str); - - if ($trim === TRUE) - { - $str = trim($str, $character); - } - - return $str; + return ($trim === TRUE) ? trim($str, $character) : $str; } } // ------------------------------------------------------------------------ -/** - * Create a Random String - * - * Useful for generating passwords or hashes. - * - * @access public - * @param string type of random string. basic, alpha, alunum, numeric, nozero, unique, md5, encrypt and sha1 - * @param integer number of characters - * @return string - */ if ( ! function_exists('random_string')) { + /** + * Create a Random String + * + * Useful for generating passwords or hashes. + * + * @param string type of random string. basic, alpha, alnum, numeric, nozero, unique, md5, encrypt and sha1 + * @param int number of characters + * @return string + */ function random_string($type = 'alnum', $len = 8) { - switch($type) + switch ($type) { - case 'basic' : return mt_rand(); - break; - case 'alnum' : - case 'numeric' : - case 'nozero' : - case 'alpha' : - - switch ($type) - { - case 'alpha' : $pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; - break; - case 'alnum' : $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; - break; - case 'numeric' : $pool = '0123456789'; - break; - case 'nozero' : $pool = '123456789'; - break; - } - - $str = ''; - for ($i=0; $i < $len; $i++) - { - $str .= substr($pool, mt_rand(0, strlen($pool) -1), 1); - } - return $str; - break; - case 'unique' : - case 'md5' : - - return md5(uniqid(mt_rand())); - break; - case 'encrypt' : - case 'sha1' : - - $CI =& get_instance(); - $CI->load->helper('security'); - - return do_hash(uniqid(mt_rand(), TRUE), 'sha1'); - break; + case 'basic': + return mt_rand(); + case 'alnum': + case 'numeric': + case 'nozero': + case 'alpha': + switch ($type) + { + case 'alpha': + $pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + break; + case 'alnum': + $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + break; + case 'numeric': + $pool = '0123456789'; + break; + case 'nozero': + $pool = '123456789'; + break; + } + return substr(str_shuffle(str_repeat($pool, ceil($len / strlen($pool)))), 0, $len); + case 'unique': // todo: remove in 3.1+ + case 'md5': + return md5(uniqid(mt_rand())); + case 'encrypt': // todo: remove in 3.1+ + case 'sha1': + return sha1(uniqid(mt_rand(), TRUE)); } } } // ------------------------------------------------------------------------ -/** - * Add's _1 to a string or increment the ending number to allow _2, _3, etc - * - * @param string $str required - * @param string $separator What should the duplicate number be appended with - * @param string $first Which number should be used for the first dupe increment - * @return string - */ -function increment_string($str, $separator = '_', $first = 1) +if ( ! function_exists('increment_string')) { - preg_match('/(.+)'.$separator.'([0-9]+)$/', $str, $match); - - return isset($match[2]) ? $match[1].$separator.($match[2] + 1) : $str.$separator.$first; + /** + * Add's _1 to a string or increment the ending number to allow _2, _3, etc + * + * @param string required + * @param string What should the duplicate number be appended with + * @param string Which number should be used for the first dupe increment + * @return string + */ + function increment_string($str, $separator = '_', $first = 1) + { + preg_match('/(.+)'.preg_quote($separator, '/').'([0-9]+)$/', $str, $match); + return isset($match[2]) ? $match[1].$separator.($match[2] + 1) : $str.$separator.$first; + } } // ------------------------------------------------------------------------ -/** - * Alternator - * - * Allows strings to be alternated. See docs... - * - * @access public - * @param string (as many parameters as needed) - * @return string - */ if ( ! function_exists('alternator')) { + /** + * Alternator + * + * Allows strings to be alternated. See docs... + * + * @param string (as many parameters as needed) + * @return string + */ function alternator() { static $i; - if (func_num_args() == 0) + if (func_num_args() === 0) { $i = 0; return ''; } + $args = func_get_args(); return $args[($i++ % count($args))]; } @@ -287,22 +287,20 @@ if ( ! function_exists('alternator')) // ------------------------------------------------------------------------ -/** - * Repeater function - * - * @access public - * @param string - * @param integer number of repeats - * @return string - */ if ( ! function_exists('repeater')) { + /** + * Repeater function + * + * @todo Remove in version 3.1+. + * @deprecated 3.0.0 This is just an alias for PHP's native str_repeat() + * + * @param string $data String to repeat + * @param int $num Number of repeats + * @return string + */ function repeater($data, $num = 1) { - return (($num > 0) ? str_repeat($data, $num) : ''); + return ($num > 0) ? str_repeat($data, $num) : ''; } } - - -/* End of file string_helper.php */ -/* Location: ./system/helpers/string_helper.php */ \ No newline at end of file diff --git a/src/system/helpers/text_helper.php b/src/system/helpers/text_helper.php index 85c3091a..4f9210f2 100644 --- a/src/system/helpers/text_helper.php +++ b/src/system/helpers/text_helper.php @@ -1,20 +1,41 @@ -= $n) + if (mb_strlen($out) >= $n) { $out = trim($out); - return (strlen($out) == strlen($str)) ? $out : $out.$end_char; + return (mb_strlen($out) === mb_strlen($str)) ? $out : $out.$end_char; } } } @@ -105,24 +125,20 @@ if ( ! function_exists('character_limiter')) // ------------------------------------------------------------------------ -/** - * High ASCII to Entities - * - * Converts High ascii text and MS Word special characters to character entities - * - * @access public - * @param string - * @return string - */ if ( ! function_exists('ascii_to_entities')) { + /** + * High ASCII to Entities + * + * Converts high ASCII text and MS Word special characters to character entities + * + * @param string $str + * @return string + */ function ascii_to_entities($str) { - $count = 1; - $out = ''; - $temp = array(); - - for ($i = 0, $s = strlen($str); $i < $s; $i++) + $out = ''; + for ($i = 0, $s = strlen($str) - 1, $count = 1, $temp = array(); $i <= $s; $i++) { $ordinal = ord($str[$i]); @@ -132,9 +148,9 @@ if ( ! function_exists('ascii_to_entities')) If the $temp array has a value but we have moved on, then it seems only fair that we output that entity and restart $temp before continuing. -Paul */ - if (count($temp) == 1) + if (count($temp) === 1) { - $out .= '&#'.array_shift($temp).';'; + $out .= '&#'.array_shift($temp).';'; $count = 1; } @@ -142,21 +158,28 @@ if ( ! function_exists('ascii_to_entities')) } else { - if (count($temp) == 0) + if (count($temp) === 0) { $count = ($ordinal < 224) ? 2 : 3; } $temp[] = $ordinal; - if (count($temp) == $count) + if (count($temp) === $count) { - $number = ($count == 3) ? (($temp['0'] % 16) * 4096) + (($temp['1'] % 64) * 64) + ($temp['2'] % 64) : (($temp['0'] % 32) * 64) + ($temp['1'] % 64); + $number = ($count === 3) + ? (($temp[0] % 16) * 4096) + (($temp[1] % 64) * 64) + ($temp[2] % 64) + : (($temp[0] % 32) * 64) + ($temp[1] % 64); $out .= '&#'.$number.';'; $count = 1; $temp = array(); } + // If this is the last iteration, just output whatever we have + elseif ($i === $s) + { + $out .= '&#'.implode(';', $temp).';'; + } } } @@ -166,26 +189,24 @@ if ( ! function_exists('ascii_to_entities')) // ------------------------------------------------------------------------ -/** - * Entities to ASCII - * - * Converts character entities back to ASCII - * - * @access public - * @param string - * @param bool - * @return string - */ if ( ! function_exists('entities_to_ascii')) { + /** + * Entities to ASCII + * + * Converts character entities back to ASCII + * + * @param string + * @param bool + * @return string + */ function entities_to_ascii($str, $all = TRUE) { if (preg_match_all('/\&#(\d+)\;/', $str, $matches)) { - for ($i = 0, $s = count($matches['0']); $i < $s; $i++) + for ($i = 0, $s = count($matches[0]); $i < $s; $i++) { - $digits = $matches['1'][$i]; - + $digits = $matches[1][$i]; $out = ''; if ($digits < 128) @@ -195,25 +216,26 @@ if ( ! function_exists('entities_to_ascii')) } elseif ($digits < 2048) { - $out .= chr(192 + (($digits - ($digits % 64)) / 64)); - $out .= chr(128 + ($digits % 64)); + $out .= chr(192 + (($digits - ($digits % 64)) / 64)).chr(128 + ($digits % 64)); } else { - $out .= chr(224 + (($digits - ($digits % 4096)) / 4096)); - $out .= chr(128 + ((($digits % 4096) - ($digits % 64)) / 64)); - $out .= chr(128 + ($digits % 64)); + $out .= chr(224 + (($digits - ($digits % 4096)) / 4096)) + .chr(128 + ((($digits % 4096) - ($digits % 64)) / 64)) + .chr(128 + ($digits % 64)); } - $str = str_replace($matches['0'][$i], $out, $str); + $str = str_replace($matches[0][$i], $out, $str); } } if ($all) { - $str = str_replace(array("&", "<", ">", """, "'", "-"), - array("&","<",">","\"", "'", "-"), - $str); + return str_replace( + array('&', '<', '>', '"', ''', '-'), + array('&', '<', '>', '"', "'", '-'), + $str + ); } return $str; @@ -222,21 +244,20 @@ if ( ! function_exists('entities_to_ascii')) // ------------------------------------------------------------------------ -/** - * Word Censoring Function - * - * Supply a string and an array of disallowed words and any - * matched words will be converted to #### or to the replacement - * word you've submitted. - * - * @access public - * @param string the text string - * @param string the array of censoered words - * @param string the optional replacement value - * @return string - */ if ( ! function_exists('word_censor')) { + /** + * Word Censoring Function + * + * Supply a string and an array of disallowed words and any + * matched words will be converted to #### or to the replacement + * word you've submitted. + * + * @param string the text string + * @param string the array of censored words + * @param string the optional replacement value + * @return string + */ function word_censor($str, $censored, $replacement = '') { if ( ! is_array($censored)) @@ -254,13 +275,28 @@ if ( ! function_exists('word_censor')) foreach ($censored as $badword) { - if ($replacement != '') + $badword = str_replace('\*', '\w*?', preg_quote($badword, '/')); + if ($replacement !== '') { - $str = preg_replace("/({$delim})(".str_replace('\*', '\w*?', preg_quote($badword, '/')).")({$delim})/i", "\\1{$replacement}\\3", $str); + $str = preg_replace( + "/({$delim})(".$badword.")({$delim})/i", + "\\1{$replacement}\\3", + $str + ); } - else + elseif (preg_match_all("/{$delim}(".$badword."){$delim}/i", $str, $matches, PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE)) { - $str = preg_replace("/({$delim})(".str_replace('\*', '\w*?', preg_quote($badword, '/')).")({$delim})/ie", "'\\1'.str_repeat('#', strlen('\\2')).'\\3'", $str); + $matches = $matches[1]; + for ($i = count($matches) - 1; $i >= 0; $i--) + { + $length = strlen($matches[$i][0]); + $str = substr_replace( + $str, + str_repeat('#', $length), + $matches[$i][1], + $length + ); + } } } @@ -270,136 +306,146 @@ if ( ! function_exists('word_censor')) // ------------------------------------------------------------------------ -/** - * Code Highlighter - * - * Colorizes code strings - * - * @access public - * @param string the text string - * @return string - */ if ( ! function_exists('highlight_code')) { + /** + * Code Highlighter + * + * Colorizes code strings + * + * @param string the text string + * @return string + */ function highlight_code($str) { - // The highlight string function encodes and highlights - // brackets so we need them to start raw - $str = str_replace(array('<', '>'), array('<', '>'), $str); - - // Replace any existing PHP tags to temporary markers so they don't accidentally - // break the string out of PHP, and thus, thwart the highlighting. - - $str = str_replace(array('', '<%', '%>', '\\', ''), - array('phptagopen', 'phptagclose', 'asptagopen', 'asptagclose', 'backslashtmp', 'scriptclose'), $str); + /* The highlight string function encodes and highlights + * brackets so we need them to start raw. + * + * Also replace any existing PHP tags to temporary markers + * so they don't accidentally break the string out of PHP, + * and thus, thwart the highlighting. + */ + $str = str_replace( + array('<', '>', '', '<%', '%>', '\\', ''), + array('<', '>', 'phptagopen', 'phptagclose', 'asptagopen', 'asptagclose', 'backslashtmp', 'scriptclose'), + $str + ); // The highlight_string function requires that the text be surrounded // by PHP tags, which we will remove later - $str = ''; // ', TRUE); // Remove our artificially added PHP, and the syntax highlighting that came with it - $str = preg_replace('/<\?php( | )/i', '', $str); - $str = preg_replace('/(.*?)\?><\/span>\n<\/span>\n<\/code>/is', "$1\n\n", $str); - $str = preg_replace('/<\/span>/i', '', $str); + $str = preg_replace( + array( + '/<\?php( | )/i', + '/(.*?)\?><\/span>\n<\/span>\n<\/code>/is', + '/<\/span>/i' + ), + array( + '', + "$1\n\n", + '' + ), + $str + ); // Replace our markers back to PHP tags. - $str = str_replace(array('phptagopen', 'phptagclose', 'asptagopen', 'asptagclose', 'backslashtmp', 'scriptclose'), - array('<?', '?>', '<%', '%>', '\\', '</script>'), $str); - - return $str; + return str_replace( + array('phptagopen', 'phptagclose', 'asptagopen', 'asptagclose', 'backslashtmp', 'scriptclose'), + array('<?', '?>', '<%', '%>', '\\', '</script>'), + $str + ); } } // ------------------------------------------------------------------------ -/** - * Phrase Highlighter - * - * Highlights a phrase within a text string - * - * @access public - * @param string the text string - * @param string the phrase you'd like to highlight - * @param string the openging tag to precede the phrase with - * @param string the closing tag to end the phrase with - * @return string - */ if ( ! function_exists('highlight_phrase')) { - function highlight_phrase($str, $phrase, $tag_open = '', $tag_close = '') + /** + * Phrase Highlighter + * + * Highlights a phrase within a text string + * + * @param string $str the text string + * @param string $phrase the phrase you'd like to highlight + * @param string $tag_open the openging tag to precede the phrase with + * @param string $tag_close the closing tag to end the phrase with + * @return string + */ + function highlight_phrase($str, $phrase, $tag_open = '', $tag_close = '') { - if ($str == '') - { - return ''; - } - - if ($phrase != '') - { - return preg_replace('/('.preg_quote($phrase, '/').')/i', $tag_open."\\1".$tag_close, $str); - } - - return $str; + return ($str !== '' && $phrase !== '') + ? preg_replace('/('.preg_quote($phrase, '/').')/i'.(UTF8_ENABLED ? 'u' : ''), $tag_open.'\\1'.$tag_close, $str) + : $str; } } // ------------------------------------------------------------------------ -/** - * Convert Accented Foreign Characters to ASCII - * - * @access public - * @param string the text string - * @return string - */ if ( ! function_exists('convert_accented_characters')) { + /** + * Convert Accented Foreign Characters to ASCII + * + * @param string $str Input string + * @return string + */ function convert_accented_characters($str) { - if (defined('ENVIRONMENT') AND is_file(APPPATH.'config/'.ENVIRONMENT.'/foreign_chars.php')) + static $array_from, $array_to; + + if ( ! is_array($array_from)) { - include(APPPATH.'config/'.ENVIRONMENT.'/foreign_chars.php'); - } - elseif (is_file(APPPATH.'config/foreign_chars.php')) - { - include(APPPATH.'config/foreign_chars.php'); + if (file_exists(APPPATH.'config/foreign_chars.php')) + { + include(APPPATH.'config/foreign_chars.php'); + } + + if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/foreign_chars.php')) + { + include(APPPATH.'config/'.ENVIRONMENT.'/foreign_chars.php'); + } + + if (empty($foreign_characters) OR ! is_array($foreign_characters)) + { + $array_from = array(); + $array_to = array(); + + return $str; + } + + $array_from = array_keys($foreign_characters); + $array_to = array_values($foreign_characters); } - if ( ! isset($foreign_characters)) - { - return $str; - } - - return preg_replace(array_keys($foreign_characters), array_values($foreign_characters), $str); + return preg_replace($array_from, $array_to, $str); } } // ------------------------------------------------------------------------ -/** - * Word Wrap - * - * Wraps text at the specified character. Maintains the integrity of words. - * Anything placed between {unwrap}{/unwrap} will not be word wrapped, nor - * will URLs. - * - * @access public - * @param string the text string - * @param integer the number of characters to wrap at - * @return string - */ if ( ! function_exists('word_wrap')) { - function word_wrap($str, $charlim = '76') + /** + * Word Wrap + * + * Wraps text at the specified character. Maintains the integrity of words. + * Anything placed between {unwrap}{/unwrap} will not be word wrapped, nor + * will URLs. + * + * @param string $str the text string + * @param int $charlim = 76 the number of characters to wrap at + * @return string + */ + function word_wrap($str, $charlim = 76) { - // Se the character limit - if ( ! is_numeric($charlim)) - $charlim = 76; + // Set the character limit + is_numeric($charlim) OR $charlim = 76; // Reduce multiple spaces - $str = preg_replace("| +|", " ", $str); + $str = preg_replace('| +|', ' ', $str); // Standardize newlines if (strpos($str, "\r") !== FALSE) @@ -410,58 +456,56 @@ if ( ! function_exists('word_wrap')) // If the current word is surrounded by {unwrap} tags we'll // strip the entire chunk and replace it with a marker. $unwrap = array(); - if (preg_match_all("|(\{unwrap\}.+?\{/unwrap\})|s", $str, $matches)) + if (preg_match_all('|\{unwrap\}(.+?)\{/unwrap\}|s', $str, $matches)) { - for ($i = 0; $i < count($matches['0']); $i++) + for ($i = 0, $c = count($matches[0]); $i < $c; $i++) { - $unwrap[] = $matches['1'][$i]; - $str = str_replace($matches['1'][$i], "{{unwrapped".$i."}}", $str); + $unwrap[] = $matches[1][$i]; + $str = str_replace($matches[0][$i], '{{unwrapped'.$i.'}}', $str); } } // Use PHP's native function to do the initial wordwrap. // We set the cut flag to FALSE so that any individual words that are - // too long get left alone. In the next step we'll deal with them. + // too long get left alone. In the next step we'll deal with them. $str = wordwrap($str, $charlim, "\n", FALSE); // Split the string into individual lines of text and cycle through them - $output = ""; + $output = ''; foreach (explode("\n", $str) as $line) { // Is the line within the allowed character count? // If so we'll join it to the output and continue - if (strlen($line) <= $charlim) + if (mb_strlen($line) <= $charlim) { $output .= $line."\n"; continue; } $temp = ''; - while ((strlen($line)) > $charlim) + while (mb_strlen($line) > $charlim) { // If the over-length word is a URL we won't wrap it - if (preg_match("!\[url.+\]|://|wwww.!", $line)) + if (preg_match('!\[url.+\]|://|www\.!', $line)) { break; } // Trim the word down - $temp .= substr($line, 0, $charlim-1); - $line = substr($line, $charlim-1); + $temp .= mb_substr($line, 0, $charlim - 1); + $line = mb_substr($line, $charlim - 1); } // If $temp contains data it means we had to split up an over-length // word into smaller chunks so we'll add it back to our current line - if ($temp != '') + if ($temp !== '') { - $output .= $temp."\n".$line; + $output .= $temp."\n".$line."\n"; } else { - $output .= $line; + $output .= $line."\n"; } - - $output .= "\n"; } // Put our markers back @@ -469,59 +513,52 @@ if ( ! function_exists('word_wrap')) { foreach ($unwrap as $key => $val) { - $output = str_replace("{{unwrapped".$key."}}", $val, $output); + $output = str_replace('{{unwrapped'.$key.'}}', $val, $output); } } - // Remove the unwrap tags - $output = str_replace(array('{unwrap}', '{/unwrap}'), '', $output); - return $output; } } // ------------------------------------------------------------------------ -/** - * Ellipsize String - * - * This function will strip tags from a string, split it at its max_length and ellipsize - * - * @param string string to ellipsize - * @param integer max length of string - * @param mixed int (1|0) or float, .5, .2, etc for position to split - * @param string ellipsis ; Default '...' - * @return string ellipsized string - */ if ( ! function_exists('ellipsize')) { + /** + * Ellipsize String + * + * This function will strip tags from a string, split it at its max_length and ellipsize + * + * @param string string to ellipsize + * @param int max length of string + * @param mixed int (1|0) or float, .5, .2, etc for position to split + * @param string ellipsis ; Default '...' + * @return string ellipsized string + */ function ellipsize($str, $max_length, $position = 1, $ellipsis = '…') { // Strip tags $str = trim(strip_tags($str)); // Is the string long enough to ellipsize? - if (strlen($str) <= $max_length) + if (mb_strlen($str) <= $max_length) { return $str; } - $beg = substr($str, 0, floor($max_length * $position)); - + $beg = mb_substr($str, 0, floor($max_length * $position)); $position = ($position > 1) ? 1 : $position; if ($position === 1) { - $end = substr($str, 0, -($max_length - strlen($beg))); + $end = mb_substr($str, 0, -($max_length - mb_strlen($beg))); } else { - $end = substr($str, -($max_length - strlen($beg))); + $end = mb_substr($str, -($max_length - mb_strlen($beg))); } return $beg.$ellipsis.$end; } } - -/* End of file text_helper.php */ -/* Location: ./system/helpers/text_helper.php */ \ No newline at end of file diff --git a/src/system/helpers/typography_helper.php b/src/system/helpers/typography_helper.php index 1e7a6abf..928cb6d0 100644 --- a/src/system/helpers/typography_helper.php +++ b/src/system/helpers/typography_helper.php @@ -1,20 +1,41 @@ -load->library('typography'); - return $CI->typography->nl2br_except_pre($str); } } // ------------------------------------------------------------------------ -/** - * Auto Typography Wrapper Function - * - * - * @access public - * @param string - * @param bool whether to allow javascript event handlers - * @param bool whether to reduce multiple instances of double newlines to two - * @return string - */ if ( ! function_exists('auto_typography')) { - function auto_typography($str, $strip_js_event_handlers = TRUE, $reduce_linebreaks = FALSE) + /** + * Auto Typography Wrapper Function + * + * @param string $str + * @param bool $reduce_linebreaks = FALSE whether to reduce multiple instances of double newlines to two + * @return string + */ + function auto_typography($str, $reduce_linebreaks = FALSE) { $CI =& get_instance(); $CI->load->library('typography'); - return $CI->typography->auto_typography($str, $strip_js_event_handlers, $reduce_linebreaks); + return $CI->typography->auto_typography($str, $reduce_linebreaks); } } - // -------------------------------------------------------------------- -/** - * HTML Entities Decode - * - * This function is a replacement for html_entity_decode() - * - * @access public - * @param string - * @return string - */ if ( ! function_exists('entity_decode')) { - function entity_decode($str, $charset='UTF-8') + /** + * HTML Entities Decode + * + * This function is a replacement for html_entity_decode() + * + * @param string + * @param string + * @return string + */ + function entity_decode($str, $charset = NULL) { - global $SEC; - return $SEC->entity_decode($str, $charset); + return get_instance()->security->entity_decode($str, $charset); } } - -/* End of file typography_helper.php */ -/* Location: ./system/helpers/typography_helper.php */ \ No newline at end of file diff --git a/src/system/helpers/url_helper.php b/src/system/helpers/url_helper.php index 4bdf067d..fd7b5e11 100644 --- a/src/system/helpers/url_helper.php +++ b/src/system/helpers/url_helper.php @@ -1,20 +1,41 @@ -config->site_url($uri); + return get_instance()->config->site_url($uri, $protocol); } } // ------------------------------------------------------------------------ -/** - * Base URL - * - * Create a local URL based on your basepath. - * Segments can be passed in as a string or an array, same as site_url - * or a URL to a file can be passed in, e.g. to an image file. - * - * @access public - * @param string - * @return string - */ if ( ! function_exists('base_url')) { - function base_url($uri = '') + /** + * Base URL + * + * Create a local URL based on your basepath. + * Segments can be passed in as a string or an array, same as site_url + * or a URL to a file can be passed in, e.g. to an image file. + * + * @param string $uri + * @param string $protocol + * @return string + */ + function base_url($uri = '', $protocol = NULL) { - $CI =& get_instance(); - return $CI->config->base_url($uri); + return get_instance()->config->base_url($uri, $protocol); } } // ------------------------------------------------------------------------ -/** - * Current URL - * - * Returns the full URL (including segments) of the page where this - * function is placed - * - * @access public - * @return string - */ if ( ! function_exists('current_url')) { + /** + * Current URL + * + * Returns the full URL (including segments) of the page where this + * function is placed + * + * @return string + */ function current_url() { $CI =& get_instance(); @@ -90,78 +108,69 @@ if ( ! function_exists('current_url')) } // ------------------------------------------------------------------------ -/** - * URL String - * - * Returns the URI segments. - * - * @access public - * @return string - */ + if ( ! function_exists('uri_string')) { + /** + * URL String + * + * Returns the URI segments. + * + * @return string + */ function uri_string() { - $CI =& get_instance(); - return $CI->uri->uri_string(); + return get_instance()->uri->uri_string(); } } // ------------------------------------------------------------------------ -/** - * Index page - * - * Returns the "index_page" from your config file - * - * @access public - * @return string - */ if ( ! function_exists('index_page')) { + /** + * Index page + * + * Returns the "index_page" from your config file + * + * @return string + */ function index_page() { - $CI =& get_instance(); - return $CI->config->item('index_page'); + return get_instance()->config->item('index_page'); } } // ------------------------------------------------------------------------ -/** - * Anchor Link - * - * Creates an anchor based on the local URL. - * - * @access public - * @param string the URL - * @param string the link title - * @param mixed any attributes - * @return string - */ if ( ! function_exists('anchor')) { + /** + * Anchor Link + * + * Creates an anchor based on the local URL. + * + * @param string the URL + * @param string the link title + * @param mixed any attributes + * @return string + */ function anchor($uri = '', $title = '', $attributes = '') { $title = (string) $title; - if ( ! is_array($uri)) - { - $site_url = ( ! preg_match('!^\w+://! i', $uri)) ? site_url($uri) : $uri; - } - else - { - $site_url = site_url($uri); - } + $site_url = is_array($uri) + ? site_url($uri) + : (preg_match('#^(\w+:)?//#i', $uri) ? $uri : site_url($uri)); - if ($title == '') + if ($title === '') { $title = $site_url; } - if ($attributes != '') + if ($attributes !== '') { - $attributes = _parse_attributes($attributes); + $attributes = _stringify_attributes($attributes); } return ''.$title.''; @@ -170,139 +179,141 @@ if ( ! function_exists('anchor')) // ------------------------------------------------------------------------ -/** - * Anchor Link - Pop-up version - * - * Creates an anchor based on the local URL. The link - * opens a new window based on the attributes specified. - * - * @access public - * @param string the URL - * @param string the link title - * @param mixed any attributes - * @return string - */ if ( ! function_exists('anchor_popup')) { + /** + * Anchor Link - Pop-up version + * + * Creates an anchor based on the local URL. The link + * opens a new window based on the attributes specified. + * + * @param string the URL + * @param string the link title + * @param mixed any attributes + * @return string + */ function anchor_popup($uri = '', $title = '', $attributes = FALSE) { $title = (string) $title; + $site_url = preg_match('#^(\w+:)?//#i', $uri) ? $uri : site_url($uri); - $site_url = ( ! preg_match('!^\w+://! i', $uri)) ? site_url($uri) : $uri; - - if ($title == '') + if ($title === '') { $title = $site_url; } if ($attributes === FALSE) { - return "".$title.""; + return '".$title.''; } if ( ! is_array($attributes)) { - $attributes = array(); + $attributes = array($attributes); + + // Ref: http://www.w3schools.com/jsref/met_win_open.asp + $window_name = '_blank'; + } + elseif ( ! empty($attributes['window_name'])) + { + $window_name = $attributes['window_name']; + unset($attributes['window_name']); + } + else + { + $window_name = '_blank'; } - foreach (array('width' => '800', 'height' => '600', 'scrollbars' => 'yes', 'status' => 'yes', 'resizable' => 'yes', 'screenx' => '0', 'screeny' => '0', ) as $key => $val) + foreach (array('width' => '800', 'height' => '600', 'scrollbars' => 'yes', 'menubar' => 'no', 'status' => 'yes', 'resizable' => 'yes', 'screenx' => '0', 'screeny' => '0') as $key => $val) { - $atts[$key] = ( ! isset($attributes[$key])) ? $val : $attributes[$key]; + $atts[$key] = isset($attributes[$key]) ? $attributes[$key] : $val; unset($attributes[$key]); } - if ($attributes != '') - { - $attributes = _parse_attributes($attributes); - } + $attributes = _stringify_attributes($attributes); - return "".$title.""; + return ''.$title.''; } } // ------------------------------------------------------------------------ -/** - * Mailto Link - * - * @access public - * @param string the email address - * @param string the link title - * @param mixed any attributes - * @return string - */ if ( ! function_exists('mailto')) { + /** + * Mailto Link + * + * @param string the email address + * @param string the link title + * @param mixed any attributes + * @return string + */ function mailto($email, $title = '', $attributes = '') { $title = (string) $title; - if ($title == "") + if ($title === '') { $title = $email; } - $attributes = _parse_attributes($attributes); - - return ''.$title.''; + return ''.$title.''; } } // ------------------------------------------------------------------------ -/** - * Encoded Mailto Link - * - * Create a spam-protected mailto link written in Javascript - * - * @access public - * @param string the email address - * @param string the link title - * @param mixed any attributes - * @return string - */ if ( ! function_exists('safe_mailto')) { + /** + * Encoded Mailto Link + * + * Create a spam-protected mailto link written in Javascript + * + * @param string the email address + * @param string the link title + * @param mixed any attributes + * @return string + */ function safe_mailto($email, $title = '', $attributes = '') { $title = (string) $title; - if ($title == "") + if ($title === '') { $title = $email; } - for ($i = 0; $i < 16; $i++) - { - $x[] = substr(' $val) { - $x[] = ' '.$key.'="'; - for ($i = 0; $i < strlen($val); $i++) + $x[] = ' '.$key.'="'; + for ($i = 0, $l = strlen($val); $i < $l; $i++) { - $x[] = "|".ord(substr($val, $i, 1)); + $x[] = '|'.ord($val[$i]); } $x[] = '"'; } } else { - for ($i = 0; $i < strlen($attributes); $i++) + for ($i = 0, $l = strlen($attributes); $i < $l; $i++) { - $x[] = substr($attributes, $i, 1); + $x[] = $attributes[$i]; } } } @@ -310,26 +321,28 @@ if ( ! function_exists('safe_mailto')) $x[] = '>'; $temp = array(); - for ($i = 0; $i < strlen($title); $i++) + for ($i = 0, $l = strlen($title); $i < $l; $i++) { $ordinal = ord($title[$i]); if ($ordinal < 128) { - $x[] = "|".$ordinal; + $x[] = '|'.$ordinal; } else { - if (count($temp) == 0) + if (count($temp) === 0) { $count = ($ordinal < 224) ? 2 : 3; } $temp[] = $ordinal; - if (count($temp) == $count) + if (count($temp) === $count) { - $number = ($count == 3) ? (($temp['0'] % 16) * 4096) + (($temp['1'] % 64) * 64) + ($temp['2'] % 64) : (($temp['0'] % 32) * 64) + ($temp['1'] % 64); - $x[] = "|".$number; + $number = ($count === 3) + ? (($temp[0] % 16) * 4096) + (($temp[1] % 64) * 64) + ($temp[2] % 64) + : (($temp[0] % 32) * 64) + ($temp[1] % 64); + $x[] = '|'.$number; $count = 1; $temp = array(); } @@ -339,89 +352,75 @@ if ( ! function_exists('safe_mailto')) $x[] = '<'; $x[] = '/'; $x[] = 'a'; $x[] = '>'; $x = array_reverse($x); - ob_start(); - ?>= 0; i=i-1) {\n" + ."\t\tif (l[i].substring(0, 1) === '|') document.write(\"&#\"+unescape(l[i].substring(1))+\";\");\n" + ."\t\telse document.write(unescape(l[i]));\n" + ."\t}\n" + ."\t//]]>\n" + .''; + + return $output; } } // ------------------------------------------------------------------------ -/** - * Auto-linker - * - * Automatically links URL and Email addresses. - * Note: There's a bit of extra code here to deal with - * URLs or emails that end in a period. We'll strip these - * off and add them after the link. - * - * @access public - * @param string the string - * @param string the type: email, url, or both - * @param bool whether to create pop-up links - * @return string - */ if ( ! function_exists('auto_link')) { + /** + * Auto-linker + * + * Automatically links URL and Email addresses. + * Note: There's a bit of extra code here to deal with + * URLs or emails that end in a period. We'll strip these + * off and add them after the link. + * + * @param string the string + * @param string the type: email, url, or both + * @param bool whether to create pop-up links + * @return string + */ function auto_link($str, $type = 'both', $popup = FALSE) { - if ($type != 'email') + // Find and replace any URLs. + if ($type !== 'email' && preg_match_all('#(\w*://|www\.)[^\s()<>;]+\w#i', $str, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) { - if (preg_match_all("#(^|\s|\()((http(s?)://)|(www\.))(\w+[^\s\)\<]+)#i", $str, $matches)) + // Set our target HTML if using popup links. + $target = ($popup) ? ' target="_blank"' : ''; + + // We process the links in reverse order (last -> first) so that + // the returned string offsets from preg_match_all() are not + // moved as we add more HTML. + foreach (array_reverse($matches) as $match) { - $pop = ($popup == TRUE) ? " target=\"_blank\" " : ""; - - for ($i = 0; $i < count($matches['0']); $i++) - { - $period = ''; - if (preg_match("|\.$|", $matches['6'][$i])) - { - $period = '.'; - $matches['6'][$i] = substr($matches['6'][$i], 0, -1); - } - - $str = str_replace($matches['0'][$i], - $matches['1'][$i].'http'. - $matches['4'][$i].'://'. - $matches['5'][$i]. - $matches['6'][$i].''. - $period, $str); - } + // $match[0] is the matched string/link + // $match[1] is either a protocol prefix or 'www.' + // + // With PREG_OFFSET_CAPTURE, both of the above is an array, + // where the actual value is held in [0] and its offset at the [1] index. + $a = ''.$match[0][0].''; + $str = substr_replace($str, $a, $match[0][1], strlen($match[0][0])); } } - if ($type != 'url') + // Find and replace any emails. + if ($type !== 'url' && preg_match_all('#([\w\.\-\+]+@[a-z0-9\-]+\.[a-z0-9\-\.]+[^[:punct:]\s])#i', $str, $matches, PREG_OFFSET_CAPTURE)) { - if (preg_match_all("/([a-zA-Z0-9_\.\-\+]+)@([a-zA-Z0-9\-]+)\.([a-zA-Z0-9\-\.]*)/i", $str, $matches)) + foreach (array_reverse($matches[0]) as $match) { - for ($i = 0; $i < count($matches['0']); $i++) + if (filter_var($match[0], FILTER_VALIDATE_EMAIL) !== FALSE) { - $period = ''; - if (preg_match("|\.$|", $matches['3'][$i])) - { - $period = '.'; - $matches['3'][$i] = substr($matches['3'][$i], 0, -1); - } - - $str = str_replace($matches['0'][$i], safe_mailto($matches['1'][$i].'@'.$matches['2'][$i].'.'.$matches['3'][$i]).$period, $str); + $str = substr_replace($str, safe_mailto($match[0]), $match[1], strlen($match[0])); } } } @@ -432,20 +431,19 @@ if ( ! function_exists('auto_link')) // ------------------------------------------------------------------------ -/** - * Prep URL - * - * Simply adds the http:// part if no scheme is included - * - * @access public - * @param string the URL - * @return string - */ if ( ! function_exists('prep_url')) { + /** + * Prep URL + * + * Simply adds the http:// part if no scheme is included + * + * @param string the URL + * @return string + */ function prep_url($str = '') { - if ($str == 'http://' OR $str == '') + if ($str === 'http://' OR $str === '') { return ''; } @@ -454,7 +452,7 @@ if ( ! function_exists('prep_url')) if ( ! $url OR ! isset($url['scheme'])) { - $str = 'http://'.$str; + return 'http://'.$str; } return $str; @@ -463,45 +461,46 @@ if ( ! function_exists('prep_url')) // ------------------------------------------------------------------------ -/** - * Create URL Title - * - * Takes a "title" string as input and creates a - * human-friendly URL string with a "separator" string - * as the word separator. - * - * @access public - * @param string the string - * @param string the separator - * @return string - */ if ( ! function_exists('url_title')) { + /** + * Create URL Title + * + * Takes a "title" string as input and creates a + * human-friendly URL string with a "separator" string + * as the word separator. + * + * @todo Remove old 'dash' and 'underscore' usage in 3.1+. + * @param string $str Input string + * @param string $separator Word separator + * (usually '-' or '_') + * @param bool $lowercase Whether to transform the output string to lowercase + * @return string + */ function url_title($str, $separator = '-', $lowercase = FALSE) { - if ($separator == 'dash') + if ($separator === 'dash') { - $separator = '-'; + $separator = '-'; } - else if ($separator == 'underscore') + elseif ($separator === 'underscore') { - $separator = '_'; + $separator = '_'; } - - $q_separator = preg_quote($separator); + + $q_separator = preg_quote($separator, '#'); $trans = array( - '&.+?;' => '', - '[^a-z0-9 _-]' => '', - '\s+' => $separator, - '('.$q_separator.')+' => $separator + '&.+?;' => '', + '[^\w\d _-]' => '', + '\s+' => $separator, + '('.$q_separator.')+' => $separator ); $str = strip_tags($str); - foreach ($trans as $key => $val) { - $str = preg_replace("#".$key."#i", $val, $str); + $str = preg_replace('#'.$key.'#i'.(UTF8_ENABLED ? 'u' : ''), $val, $str); } if ($lowercase === TRUE) @@ -509,87 +508,62 @@ if ( ! function_exists('url_title')) $str = strtolower($str); } - return trim($str, $separator); + return trim(trim($str, $separator)); } } // ------------------------------------------------------------------------ -/** - * Header Redirect - * - * Header redirect in two flavors - * For very fine grained control over headers, you could use the Output - * Library's set_header() function. - * - * @access public - * @param string the URL - * @param string the method: location or redirect - * @return string - */ if ( ! function_exists('redirect')) { - function redirect($uri = '', $method = 'location', $http_response_code = 302) + /** + * Header Redirect + * + * Header redirect in two flavors + * For very fine grained control over headers, you could use the Output + * Library's set_header() function. + * + * @param string $uri URL + * @param string $method Redirect method + * 'auto', 'location' or 'refresh' + * @param int $code HTTP Response status code + * @return void + */ + function redirect($uri = '', $method = 'auto', $code = NULL) { - if ( ! preg_match('#^https?://#i', $uri)) + if ( ! preg_match('#^(\w+:)?//#i', $uri)) { $uri = site_url($uri); } - switch($method) + // IIS environment likely? Use 'refresh' for better compatibility + if ($method === 'auto' && isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') !== FALSE) { - case 'refresh' : header("Refresh:0;url=".$uri); + $method = 'refresh'; + } + elseif ($method !== 'refresh' && (empty($code) OR ! is_numeric($code))) + { + if (isset($_SERVER['SERVER_PROTOCOL'], $_SERVER['REQUEST_METHOD']) && $_SERVER['SERVER_PROTOCOL'] === 'HTTP/1.1') + { + $code = ($_SERVER['REQUEST_METHOD'] !== 'GET') + ? 303 // reference: http://en.wikipedia.org/wiki/Post/Redirect/Get + : 307; + } + else + { + $code = 302; + } + } + + switch ($method) + { + case 'refresh': + header('Refresh:0;url='.$uri); break; - default : header("Location: ".$uri, TRUE, $http_response_code); + default: + header('Location: '.$uri, TRUE, $code); break; } exit; } } - -// ------------------------------------------------------------------------ - -/** - * Parse out the attributes - * - * Some of the functions use this - * - * @access private - * @param array - * @param bool - * @return string - */ -if ( ! function_exists('_parse_attributes')) -{ - function _parse_attributes($attributes, $javascript = FALSE) - { - if (is_string($attributes)) - { - return ($attributes != '') ? ' '.$attributes : ''; - } - - $att = ''; - foreach ($attributes as $key => $val) - { - if ($javascript == TRUE) - { - $att .= $key . '=' . $val . ','; - } - else - { - $att .= ' ' . $key . '="' . $val . '"'; - } - } - - if ($javascript == TRUE AND $att != '') - { - $att = substr($att, 0, -1); - } - - return $att; - } -} - - -/* End of file url_helper.php */ -/* Location: ./system/helpers/url_helper.php */ \ No newline at end of file diff --git a/src/system/helpers/xml_helper.php b/src/system/helpers/xml_helper.php index 9c1fadbf..3489da91 100644 --- a/src/system/helpers/xml_helper.php +++ b/src/system/helpers/xml_helper.php @@ -1,20 +1,41 @@ -","\"", "'", "-"), - array("&", "<", ">", """, "'", "-"), - $str); + $str = str_replace( + array('&', '<', '>', '"', "'", '-'), + array('&', '<', '>', '"', ''', '-'), + $str + ); // Decode the temp markers back to entities - $str = preg_replace("/$temp(\d+);/","&#\\1;",$str); + $str = preg_replace('/'.$temp.'(\d+);/', '&#\\1;', $str); if ($protect_all === TRUE) { - $str = preg_replace("/$temp(\w+);/","&\\1;", $str); + return preg_replace('/'.$temp.'(\w+);/', '&\\1;', $str); } return $str; } } - -// ------------------------------------------------------------------------ - -/* End of file xml_helper.php */ -/* Location: ./system/helpers/xml_helper.php */ \ No newline at end of file diff --git a/src/system/index.html b/src/system/index.html index c942a79c..b702fbc3 100644 --- a/src/system/index.html +++ b/src/system/index.html @@ -1,3 +1,4 @@ + 403 Forbidden @@ -7,4 +8,4 @@

    Directory access is forbidden.

    - \ No newline at end of file + diff --git a/src/system/language/english/calendar_lang.php b/src/system/language/english/calendar_lang.php index 3e631236..8af5e805 100644 --- a/src/system/language/english/calendar_lang.php +++ b/src/system/language/english/calendar_lang.php @@ -1,51 +1,84 @@ 403 Forbidden @@ -7,4 +8,4 @@

    Directory access is forbidden.

    - \ No newline at end of file + diff --git a/src/system/language/english/migration_lang.php b/src/system/language/english/migration_lang.php index f17530f0..bce9210d 100644 --- a/src/system/language/english/migration_lang.php +++ b/src/system/language/english/migration_lang.php @@ -1,13 +1,47 @@ 403 Forbidden @@ -7,4 +8,4 @@

    Directory access is forbidden.

    - \ No newline at end of file + diff --git a/src/system/libraries/Cache/Cache.php b/src/system/libraries/Cache/Cache.php index b1453e70..349af157 100644 --- a/src/system/libraries/Cache/Cache.php +++ b/src/system/libraries/Cache/Cache.php @@ -1,20 +1,41 @@ -_adapter = $config['adapter']; + isset($config['backup']) && $this->_backup_driver = $config['backup']; + isset($config['key_prefix']) && $this->key_prefix = $config['key_prefix']; + + // If the specified adapter isn't available, check the backup. + if ( ! $this->is_supported($this->_adapter)) { - $this->_initialize($config); + if ( ! $this->is_supported($this->_backup_driver)) + { + // Backup isn't supported either. Default to 'Dummy' driver. + log_message('error', 'Cache adapter "'.$this->_adapter.'" and backup "'.$this->_backup_driver.'" are both unavailable. Cache is now using "Dummy" adapter.'); + $this->_adapter = 'dummy'; + } + else + { + // Backup is supported. Set it to primary. + log_message('debug', 'Cache adapter "'.$this->_adapter.'" is unavailable. Falling back to "'.$this->_backup_driver.'" backup adapter.'); + $this->_adapter = $this->_backup_driver; + } } } @@ -55,15 +127,15 @@ class CI_Cache extends CI_Driver_Library { /** * Get * - * Look for a value in the cache. If it exists, return the data + * Look for a value in the cache. If it exists, return the data * if not, return FALSE * - * @param string - * @return mixed value that is stored/FALSE on failure + * @param string $id + * @return mixed value matching $id or FALSE on failure */ public function get($id) { - return $this->{$this->_adapter}->get($id); + return $this->{$this->_adapter}->get($this->key_prefix.$id); } // ------------------------------------------------------------------------ @@ -71,15 +143,15 @@ class CI_Cache extends CI_Driver_Library { /** * Cache Save * - * @param string Unique Key - * @param mixed Data to store - * @param int Length of time (in seconds) to cache the data - * - * @return boolean true on success/false on failure + * @param string $id Cache ID + * @param mixed $data Data to store + * @param int $ttl Cache TTL (in seconds) + * @param bool $raw Whether to store the raw value + * @return bool TRUE on success, FALSE on failure */ - public function save($id, $data, $ttl = 60) + public function save($id, $data, $ttl = 60, $raw = FALSE) { - return $this->{$this->_adapter}->save($id, $data, $ttl); + return $this->{$this->_adapter}->save($this->key_prefix.$id, $data, $ttl, $raw); } // ------------------------------------------------------------------------ @@ -87,12 +159,40 @@ class CI_Cache extends CI_Driver_Library { /** * Delete from Cache * - * @param mixed unique identifier of the item in the cache - * @return boolean true on success/false on failure + * @param string $id Cache ID + * @return bool TRUE on success, FALSE on failure */ public function delete($id) { - return $this->{$this->_adapter}->delete($id); + return $this->{$this->_adapter}->delete($this->key_prefix.$id); + } + + // ------------------------------------------------------------------------ + + /** + * Increment a raw value + * + * @param string $id Cache ID + * @param int $offset Step/value to add + * @return mixed New value on success or FALSE on failure + */ + public function increment($id, $offset = 1) + { + return $this->{$this->_adapter}->increment($this->key_prefix.$id, $offset); + } + + // ------------------------------------------------------------------------ + + /** + * Decrement a raw value + * + * @param string $id Cache ID + * @param int $offset Step/value to reduce by + * @return mixed New value on success or FALSE on failure + */ + public function decrement($id, $offset = 1) + { + return $this->{$this->_adapter}->decrement($this->key_prefix.$id, $offset); } // ------------------------------------------------------------------------ @@ -100,7 +200,7 @@ class CI_Cache extends CI_Driver_Library { /** * Clean the cache * - * @return boolean false on failure/true on success + * @return bool TRUE on success, FALSE on failure */ public function clean() { @@ -112,8 +212,8 @@ class CI_Cache extends CI_Driver_Library { /** * Cache Info * - * @param string user/filehits - * @return mixed array on success, false on failure + * @param string $type = 'user' user/filehits + * @return mixed array containing cache info on success OR FALSE on failure */ public function cache_info($type = 'user') { @@ -125,48 +225,12 @@ class CI_Cache extends CI_Driver_Library { /** * Get Cache Metadata * - * @param mixed key to get cache metadata on - * @return mixed return value from child method + * @param string $id key to get cache metadata on + * @return mixed cache item metadata */ public function get_metadata($id) { - return $this->{$this->_adapter}->get_metadata($id); - } - - // ------------------------------------------------------------------------ - - /** - * Initialize - * - * Initialize class properties based on the configuration array. - * - * @param array - * @return void - */ - private function _initialize($config) - { - $default_config = array( - 'adapter', - 'memcached' - ); - - foreach ($default_config as $key) - { - if (isset($config[$key])) - { - $param = '_'.$key; - - $this->{$param} = $config[$key]; - } - } - - if (isset($config['backup'])) - { - if (in_array('cache_'.$config['backup'], $this->valid_drivers)) - { - $this->_backup_driver = $config['backup']; - } - } + return $this->{$this->_adapter}->get_metadata($this->key_prefix.$id); } // ------------------------------------------------------------------------ @@ -174,43 +238,18 @@ class CI_Cache extends CI_Driver_Library { /** * Is the requested driver supported in this environment? * - * @param string The driver to test. - * @return array + * @param string $driver The driver to test + * @return array */ public function is_supported($driver) { - static $support = array(); + static $support; - if ( ! isset($support[$driver])) + if ( ! isset($support, $support[$driver])) { $support[$driver] = $this->{$driver}->is_supported(); } return $support[$driver]; } - - // ------------------------------------------------------------------------ - - /** - * __get() - * - * @param child - * @return object - */ - public function __get($child) - { - $obj = parent::__get($child); - - if ( ! $this->is_supported($child)) - { - $this->_adapter = $this->_backup_driver; - $obj = parent::__get($this->_adapter); - } - - return $obj; - } - } - -/* End of file Cache.php */ -/* Location: ./system/libraries/Cache/Cache.php */ diff --git a/src/system/libraries/Cache/drivers/Cache_apc.php b/src/system/libraries/Cache/drivers/Cache_apc.php index 5fe717fc..07ea8f47 100644 --- a/src/system/libraries/Cache/drivers/Cache_apc.php +++ b/src/system/libraries/Cache/drivers/Cache_apc.php @@ -1,20 +1,41 @@ -is_supported()) + { + log_message('error', 'Cache: Failed to initialize APC; extension not loaded/enabled?'); + } + } + + // ------------------------------------------------------------------------ + /** * Get * - * Look for a value in the cache. If it exists, return the data + * Look for a value in the cache. If it exists, return the data * if not, return FALSE * - * @param string - * @return mixed value that is stored/FALSE on failure + * @param string + * @return mixed value that is stored/FALSE on failure */ public function get($id) { - $data = apc_fetch($id); + $success = FALSE; + $data = apc_fetch($id, $success); - return (is_array($data)) ? $data[0] : FALSE; + if ($success === TRUE) + { + return is_array($data) + ? unserialize($data[0]) + : $data; + } + + return FALSE; } // ------------------------------------------------------------------------ @@ -48,15 +95,21 @@ class CI_Cache_apc extends CI_Driver { /** * Cache Save * - * @param string Unique Key - * @param mixed Data to store - * @param int Length of time (in seconds) to cache the data - * - * @return boolean true on success/false on failure + * @param string $id Cache ID + * @param mixed $data Data to store + * @param int $ttol Length of time (in seconds) to cache the data + * @param bool $raw Whether to store the raw value + * @return bool TRUE on success, FALSE on failure */ - public function save($id, $data, $ttl = 60) + public function save($id, $data, $ttl = 60, $raw = FALSE) { - return apc_store($id, array($data, time(), $ttl), $ttl); + $ttl = (int) $ttl; + + return apc_store( + $id, + ($raw === TRUE ? $data : array(serialize($data), time(), $ttl)), + $ttl + ); } // ------------------------------------------------------------------------ @@ -64,8 +117,8 @@ class CI_Cache_apc extends CI_Driver { /** * Delete from Cache * - * @param mixed unique identifier of the item in the cache - * @param boolean true on success/false on failure + * @param mixed unique identifier of the item in the cache + * @return bool true on success/false on failure */ public function delete($id) { @@ -74,10 +127,38 @@ class CI_Cache_apc extends CI_Driver { // ------------------------------------------------------------------------ + /** + * Increment a raw value + * + * @param string $id Cache ID + * @param int $offset Step/value to add + * @return mixed New value on success or FALSE on failure + */ + public function increment($id, $offset = 1) + { + return apc_inc($id, $offset); + } + + // ------------------------------------------------------------------------ + + /** + * Decrement a raw value + * + * @param string $id Cache ID + * @param int $offset Step/value to reduce by + * @return mixed New value on success or FALSE on failure + */ + public function decrement($id, $offset = 1) + { + return apc_dec($id, $offset); + } + + // ------------------------------------------------------------------------ + /** * Clean the cache * - * @return boolean false on failure/true on success + * @return bool false on failure/true on success */ public function clean() { @@ -89,27 +170,28 @@ class CI_Cache_apc extends CI_Driver { /** * Cache Info * - * @param string user/filehits - * @return mixed array on success, false on failure + * @param string user/filehits + * @return mixed array on success, false on failure */ - public function cache_info($type = NULL) - { - return apc_cache_info($type); - } + public function cache_info($type = NULL) + { + return apc_cache_info($type); + } // ------------------------------------------------------------------------ /** * Get Cache Metadata * - * @param mixed key to get cache metadata on - * @return mixed array on success/false on failure + * @param mixed key to get cache metadata on + * @return mixed array on success/false on failure */ public function get_metadata($id) { - $stored = apc_fetch($id); + $success = FALSE; + $stored = apc_fetch($id, $success); - if (count($stored) !== 3) + if ($success === FALSE OR count($stored) !== 3) { return FALSE; } @@ -119,7 +201,7 @@ class CI_Cache_apc extends CI_Driver { return array( 'expire' => $time + $ttl, 'mtime' => $time, - 'data' => $data + 'data' => unserialize($data) ); } @@ -129,19 +211,11 @@ class CI_Cache_apc extends CI_Driver { * is_supported() * * Check to see if APC is available on this system, bail if it isn't. + * + * @return bool */ public function is_supported() { - if ( ! extension_loaded('apc') OR ini_get('apc.enabled') != "1") - { - log_message('error', 'The APC PHP extension must be loaded to use APC Cache.'); - return FALSE; - } - - return TRUE; + return (extension_loaded('apc') && ini_get('apc.enabled')); } - } - -/* End of file Cache_apc.php */ -/* Location: ./system/libraries/Cache/drivers/Cache_apc.php */ \ No newline at end of file diff --git a/src/system/libraries/Cache/drivers/Cache_dummy.php b/src/system/libraries/Cache/drivers/Cache_dummy.php index 25462537..4323a68a 100644 --- a/src/system/libraries/Cache/drivers/Cache_dummy.php +++ b/src/system/libraries/Cache/drivers/Cache_dummy.php @@ -1,20 +1,41 @@ -load->helper('file'); - $path = $CI->config->item('cache_path'); - - $this->_cache_path = ($path == '') ? APPPATH.'cache/' : $path; + $this->_cache_path = ($path === '') ? APPPATH.'cache/' : $path; } // ------------------------------------------------------------------------ @@ -47,26 +73,13 @@ class CI_Cache_file extends CI_Driver { /** * Fetch from cache * - * @param mixed unique key id - * @return mixed data on success/false on failure + * @param string $id Cache ID + * @return mixed Data on success, FALSE on failure */ public function get($id) { - if ( ! file_exists($this->_cache_path.$id)) - { - return FALSE; - } - - $data = read_file($this->_cache_path.$id); - $data = unserialize($data); - - if (time() > $data['time'] + $data['ttl']) - { - unlink($this->_cache_path.$id); - return FALSE; - } - - return $data['data']; + $data = $this->_get($id); + return is_array($data) ? $data['data'] : FALSE; } // ------------------------------------------------------------------------ @@ -74,23 +87,23 @@ class CI_Cache_file extends CI_Driver { /** * Save into cache * - * @param string unique key - * @param mixed data to store - * @param int length of time (in seconds) the cache is valid - * - Default is 60 seconds - * @return boolean true on success/false on failure + * @param string $id Cache ID + * @param mixed $data Data to store + * @param int $ttl Time to live in seconds + * @param bool $raw Whether to store the raw value (unused) + * @return bool TRUE on success, FALSE on failure */ - public function save($id, $data, $ttl = 60) + public function save($id, $data, $ttl = 60, $raw = FALSE) { $contents = array( - 'time' => time(), - 'ttl' => $ttl, - 'data' => $data - ); + 'time' => time(), + 'ttl' => $ttl, + 'data' => $data + ); if (write_file($this->_cache_path.$id, serialize($contents))) { - @chmod($this->_cache_path.$id, 0777); + chmod($this->_cache_path.$id, 0640); return TRUE; } @@ -102,12 +115,68 @@ class CI_Cache_file extends CI_Driver { /** * Delete from Cache * - * @param mixed unique identifier of item in cache - * @return boolean true on success/false on failure + * @param mixed unique identifier of item in cache + * @return bool true on success/false on failure */ public function delete($id) { - return unlink($this->_cache_path.$id); + return file_exists($this->_cache_path.$id) ? unlink($this->_cache_path.$id) : FALSE; + } + + // ------------------------------------------------------------------------ + + /** + * Increment a raw value + * + * @param string $id Cache ID + * @param int $offset Step/value to add + * @return New value on success, FALSE on failure + */ + public function increment($id, $offset = 1) + { + $data = $this->_get($id); + + if ($data === FALSE) + { + $data = array('data' => 0, 'ttl' => 60); + } + elseif ( ! is_int($data['data'])) + { + return FALSE; + } + + $new_value = $data['data'] + $offset; + return $this->save($id, $new_value, $data['ttl']) + ? $new_value + : FALSE; + } + + // ------------------------------------------------------------------------ + + /** + * Decrement a raw value + * + * @param string $id Cache ID + * @param int $offset Step/value to reduce by + * @return New value on success, FALSE on failure + */ + public function decrement($id, $offset = 1) + { + $data = $this->_get($id); + + if ($data === FALSE) + { + $data = array('data' => 0, 'ttl' => 60); + } + elseif ( ! is_int($data['data'])) + { + return FALSE; + } + + $new_value = $data['data'] - $offset; + return $this->save($id, $new_value, $data['ttl']) + ? $new_value + : FALSE; } // ------------------------------------------------------------------------ @@ -115,11 +184,11 @@ class CI_Cache_file extends CI_Driver { /** * Clean the Cache * - * @return boolean false on failure/true on success + * @return bool false on failure/true on success */ public function clean() { - return delete_files($this->_cache_path); + return delete_files($this->_cache_path, FALSE, TRUE); } // ------------------------------------------------------------------------ @@ -129,8 +198,8 @@ class CI_Cache_file extends CI_Driver { * * Not supported by file-based caching * - * @param string user/filehits - * @return mixed FALSE + * @param string user/filehits + * @return mixed FALSE */ public function cache_info($type = NULL) { @@ -142,8 +211,8 @@ class CI_Cache_file extends CI_Driver { /** * Get Cache Metadata * - * @param mixed key to get cache metadata on - * @return mixed FALSE on failure, array on success. + * @param mixed key to get cache metadata on + * @return mixed FALSE on failure, array on success. */ public function get_metadata($id) { @@ -152,8 +221,7 @@ class CI_Cache_file extends CI_Driver { return FALSE; } - $data = read_file($this->_cache_path.$id); - $data = unserialize($data); + $data = unserialize(file_get_contents($this->_cache_path.$id)); if (is_array($data)) { @@ -165,8 +233,8 @@ class CI_Cache_file extends CI_Driver { } return array( - 'expire' => $mtime + $data['ttl'], - 'mtime' => $mtime + 'expire' => $mtime + $data['ttl'], + 'mtime' => $mtime ); } @@ -180,14 +248,39 @@ class CI_Cache_file extends CI_Driver { * * In the file driver, check to see that the cache directory is indeed writable * - * @return boolean + * @return bool */ public function is_supported() { return is_really_writable($this->_cache_path); } -} + // ------------------------------------------------------------------------ -/* End of file Cache_file.php */ -/* Location: ./system/libraries/Cache/drivers/Cache_file.php */ \ No newline at end of file + /** + * Get all data + * + * Internal method to get all the relevant data about a cache item + * + * @param string $id Cache ID + * @return mixed Data array on success or FALSE on failure + */ + protected function _get($id) + { + if ( ! is_file($this->_cache_path.$id)) + { + return FALSE; + } + + $data = unserialize(file_get_contents($this->_cache_path.$id)); + + if ($data['ttl'] > 0 && time() > $data['time'] + $data['ttl']) + { + unlink($this->_cache_path.$id); + return FALSE; + } + + return $data; + } + +} diff --git a/src/system/libraries/Cache/drivers/Cache_memcached.php b/src/system/libraries/Cache/drivers/Cache_memcached.php index cf6242ce..6dee1e8e 100644 --- a/src/system/libraries/Cache/drivers/Cache_memcached.php +++ b/src/system/libraries/Cache/drivers/Cache_memcached.php @@ -1,20 +1,41 @@ - array( - 'default_host' => '127.0.0.1', - 'default_port' => 11211, - 'default_weight' => 1 - ) + /** + * Memcached configuration + * + * @var array + */ + protected $_config = array( + 'default' => array( + 'host' => '127.0.0.1', + 'port' => 11211, + 'weight' => 1 + ) + ); + + // ------------------------------------------------------------------------ + + /** + * Class constructor + * + * Setup Memcache(d) + * + * @return void + */ + public function __construct() + { + // Try to load memcached server info from the config file. + $CI =& get_instance(); + $defaults = $this->_config['default']; + + if ($CI->config->load('memcached', TRUE, TRUE)) + { + $this->_config = $CI->config->config['memcached']; + } + + if (class_exists('Memcached', FALSE)) + { + $this->_memcached = new Memcached(); + } + elseif (class_exists('Memcache', FALSE)) + { + $this->_memcached = new Memcache(); + } + else + { + log_message('error', 'Cache: Failed to create Memcache(d) object; extension not loaded?'); + return; + } + + foreach ($this->_config as $cache_server) + { + isset($cache_server['hostname']) OR $cache_server['hostname'] = $defaults['host']; + isset($cache_server['port']) OR $cache_server['port'] = $defaults['port']; + isset($cache_server['weight']) OR $cache_server['weight'] = $defaults['weight']; + + if ($this->_memcached instanceof Memcache) + { + // Third parameter is persistance and defaults to TRUE. + $this->_memcached->addServer( + $cache_server['hostname'], + $cache_server['port'], + TRUE, + $cache_server['weight'] ); + } + elseif ($this->_memcached instanceof Memcached) + { + $this->_memcached->addServer( + $cache_server['hostname'], + $cache_server['port'], + $cache_server['weight'] + ); + } + } + } // ------------------------------------------------------------------------ /** * Fetch from cache * - * @param mixed unique key id - * @return mixed data on success/false on failure + * @param string $id Cache ID + * @return mixed Data on success, FALSE on failure */ public function get($id) { $data = $this->_memcached->get($id); - return (is_array($data)) ? $data[0] : FALSE; + return is_array($data) ? $data[0] : $data; } // ------------------------------------------------------------------------ @@ -57,20 +149,26 @@ class CI_Cache_memcached extends CI_Driver { /** * Save * - * @param string unique identifier - * @param mixed data being cached - * @param int time to live - * @return boolean true on success, false on failure + * @param string $id Cache ID + * @param mixed $data Data being cached + * @param int $ttl Time to live + * @param bool $raw Whether to store the raw value + * @return bool TRUE on success, FALSE on failure */ - public function save($id, $data, $ttl = 60) + public function save($id, $data, $ttl = 60, $raw = FALSE) { - if (get_class($this->_memcached) == 'Memcached') + if ($raw !== TRUE) { - return $this->_memcached->set($id, array($data, time(), $ttl), $ttl); + $data = array($data, time(), $ttl); } - else if (get_class($this->_memcached) == 'Memcache') + + if ($this->_memcached instanceof Memcached) { - return $this->_memcached->set($id, array($data, time(), $ttl), 0, $ttl); + return $this->_memcached->set($id, $data, $ttl); + } + elseif ($this->_memcached instanceof Memcache) + { + return $this->_memcached->set($id, $data, 0, $ttl); } return FALSE; @@ -81,8 +179,8 @@ class CI_Cache_memcached extends CI_Driver { /** * Delete from Cache * - * @param mixed key to be deleted. - * @return boolean true on success, false on failure + * @param mixed $id key to be deleted. + * @return bool true on success, false on failure */ public function delete($id) { @@ -91,10 +189,38 @@ class CI_Cache_memcached extends CI_Driver { // ------------------------------------------------------------------------ + /** + * Increment a raw value + * + * @param string $id Cache ID + * @param int $offset Step/value to add + * @return mixed New value on success or FALSE on failure + */ + public function increment($id, $offset = 1) + { + return $this->_memcached->increment($id, $offset); + } + + // ------------------------------------------------------------------------ + + /** + * Decrement a raw value + * + * @param string $id Cache ID + * @param int $offset Step/value to reduce by + * @return mixed New value on success or FALSE on failure + */ + public function decrement($id, $offset = 1) + { + return $this->_memcached->decrement($id, $offset); + } + + // ------------------------------------------------------------------------ + /** * Clean the Cache * - * @return boolean false on failure/true on success + * @return bool false on failure/true on success */ public function clean() { @@ -106,10 +232,9 @@ class CI_Cache_memcached extends CI_Driver { /** * Cache Info * - * @param null type not supported in memcached - * @return mixed array on success, false on failure + * @return mixed array on success, false on failure */ - public function cache_info($type = NULL) + public function cache_info() { return $this->_memcached->getStats(); } @@ -119,8 +244,8 @@ class CI_Cache_memcached extends CI_Driver { /** * Get Cache Metadata * - * @param mixed key to get cache metadata on - * @return mixed FALSE on failure, array on success. + * @param mixed $id key to get cache metadata on + * @return mixed FALSE on failure, array on success. */ public function get_metadata($id) { @@ -142,73 +267,37 @@ class CI_Cache_memcached extends CI_Driver { // ------------------------------------------------------------------------ - /** - * Setup memcached. - */ - private function _setup_memcached() - { - // Try to load memcached server info from the config file. - $CI =& get_instance(); - if ($CI->config->load('memcached', TRUE, TRUE)) - { - if (is_array($CI->config->config['memcached'])) - { - $this->_memcache_conf = NULL; - - foreach ($CI->config->config['memcached'] as $name => $conf) - { - $this->_memcache_conf[$name] = $conf; - } - } - } - - $this->_memcached = new Memcached(); - - foreach ($this->_memcache_conf as $name => $cache_server) - { - if ( ! array_key_exists('hostname', $cache_server)) - { - $cache_server['hostname'] = $this->_default_options['default_host']; - } - - if ( ! array_key_exists('port', $cache_server)) - { - $cache_server['port'] = $this->_default_options['default_port']; - } - - if ( ! array_key_exists('weight', $cache_server)) - { - $cache_server['weight'] = $this->_default_options['default_weight']; - } - - $this->_memcached->addServer( - $cache_server['hostname'], $cache_server['port'], $cache_server['weight'] - ); - } - } - - // ------------------------------------------------------------------------ - - /** * Is supported * * Returns FALSE if memcached is not supported on the system. * If it is, we setup the memcached object & return TRUE + * + * @return bool */ public function is_supported() { - if ( ! extension_loaded('memcached')) - { - log_message('error', 'The Memcached Extension must be loaded to use Memcached Cache.'); - return FALSE; - } - - $this->_setup_memcached(); - return TRUE; + return (extension_loaded('memcached') OR extension_loaded('memcache')); } -} + // ------------------------------------------------------------------------ -/* End of file Cache_memcached.php */ -/* Location: ./system/libraries/Cache/drivers/Cache_memcached.php */ \ No newline at end of file + /** + * Class destructor + * + * Closes the connection to Memcache(d) if present. + * + * @return void + */ + public function __destruct() + { + if ($this->_memcached instanceof Memcache) + { + $this->_memcached->close(); + } + elseif ($this->_memcached instanceof Memcached) + { + $this->_memcached->quit(); + } + } +} diff --git a/src/system/libraries/Cache/drivers/Cache_redis.php b/src/system/libraries/Cache/drivers/Cache_redis.php new file mode 100644 index 00000000..d4d95ebb --- /dev/null +++ b/src/system/libraries/Cache/drivers/Cache_redis.php @@ -0,0 +1,328 @@ + + * @link + */ +class CI_Cache_redis extends CI_Driver +{ + /** + * Default config + * + * @static + * @var array + */ + protected static $_default_config = array( + 'socket_type' => 'tcp', + 'host' => '127.0.0.1', + 'password' => NULL, + 'port' => 6379, + 'timeout' => 0 + ); + + /** + * Redis connection + * + * @var Redis + */ + protected $_redis; + + /** + * An internal cache for storing keys of serialized values. + * + * @var array + */ + protected $_serialized = array(); + + // ------------------------------------------------------------------------ + + /** + * Class constructor + * + * Setup Redis + * + * Loads Redis config file if present. Will halt execution + * if a Redis connection can't be established. + * + * @return void + * @see Redis::connect() + */ + public function __construct() + { + if ( ! $this->is_supported()) + { + log_message('error', 'Cache: Failed to create Redis object; extension not loaded?'); + return; + } + + $CI =& get_instance(); + + if ($CI->config->load('redis', TRUE, TRUE)) + { + $config = array_merge(self::$_default_config, $CI->config->item('redis')); + } + else + { + $config = self::$_default_config; + } + + $this->_redis = new Redis(); + + try + { + if ($config['socket_type'] === 'unix') + { + $success = $this->_redis->connect($config['socket']); + } + else // tcp socket + { + $success = $this->_redis->connect($config['host'], $config['port'], $config['timeout']); + } + + if ( ! $success) + { + log_message('error', 'Cache: Redis connection failed. Check your configuration.'); + } + + if (isset($config['password']) && ! $this->_redis->auth($config['password'])) + { + log_message('error', 'Cache: Redis authentication failed.'); + } + } + catch (RedisException $e) + { + log_message('error', 'Cache: Redis connection refused ('.$e->getMessage().')'); + } + + // Initialize the index of serialized values. + $serialized = $this->_redis->sMembers('_ci_redis_serialized'); + empty($serialized) OR $this->_serialized = array_flip($serialized); + } + + // ------------------------------------------------------------------------ + + /** + * Get cache + * + * @param string $key Cache ID + * @return mixed + */ + public function get($key) + { + $value = $this->_redis->get($key); + + if ($value !== FALSE && isset($this->_serialized[$key])) + { + return unserialize($value); + } + + return $value; + } + + // ------------------------------------------------------------------------ + + /** + * Save cache + * + * @param string $id Cache ID + * @param mixed $data Data to save + * @param int $ttl Time to live in seconds + * @param bool $raw Whether to store the raw value (unused) + * @return bool TRUE on success, FALSE on failure + */ + public function save($id, $data, $ttl = 60, $raw = FALSE) + { + if (is_array($data) OR is_object($data)) + { + if ( ! $this->_redis->sIsMember('_ci_redis_serialized', $id) && ! $this->_redis->sAdd('_ci_redis_serialized', $id)) + { + return FALSE; + } + + isset($this->_serialized[$id]) OR $this->_serialized[$id] = TRUE; + $data = serialize($data); + } + elseif (isset($this->_serialized[$id])) + { + $this->_serialized[$id] = NULL; + $this->_redis->sRemove('_ci_redis_serialized', $id); + } + + return $this->_redis->set($id, $data, $ttl); + } + + // ------------------------------------------------------------------------ + + /** + * Delete from cache + * + * @param string $key Cache key + * @return bool + */ + public function delete($key) + { + if ($this->_redis->delete($key) !== 1) + { + return FALSE; + } + + if (isset($this->_serialized[$key])) + { + $this->_serialized[$key] = NULL; + $this->_redis->sRemove('_ci_redis_serialized', $key); + } + + return TRUE; + } + + // ------------------------------------------------------------------------ + + /** + * Increment a raw value + * + * @param string $id Cache ID + * @param int $offset Step/value to add + * @return mixed New value on success or FALSE on failure + */ + public function increment($id, $offset = 1) + { + return $this->_redis->incr($id, $offset); + } + + // ------------------------------------------------------------------------ + + /** + * Decrement a raw value + * + * @param string $id Cache ID + * @param int $offset Step/value to reduce by + * @return mixed New value on success or FALSE on failure + */ + public function decrement($id, $offset = 1) + { + return $this->_redis->decr($id, $offset); + } + + // ------------------------------------------------------------------------ + + /** + * Clean cache + * + * @return bool + * @see Redis::flushDB() + */ + public function clean() + { + return $this->_redis->flushDB(); + } + + // ------------------------------------------------------------------------ + + /** + * Get cache driver info + * + * @param string $type Not supported in Redis. + * Only included in order to offer a + * consistent cache API. + * @return array + * @see Redis::info() + */ + public function cache_info($type = NULL) + { + return $this->_redis->info(); + } + + // ------------------------------------------------------------------------ + + /** + * Get cache metadata + * + * @param string $key Cache key + * @return array + */ + public function get_metadata($key) + { + $value = $this->get($key); + + if ($value !== FALSE) + { + return array( + 'expire' => time() + $this->_redis->ttl($key), + 'data' => $value + ); + } + + return FALSE; + } + + // ------------------------------------------------------------------------ + + /** + * Check if Redis driver is supported + * + * @return bool + */ + public function is_supported() + { + return extension_loaded('redis'); + } + + // ------------------------------------------------------------------------ + + /** + * Class destructor + * + * Closes the connection to Redis if present. + * + * @return void + */ + public function __destruct() + { + if ($this->_redis) + { + $this->_redis->close(); + } + } +} diff --git a/src/system/libraries/Cache/drivers/Cache_wincache.php b/src/system/libraries/Cache/drivers/Cache_wincache.php new file mode 100644 index 00000000..d6a0d4fb --- /dev/null +++ b/src/system/libraries/Cache/drivers/Cache_wincache.php @@ -0,0 +1,217 @@ +is_supported()) + { + log_message('error', 'Cache: Failed to initialize Wincache; extension not loaded/enabled?'); + } + } + + // ------------------------------------------------------------------------ + + /** + * Get + * + * Look for a value in the cache. If it exists, return the data, + * if not, return FALSE + * + * @param string $id Cache Ide + * @return mixed Value that is stored/FALSE on failure + */ + public function get($id) + { + $success = FALSE; + $data = wincache_ucache_get($id, $success); + + // Success returned by reference from wincache_ucache_get() + return ($success) ? $data : FALSE; + } + + // ------------------------------------------------------------------------ + + /** + * Cache Save + * + * @param string $id Cache ID + * @param mixed $data Data to store + * @param int $ttl Time to live (in seconds) + * @param bool $raw Whether to store the raw value (unused) + * @return bool true on success/false on failure + */ + public function save($id, $data, $ttl = 60, $raw = FALSE) + { + return wincache_ucache_set($id, $data, $ttl); + } + + // ------------------------------------------------------------------------ + + /** + * Delete from Cache + * + * @param mixed unique identifier of the item in the cache + * @return bool true on success/false on failure + */ + public function delete($id) + { + return wincache_ucache_delete($id); + } + + // ------------------------------------------------------------------------ + + /** + * Increment a raw value + * + * @param string $id Cache ID + * @param int $offset Step/value to add + * @return mixed New value on success or FALSE on failure + */ + public function increment($id, $offset = 1) + { + $success = FALSE; + $value = wincache_ucache_inc($id, $offset, $success); + + return ($success === TRUE) ? $value : FALSE; + } + + // ------------------------------------------------------------------------ + + /** + * Decrement a raw value + * + * @param string $id Cache ID + * @param int $offset Step/value to reduce by + * @return mixed New value on success or FALSE on failure + */ + public function decrement($id, $offset = 1) + { + $success = FALSE; + $value = wincache_ucache_dec($id, $offset, $success); + + return ($success === TRUE) ? $value : FALSE; + } + + // ------------------------------------------------------------------------ + + /** + * Clean the cache + * + * @return bool false on failure/true on success + */ + public function clean() + { + return wincache_ucache_clear(); + } + + // ------------------------------------------------------------------------ + + /** + * Cache Info + * + * @return mixed array on success, false on failure + */ + public function cache_info() + { + return wincache_ucache_info(TRUE); + } + + // ------------------------------------------------------------------------ + + /** + * Get Cache Metadata + * + * @param mixed key to get cache metadata on + * @return mixed array on success/false on failure + */ + public function get_metadata($id) + { + if ($stored = wincache_ucache_info(FALSE, $id)) + { + $age = $stored['ucache_entries'][1]['age_seconds']; + $ttl = $stored['ucache_entries'][1]['ttl_seconds']; + $hitcount = $stored['ucache_entries'][1]['hitcount']; + + return array( + 'expire' => $ttl - $age, + 'hitcount' => $hitcount, + 'age' => $age, + 'ttl' => $ttl + ); + } + + return FALSE; + } + + // ------------------------------------------------------------------------ + + /** + * is_supported() + * + * Check to see if WinCache is available on this system, bail if it isn't. + * + * @return bool + */ + public function is_supported() + { + return (extension_loaded('wincache') && ini_get('wincache.ucenabled')); + } +} diff --git a/src/system/libraries/Cache/drivers/index.html b/src/system/libraries/Cache/drivers/index.html index c942a79c..b702fbc3 100644 --- a/src/system/libraries/Cache/drivers/index.html +++ b/src/system/libraries/Cache/drivers/index.html @@ -1,3 +1,4 @@ + 403 Forbidden @@ -7,4 +8,4 @@

    Directory access is forbidden.

    - \ No newline at end of file + diff --git a/src/system/libraries/Cache/index.html b/src/system/libraries/Cache/index.html index c942a79c..b702fbc3 100644 --- a/src/system/libraries/Cache/index.html +++ b/src/system/libraries/Cache/index.html @@ -1,3 +1,4 @@ + 403 Forbidden @@ -7,4 +8,4 @@

    Directory access is forbidden.

    - \ No newline at end of file + diff --git a/src/system/libraries/Calendar.php b/src/system/libraries/Calendar.php index 6c7af070..1f8ef814 100644 --- a/src/system/libraries/Calendar.php +++ b/src/system/libraries/Calendar.php @@ -1,20 +1,41 @@ -CI =& get_instance(); + $this->CI->lang->load('calendar'); - if ( ! in_array('calendar_lang.php', $this->CI->lang->is_loaded, TRUE)) - { - $this->CI->lang->load('calendar'); - } + empty($config) OR $this->initialize($config); - $this->local_time = time(); - - if (count($config) > 0) - { - $this->initialize($config); - } - - log_message('debug', "Calendar Class Initialized"); + log_message('info', 'Calendar Class Initialized'); } // -------------------------------------------------------------------- @@ -70,11 +144,10 @@ class CI_Calendar { * * Accepts an associative array as input, containing display preferences * - * @access public * @param array config preferences - * @return void + * @return CI_Calendar */ - function initialize($config = array()) + public function initialize($config = array()) { foreach ($config as $key => $val) { @@ -83,6 +156,14 @@ class CI_Calendar { $this->$key = $val; } } + + // Set the next_prev_url to the controller if required but not defined + if ($this->show_next_prev === TRUE && empty($this->next_prev_url)) + { + $this->next_prev_url = $this->CI->config->site_url($this->CI->router->class.'/'.$this->CI->router->method); + } + + return $this; } // -------------------------------------------------------------------- @@ -90,29 +171,37 @@ class CI_Calendar { /** * Generate the calendar * - * @access public - * @param integer the year - * @param integer the month + * @param int the year + * @param int the month * @param array the data to be shown in the calendar cells * @return string */ - function generate($year = '', $month = '', $data = array()) + public function generate($year = '', $month = '', $data = array()) { + $local_time = time(); + // Set and validate the supplied month/year - if ($year == '') - $year = date("Y", $this->local_time); - - if ($month == '') - $month = date("m", $this->local_time); - - if (strlen($year) == 1) + if (empty($year)) + { + $year = date('Y', $local_time); + } + elseif (strlen($year) === 1) + { $year = '200'.$year; - - if (strlen($year) == 2) + } + elseif (strlen($year) === 2) + { $year = '20'.$year; + } - if (strlen($month) == 1) + if (empty($month)) + { + $month = date('m', $local_time); + } + elseif (strlen($month) === 1) + { $month = '0'.$month; + } $adjusted_date = $this->adjust_date($month, $year); @@ -124,12 +213,12 @@ class CI_Calendar { // Set the starting day of the week $start_days = array('sunday' => 0, 'monday' => 1, 'tuesday' => 2, 'wednesday' => 3, 'thursday' => 4, 'friday' => 5, 'saturday' => 6); - $start_day = ( ! isset($start_days[$this->start_day])) ? 0 : $start_days[$this->start_day]; + $start_day = isset($start_days[$this->start_day]) ? $start_days[$this->start_day] : 0; // Set the starting day number $local_date = mktime(12, 0, 0, $month, 1, $year); $date = getdate($local_date); - $day = $start_day + 1 - $date["wday"]; + $day = $start_day + 1 - $date['wday']; while ($day > 1) { @@ -138,115 +227,116 @@ class CI_Calendar { // Set the current month/year/day // We use this to determine the "today" date - $cur_year = date("Y", $this->local_time); - $cur_month = date("m", $this->local_time); - $cur_day = date("j", $this->local_time); + $cur_year = date('Y', $local_time); + $cur_month = date('m', $local_time); + $cur_day = date('j', $local_time); - $is_current_month = ($cur_year == $year AND $cur_month == $month) ? TRUE : FALSE; + $is_current_month = ($cur_year == $year && $cur_month == $month); // Generate the template data array $this->parse_template(); // Begin building the calendar output - $out = $this->temp['table_open']; - $out .= "\n"; - - $out .= "\n"; - $out .= $this->temp['heading_row_start']; - $out .= "\n"; + $out = $this->replacements['table_open']."\n\n".$this->replacements['heading_row_start']."\n"; // "previous" month link - if ($this->show_next_prev == TRUE) + if ($this->show_next_prev === TRUE) { - // Add a trailing slash to the URL if needed - $this->next_prev_url = preg_replace("/(.+?)\/*$/", "\\1/", $this->next_prev_url); + // Add a trailing slash to the URL if needed + $this->next_prev_url = preg_replace('/(.+?)\/*$/', '\\1/', $this->next_prev_url); $adjusted_date = $this->adjust_date($month - 1, $year); - $out .= str_replace('{previous_url}', $this->next_prev_url.$adjusted_date['year'].'/'.$adjusted_date['month'], $this->temp['heading_previous_cell']); - $out .= "\n"; + $out .= str_replace('{previous_url}', $this->next_prev_url.$adjusted_date['year'].'/'.$adjusted_date['month'], $this->replacements['heading_previous_cell'])."\n"; } // Heading containing the month/year - $colspan = ($this->show_next_prev == TRUE) ? 5 : 7; + $colspan = ($this->show_next_prev === TRUE) ? 5 : 7; - $this->temp['heading_title_cell'] = str_replace('{colspan}', $colspan, $this->temp['heading_title_cell']); - $this->temp['heading_title_cell'] = str_replace('{heading}', $this->get_month_name($month)." ".$year, $this->temp['heading_title_cell']); + $this->replacements['heading_title_cell'] = str_replace('{colspan}', $colspan, + str_replace('{heading}', $this->get_month_name($month).' '.$year, $this->replacements['heading_title_cell'])); - $out .= $this->temp['heading_title_cell']; - $out .= "\n"; + $out .= $this->replacements['heading_title_cell']."\n"; // "next" month link - if ($this->show_next_prev == TRUE) + if ($this->show_next_prev === TRUE) { $adjusted_date = $this->adjust_date($month + 1, $year); - $out .= str_replace('{next_url}', $this->next_prev_url.$adjusted_date['year'].'/'.$adjusted_date['month'], $this->temp['heading_next_cell']); + $out .= str_replace('{next_url}', $this->next_prev_url.$adjusted_date['year'].'/'.$adjusted_date['month'], $this->replacements['heading_next_cell']); } - $out .= "\n"; - $out .= $this->temp['heading_row_end']; - $out .= "\n"; - - // Write the cells containing the days of the week - $out .= "\n"; - $out .= $this->temp['week_row_start']; - $out .= "\n"; + $out .= "\n".$this->replacements['heading_row_end']."\n\n" + // Write the cells containing the days of the week + .$this->replacements['week_row_start']."\n"; $day_names = $this->get_day_names(); for ($i = 0; $i < 7; $i ++) { - $out .= str_replace('{week_day}', $day_names[($start_day + $i) %7], $this->temp['week_day_cell']); + $out .= str_replace('{week_day}', $day_names[($start_day + $i) %7], $this->replacements['week_day_cell']); } - $out .= "\n"; - $out .= $this->temp['week_row_end']; - $out .= "\n"; + $out .= "\n".$this->replacements['week_row_end']."\n"; // Build the main body of the calendar while ($day <= $total_days) { - $out .= "\n"; - $out .= $this->temp['cal_row_start']; - $out .= "\n"; + $out .= "\n".$this->replacements['cal_row_start']."\n"; for ($i = 0; $i < 7; $i++) { - $out .= ($is_current_month == TRUE AND $day == $cur_day) ? $this->temp['cal_cell_start_today'] : $this->temp['cal_cell_start']; - - if ($day > 0 AND $day <= $total_days) + if ($day > 0 && $day <= $total_days) { + $out .= ($is_current_month === TRUE && $day == $cur_day) ? $this->replacements['cal_cell_start_today'] : $this->replacements['cal_cell_start']; + if (isset($data[$day])) { // Cells with content - $temp = ($is_current_month == TRUE AND $day == $cur_day) ? $this->temp['cal_cell_content_today'] : $this->temp['cal_cell_content']; - $out .= str_replace('{day}', $day, str_replace('{content}', $data[$day], $temp)); + $temp = ($is_current_month === TRUE && $day == $cur_day) ? + $this->replacements['cal_cell_content_today'] : $this->replacements['cal_cell_content']; + $out .= str_replace(array('{content}', '{day}'), array($data[$day], $day), $temp); } else { // Cells with no content - $temp = ($is_current_month == TRUE AND $day == $cur_day) ? $this->temp['cal_cell_no_content_today'] : $this->temp['cal_cell_no_content']; + $temp = ($is_current_month === TRUE && $day == $cur_day) ? + $this->replacements['cal_cell_no_content_today'] : $this->replacements['cal_cell_no_content']; $out .= str_replace('{day}', $day, $temp); } + + $out .= ($is_current_month === TRUE && $day == $cur_day) ? $this->replacements['cal_cell_end_today'] : $this->replacements['cal_cell_end']; + } + elseif ($this->show_other_days === TRUE) + { + $out .= $this->replacements['cal_cell_start_other']; + + if ($day <= 0) + { + // Day of previous month + $prev_month = $this->adjust_date($month - 1, $year); + $prev_month_days = $this->get_total_days($prev_month['month'], $prev_month['year']); + $out .= str_replace('{day}', $prev_month_days + $day, $this->replacements['cal_cell_other']); + } + else + { + // Day of next month + $out .= str_replace('{day}', $day - $total_days, $this->replacements['cal_cell_other']); + } + + $out .= $this->replacements['cal_cell_end_other']; } else { // Blank cells - $out .= $this->temp['cal_cell_blank']; + $out .= $this->replacements['cal_cell_start'].$this->replacements['cal_cell_blank'].$this->replacements['cal_cell_end']; } - $out .= ($is_current_month == TRUE AND $day == $cur_day) ? $this->temp['cal_cell_end_today'] : $this->temp['cal_cell_end']; $day++; } - $out .= "\n"; - $out .= $this->temp['cal_row_end']; - $out .= "\n"; + $out .= "\n".$this->replacements['cal_row_end']."\n"; } - $out .= "\n"; - $out .= $this->temp['table_close']; - - return $out; + return $out .= "\n".$this->replacements['table_close']; } // -------------------------------------------------------------------- @@ -257,13 +347,12 @@ class CI_Calendar { * Generates a textual month name based on the numeric * month provided. * - * @access public - * @param integer the month + * @param int the month * @return string */ - function get_month_name($month) + public function get_month_name($month) { - if ($this->month_type == 'short') + if ($this->month_type === 'short') { $month_names = array('01' => 'cal_jan', '02' => 'cal_feb', '03' => 'cal_mar', '04' => 'cal_apr', '05' => 'cal_may', '06' => 'cal_jun', '07' => 'cal_jul', '08' => 'cal_aug', '09' => 'cal_sep', '10' => 'cal_oct', '11' => 'cal_nov', '12' => 'cal_dec'); } @@ -272,14 +361,9 @@ class CI_Calendar { $month_names = array('01' => 'cal_january', '02' => 'cal_february', '03' => 'cal_march', '04' => 'cal_april', '05' => 'cal_mayl', '06' => 'cal_june', '07' => 'cal_july', '08' => 'cal_august', '09' => 'cal_september', '10' => 'cal_october', '11' => 'cal_november', '12' => 'cal_december'); } - $month = $month_names[$month]; - - if ($this->CI->lang->line($month) === FALSE) - { - return ucfirst(str_replace('cal_', '', $month)); - } - - return $this->CI->lang->line($month); + return ($this->CI->lang->line($month_names[$month]) === FALSE) + ? ucfirst(substr($month_names[$month], 4)) + : $this->CI->lang->line($month_names[$month]); } // -------------------------------------------------------------------- @@ -288,22 +372,23 @@ class CI_Calendar { * Get Day Names * * Returns an array of day names (Sunday, Monday, etc.) based - * on the type. Options: long, short, abrev + * on the type. Options: long, short, abr * - * @access public * @param string * @return array */ - function get_day_names($day_type = '') + public function get_day_names($day_type = '') { - if ($day_type != '') + if ($day_type !== '') + { $this->day_type = $day_type; + } - if ($this->day_type == 'long') + if ($this->day_type === 'long') { $day_names = array('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'); } - elseif ($this->day_type == 'short') + elseif ($this->day_type === 'short') { $day_names = array('sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'); } @@ -313,9 +398,9 @@ class CI_Calendar { } $days = array(); - foreach ($day_names as $val) + for ($i = 0, $c = count($day_names); $i < $c; $i++) { - $days[] = ($this->CI->lang->line('cal_'.$val) === FALSE) ? ucfirst($val) : $this->CI->lang->line('cal_'.$val); + $days[] = ($this->CI->lang->line('cal_'.$day_names[$i]) === FALSE) ? ucfirst($day_names[$i]) : $this->CI->lang->line('cal_'.$day_names[$i]); } return $days; @@ -330,12 +415,11 @@ class CI_Calendar { * For example, if you submit 13 as the month, the year will * increment and the month will become January. * - * @access public - * @param integer the month - * @param integer the year + * @param int the month + * @param int the year * @return array */ - function adjust_date($month, $year) + public function adjust_date($month, $year) { $date = array(); @@ -354,7 +438,7 @@ class CI_Calendar { $date['year']--; } - if (strlen($date['month']) == 1) + if (strlen($date['month']) === 1) { $date['month'] = '0'.$date['month']; } @@ -367,30 +451,14 @@ class CI_Calendar { /** * Total days in a given month * - * @access public - * @param integer the month - * @param integer the year - * @return integer + * @param int the month + * @param int the year + * @return int */ - function get_total_days($month, $year) + public function get_total_days($month, $year) { - $days_in_month = array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); - - if ($month < 1 OR $month > 12) - { - return 0; - } - - // Is the year a leap year? - if ($month == 2) - { - if ($year % 400 == 0 OR ($year % 4 == 0 AND $year % 100 != 0)) - { - return 29; - } - } - - return $days_in_month[$month - 1]; + $this->CI->load->helper('date'); + return days_in_month($month, $year); } // -------------------------------------------------------------------- @@ -400,34 +468,36 @@ class CI_Calendar { * * This is used in the event that the user has not created their own template * - * @access public - * @return array + * @return array */ - function default_template() + public function default_template() { - return array ( - 'table_open' => '', - 'heading_row_start' => '', - 'heading_previous_cell' => '', - 'heading_title_cell' => '', - 'heading_next_cell' => '', - 'heading_row_end' => '', - 'week_row_start' => '', - 'week_day_cell' => '', - 'week_row_end' => '', - 'cal_row_start' => '', - 'cal_cell_start' => '', - 'cal_cell_end_today' => '', - 'cal_row_end' => '', - 'table_close' => '
    <<{heading}>>
    {week_day}
    ', - 'cal_cell_start_today' => '', - 'cal_cell_content' => '{day}', - 'cal_cell_content_today' => '{day}', - 'cal_cell_no_content' => '{day}', - 'cal_cell_no_content_today' => '{day}', - 'cal_cell_blank' => ' ', - 'cal_cell_end' => '
    ' - ); + return array( + 'table_open' => '', + 'heading_row_start' => '', + 'heading_previous_cell' => '', + 'heading_title_cell' => '', + 'heading_next_cell' => '', + 'heading_row_end' => '', + 'week_row_start' => '', + 'week_day_cell' => '', + 'week_row_end' => '', + 'cal_row_start' => '', + 'cal_cell_start' => '', + 'cal_cell_end_today' => '', + 'cal_cell_end_other' => '', + 'cal_row_end' => '', + 'table_close' => '
    <<{heading}>>
    {week_day}
    ', + 'cal_cell_start_today' => '', + 'cal_cell_start_other' => '', + 'cal_cell_content' => '{day}', + 'cal_cell_content_today' => '{day}', + 'cal_cell_no_content' => '{day}', + 'cal_cell_no_content_today' => '{day}', + 'cal_cell_blank' => ' ', + 'cal_cell_other' => '{day}', + 'cal_cell_end' => '
    ' + ); } // -------------------------------------------------------------------- @@ -438,39 +508,39 @@ class CI_Calendar { * Harvests the data within the template {pseudo-variables} * used to display the calendar * - * @access public - * @return void + * @return CI_Calendar */ - function parse_template() + public function parse_template() { - $this->temp = $this->default_template(); + $this->replacements = $this->default_template(); - if ($this->template == '') + if (empty($this->template)) { - return; + return $this; } - $today = array('cal_cell_start_today', 'cal_cell_content_today', 'cal_cell_no_content_today', 'cal_cell_end_today'); - - foreach (array('table_open', 'table_close', 'heading_row_start', 'heading_previous_cell', 'heading_title_cell', 'heading_next_cell', 'heading_row_end', 'week_row_start', 'week_day_cell', 'week_row_end', 'cal_row_start', 'cal_cell_start', 'cal_cell_content', 'cal_cell_no_content', 'cal_cell_blank', 'cal_cell_end', 'cal_row_end', 'cal_cell_start_today', 'cal_cell_content_today', 'cal_cell_no_content_today', 'cal_cell_end_today') as $val) + if (is_string($this->template)) { - if (preg_match("/\{".$val."\}(.*?)\{\/".$val."\}/si", $this->template, $match)) + $today = array('cal_cell_start_today', 'cal_cell_content_today', 'cal_cell_no_content_today', 'cal_cell_end_today'); + + foreach (array('table_open', 'table_close', 'heading_row_start', 'heading_previous_cell', 'heading_title_cell', 'heading_next_cell', 'heading_row_end', 'week_row_start', 'week_day_cell', 'week_row_end', 'cal_row_start', 'cal_cell_start', 'cal_cell_content', 'cal_cell_no_content', 'cal_cell_blank', 'cal_cell_end', 'cal_row_end', 'cal_cell_start_today', 'cal_cell_content_today', 'cal_cell_no_content_today', 'cal_cell_end_today', 'cal_cell_start_other', 'cal_cell_other', 'cal_cell_end_other') as $val) { - $this->temp[$val] = $match['1']; - } - else - { - if (in_array($val, $today, TRUE)) + if (preg_match('/\{'.$val.'\}(.*?)\{\/'.$val.'\}/si', $this->template, $match)) { - $this->temp[$val] = $this->temp[str_replace('_today', '', $val)]; + $this->replacements[$val] = $match[1]; + } + elseif (in_array($val, $today, TRUE)) + { + $this->replacements[$val] = $this->replacements[substr($val, 0, -6)]; } } } + elseif (is_array($this->template)) + { + $this->replacements = array_merge($this->replacements, $this->template); + } + + return $this; } } - -// END CI_Calendar class - -/* End of file Calendar.php */ -/* Location: ./system/libraries/Calendar.php */ \ No newline at end of file diff --git a/src/system/libraries/Cart.php b/src/system/libraries/Cart.php index 33c067fa..44d87e0b 100644 --- a/src/system/libraries/Cart.php +++ b/src/system/libraries/Cart.php @@ -1,20 +1,41 @@ -CI =& get_instance(); // Are any config settings being passed manually? If so, set them - $config = array(); - if (count($params) > 0) - { - foreach ($params as $key => $val) - { - $config[$key] = $val; - } - } + $config = is_array($params) ? $params : array(); // Load the Sessions class - $this->CI->load->library('session', $config); + $this->CI->load->driver('session', $config); - // Grab the shopping cart array from the session table, if it exists - if ($this->CI->session->userdata('cart_contents') !== FALSE) - { - $this->_cart_contents = $this->CI->session->userdata('cart_contents'); - } - else + // Grab the shopping cart array from the session table + $this->_cart_contents = $this->CI->session->userdata('cart_contents'); + if ($this->_cart_contents === NULL) { // No cart exists so we'll set some base values - $this->_cart_contents['cart_total'] = 0; - $this->_cart_contents['total_items'] = 0; + $this->_cart_contents = array('cart_total' => 0, 'total_items' => 0); } - log_message('debug', "Cart Class Initialized"); + log_message('info', 'Cart Class Initialized'); } // -------------------------------------------------------------------- @@ -79,14 +123,13 @@ class CI_Cart { /** * Insert items into the cart and save it to the session table * - * @access public * @param array * @return bool */ - function insert($items = array()) + public function insert($items = array()) { // Was any cart data passed? No? Bah... - if ( ! is_array($items) OR count($items) == 0) + if ( ! is_array($items) OR count($items) === 0) { log_message('error', 'The insert method must be passed an array containing data.'); return FALSE; @@ -109,7 +152,7 @@ class CI_Cart { { foreach ($items as $val) { - if (is_array($val) AND isset($val['id'])) + if (is_array($val) && isset($val['id'])) { if ($this->_insert($val)) { @@ -120,7 +163,7 @@ class CI_Cart { } // Save the cart data if the insert was successful - if ($save_cart == TRUE) + if ($save_cart === TRUE) { $this->_save_cart(); return isset($rowid) ? $rowid : TRUE; @@ -134,14 +177,13 @@ class CI_Cart { /** * Insert * - * @access private * @param array * @return bool */ - function _insert($items = array()) + protected function _insert($items = array()) { // Was any cart data passed? No? Bah... - if ( ! is_array($items) OR count($items) == 0) + if ( ! is_array($items) OR count($items) === 0) { log_message('error', 'The insert method must be passed an array containing data.'); return FALSE; @@ -150,7 +192,7 @@ class CI_Cart { // -------------------------------------------------------------------- // Does the $items array contain an id, quantity, price, and name? These are required - if ( ! isset($items['id']) OR ! isset($items['qty']) OR ! isset($items['price']) OR ! isset($items['name'])) + if ( ! isset($items['id'], $items['qty'], $items['price'], $items['name'])) { log_message('error', 'The cart array must contain a product ID, quantity, price, and name.'); return FALSE; @@ -158,13 +200,11 @@ class CI_Cart { // -------------------------------------------------------------------- - // Prep the quantity. It can only be a number. Duh... - $items['qty'] = trim(preg_replace('/([^0-9])/i', '', $items['qty'])); - // Trim any leading zeros - $items['qty'] = trim(preg_replace('/(^[0]+)/i', '', $items['qty'])); + // Prep the quantity. It can only be a number. Duh... also trim any leading zeros + $items['qty'] = (float) $items['qty']; // If the quantity is zero or blank there's nothing for us to do - if ( ! is_numeric($items['qty']) OR $items['qty'] == 0) + if ($items['qty'] == 0) { return FALSE; } @@ -174,7 +214,7 @@ class CI_Cart { // Validate the product ID. It can only be alpha-numeric, dashes, underscores or periods // Not totally sure we should impose this rule, but it seems prudent to standardize IDs. // Note: These can be user-specified by setting the $this->product_id_rules variable. - if ( ! preg_match("/^[".$this->product_id_rules."]+$/i", $items['id'])) + if ( ! preg_match('/^['.$this->product_id_rules.']+$/i', $items['id'])) { log_message('error', 'Invalid product ID. The product ID can only contain alpha-numeric characters, dashes, and underscores'); return FALSE; @@ -184,7 +224,7 @@ class CI_Cart { // Validate the product name. It can only be alpha-numeric, dashes, underscores, colons or periods. // Note: These can be user-specified by setting the $this->product_name_rules variable. - if ( ! preg_match("/^[".$this->product_name_rules."]+$/i", $items['name'])) + if ($this->product_name_safe && ! preg_match('/^['.$this->product_name_rules.']+$/i'.(UTF8_ENABLED ? 'u' : ''), $items['name'])) { log_message('error', 'An invalid name was submitted as the product name: '.$items['name'].' The name can only contain alpha-numeric characters, dashes, underscores, colons, and spaces'); return FALSE; @@ -192,19 +232,8 @@ class CI_Cart { // -------------------------------------------------------------------- - // Prep the price. Remove anything that isn't a number or decimal point. - $items['price'] = trim(preg_replace('/([^0-9\.])/i', '', $items['price'])); - // Trim any leading zeros - $items['price'] = trim(preg_replace('/(^[0]+)/i', '', $items['price'])); - - // Is the price a valid number? - if ( ! is_numeric($items['price'])) - { - log_message('error', 'An invalid price was submitted for product ID: '.$items['id']); - return FALSE; - } - - // -------------------------------------------------------------------- + // Prep the price. Remove leading zeros and anything that isn't a number or decimal point. + $items['price'] = (float) $items['price']; // We now need to create a unique identifier for the item being inserted into the cart. // Every time something is added to the cart it is stored in the master cart array. @@ -216,9 +245,9 @@ class CI_Cart { // Internally, we need to treat identical submissions, but with different options, as a unique product. // Our solution is to convert the options array to a string and MD5 it along with the product ID. // This becomes the unique "row ID" - if (isset($items['options']) AND count($items['options']) > 0) + if (isset($items['options']) && count($items['options']) > 0) { - $rowid = md5($items['id'].implode('', $items['options'])); + $rowid = md5($items['id'].serialize($items['options'])); } else { @@ -231,20 +260,14 @@ class CI_Cart { // -------------------------------------------------------------------- // Now that we have our unique "row ID", we'll add our cart items to the master array + // grab quantity if it's already there and add it on + $old_quantity = isset($this->_cart_contents[$rowid]['qty']) ? (int) $this->_cart_contents[$rowid]['qty'] : 0; - // let's unset this first, just to make sure our index contains only the data from this submission - unset($this->_cart_contents[$rowid]); + // Re-create the entry, just to make sure our index contains only the data from this submission + $items['rowid'] = $rowid; + $items['qty'] += $old_quantity; + $this->_cart_contents[$rowid] = $items; - // Create a new index with our new row ID - $this->_cart_contents[$rowid]['rowid'] = $rowid; - - // And add the new items to the cart array - foreach ($items as $key => $val) - { - $this->_cart_contents[$rowid][$key] = $val; - } - - // Woot! return $rowid; } @@ -258,27 +281,25 @@ class CI_Cart { * changes to the quantity before checkout. That array must contain the * product ID and quantity for each item. * - * @access public * @param array - * @param string * @return bool */ - function update($items = array()) + public function update($items = array()) { // Was any cart data passed? - if ( ! is_array($items) OR count($items) == 0) + if ( ! is_array($items) OR count($items) === 0) { return FALSE; } // You can either update a single product using a one-dimensional array, // or multiple products using a multi-dimensional one. The way we - // determine the array type is by looking for a required array key named "id". + // determine the array type is by looking for a required array key named "rowid". // If it's not found we assume it's a multi-dimensional array $save_cart = FALSE; - if (isset($items['rowid']) AND isset($items['qty'])) + if (isset($items['rowid'])) { - if ($this->_update($items) == TRUE) + if ($this->_update($items) === TRUE) { $save_cart = TRUE; } @@ -287,9 +308,9 @@ class CI_Cart { { foreach ($items as $val) { - if (is_array($val) AND isset($val['rowid']) AND isset($val['qty'])) + if (is_array($val) && isset($val['rowid'])) { - if ($this->_update($val) == TRUE) + if ($this->_update($val) === TRUE) { $save_cart = TRUE; } @@ -298,7 +319,7 @@ class CI_Cart { } // Save the cart data if the insert was successful - if ($save_cart == TRUE) + if ($save_cart === TRUE) { $this->_save_cart(); return TRUE; @@ -312,48 +333,47 @@ class CI_Cart { /** * Update the cart * - * This function permits the quantity of a given item to be changed. + * This function permits changing item properties. * Typically it is called from the "view cart" page if a user makes * changes to the quantity before checkout. That array must contain the - * product ID and quantity for each item. + * rowid and quantity for each item. * - * @access private * @param array * @return bool */ - function _update($items = array()) + protected function _update($items = array()) { // Without these array indexes there is nothing we can do - if ( ! isset($items['qty']) OR ! isset($items['rowid']) OR ! isset($this->_cart_contents[$items['rowid']])) + if ( ! isset($items['rowid'], $this->_cart_contents[$items['rowid']])) { return FALSE; } // Prep the quantity - $items['qty'] = preg_replace('/([^0-9])/i', '', $items['qty']); - - // Is the quantity a number? - if ( ! is_numeric($items['qty'])) + if (isset($items['qty'])) { - return FALSE; + $items['qty'] = (float) $items['qty']; + // Is the quantity zero? If so we will remove the item from the cart. + // If the quantity is greater than zero we are updating + if ($items['qty'] == 0) + { + unset($this->_cart_contents[$items['rowid']]); + return TRUE; + } } - // Is the new quantity different than what is already saved in the cart? - // If it's the same there's nothing to do - if ($this->_cart_contents[$items['rowid']]['qty'] == $items['qty']) + // find updatable keys + $keys = array_intersect(array_keys($this->_cart_contents[$items['rowid']]), array_keys($items)); + // if a price was passed, make sure it contains valid data + if (isset($items['price'])) { - return FALSE; + $items['price'] = (float) $items['price']; } - // Is the quantity zero? If so we will remove the item from the cart. - // If the quantity is greater than zero we are updating - if ($items['qty'] == 0) + // product id & name shouldn't be changed + foreach (array_diff($keys, array('id', 'name')) as $key) { - unset($this->_cart_contents[$items['rowid']]); - } - else - { - $this->_cart_contents[$items['rowid']]['qty'] = $items['qty']; + $this->_cart_contents[$items['rowid']][$key] = $items[$key]; } return TRUE; @@ -364,38 +384,26 @@ class CI_Cart { /** * Save the cart array to the session DB * - * @access private * @return bool */ - function _save_cart() + protected function _save_cart() { - // Unset these so our total can be calculated correctly below - unset($this->_cart_contents['total_items']); - unset($this->_cart_contents['cart_total']); - - // Lets add up the individual prices and set the cart sub-total - $total = 0; - $items = 0; + // Let's add up the individual prices and set the cart sub-total + $this->_cart_contents['total_items'] = $this->_cart_contents['cart_total'] = 0; foreach ($this->_cart_contents as $key => $val) { // We make sure the array contains the proper indexes - if ( ! is_array($val) OR ! isset($val['price']) OR ! isset($val['qty'])) + if ( ! is_array($val) OR ! isset($val['price'], $val['qty'])) { continue; } - $total += ($val['price'] * $val['qty']); - $items += $val['qty']; - - // Set the subtotal + $this->_cart_contents['cart_total'] += ($val['price'] * $val['qty']); + $this->_cart_contents['total_items'] += $val['qty']; $this->_cart_contents[$key]['subtotal'] = ($this->_cart_contents[$key]['price'] * $this->_cart_contents[$key]['qty']); } - // Set the cart total and total items. - $this->_cart_contents['total_items'] = $items; - $this->_cart_contents['cart_total'] = $total; - - // Is our cart empty? If so we delete it from the session + // Is our cart empty? If so we delete it from the session if (count($this->_cart_contents) <= 2) { $this->CI->session->unset_userdata('cart_contents'); @@ -417,25 +425,41 @@ class CI_Cart { /** * Cart Total * - * @access public - * @return integer + * @return int */ - function total() + public function total() { return $this->_cart_contents['cart_total']; } // -------------------------------------------------------------------- + /** + * Remove Item + * + * Removes an item from the cart + * + * @param int + * @return bool + */ + public function remove($rowid) + { + // unset & save + unset($this->_cart_contents[$rowid]); + $this->_save_cart(); + return TRUE; + } + + // -------------------------------------------------------------------- + /** * Total Items * * Returns the total item count * - * @access public - * @return integer + * @return int */ - function total_items() + public function total_items() { return $this->_cart_contents['total_items']; } @@ -447,12 +471,13 @@ class CI_Cart { * * Returns the entire cart array * - * @access public + * @param bool * @return array */ - function contents() + public function contents($newest_first = FALSE) { - $cart = $this->_cart_contents; + // do we want the newest first? + $cart = ($newest_first) ? array_reverse($this->_cart_contents) : $this->_cart_contents; // Remove these so they don't create a problem when showing the cart table unset($cart['total_items']); @@ -463,23 +488,35 @@ class CI_Cart { // -------------------------------------------------------------------- + /** + * Get cart item + * + * Returns the details of a specific item in the cart + * + * @param string $row_id + * @return array + */ + public function get_item($row_id) + { + return (in_array($row_id, array('total_items', 'cart_total'), TRUE) OR ! isset($this->_cart_contents[$row_id])) + ? FALSE + : $this->_cart_contents[$row_id]; + } + + // -------------------------------------------------------------------- + /** * Has options * * Returns TRUE if the rowid passed to this function correlates to an item * that has options associated with it. * - * @access public - * @return array + * @param string $row_id = '' + * @return bool */ - function has_options($rowid = '') + public function has_options($row_id = '') { - if ( ! isset($this->_cart_contents[$rowid]['options']) OR count($this->_cart_contents[$rowid]['options']) === 0) - { - return FALSE; - } - - return TRUE; + return (isset($this->_cart_contents[$row_id]['options']) && count($this->_cart_contents[$row_id]['options']) !== 0); } // -------------------------------------------------------------------- @@ -489,17 +526,12 @@ class CI_Cart { * * Returns the an array of options, for a particular product row ID * - * @access public + * @param string $row_id = '' * @return array */ - function product_options($rowid = '') + public function product_options($row_id = '') { - if ( ! isset($this->_cart_contents[$rowid]['options'])) - { - return array(); - } - - return $this->_cart_contents[$rowid]['options']; + return isset($this->_cart_contents[$row_id]['options']) ? $this->_cart_contents[$row_id]['options'] : array(); } // -------------------------------------------------------------------- @@ -509,20 +541,12 @@ class CI_Cart { * * Returns the supplied number with commas and a decimal point. * - * @access public - * @return integer + * @param float + * @return string */ - function format_number($n = '') + public function format_number($n = '') { - if ($n == '') - { - return ''; - } - - // Remove anything that isn't a number or decimal point. - $n = trim(preg_replace('/([^0-9\.])/i', '', $n)); - - return number_format($n, 2, '.', ','); + return ($n === '') ? '' : number_format( (float) $n, 2, '.', ','); } // -------------------------------------------------------------------- @@ -532,21 +556,12 @@ class CI_Cart { * * Empties the cart and kills the session * - * @access public - * @return null + * @return void */ - function destroy() + public function destroy() { - unset($this->_cart_contents); - - $this->_cart_contents['cart_total'] = 0; - $this->_cart_contents['total_items'] = 0; - + $this->_cart_contents = array('cart_total' => 0, 'total_items' => 0); $this->CI->session->unset_userdata('cart_contents'); } - } - -/* End of file Cart.php */ -/* Location: ./system/libraries/Cart.php */ \ No newline at end of file diff --git a/src/system/libraries/Driver.php b/src/system/libraries/Driver.php index 086fcb37..38c6aefe 100644 --- a/src/system/libraries/Driver.php +++ b/src/system/libraries/Driver.php @@ -1,20 +1,41 @@ -lib_name)) - { - $this->lib_name = get_class($this); - } - - // The class will be prefixed with the parent lib - $child_class = $this->lib_name.'_'.$child; - - // Remove the CI_ prefix and lowercase - $lib_name = ucfirst(strtolower(str_replace('CI_', '', $this->lib_name))); - $driver_name = strtolower(str_replace('CI_', '', $child_class)); - - if (in_array($driver_name, array_map('strtolower', $this->valid_drivers))) - { - // check and see if the driver is in a separate file - if ( ! class_exists($child_class)) - { - // check application path first - foreach (get_instance()->load->get_package_paths(TRUE) as $path) - { - // loves me some nesting! - foreach (array(ucfirst($driver_name), $driver_name) as $class) - { - $filepath = $path.'libraries/'.$lib_name.'/drivers/'.$class.'.php'; - - if (file_exists($filepath)) - { - include_once $filepath; - break; - } - } - } - - // it's a valid driver, but the file simply can't be found - if ( ! class_exists($child_class)) - { - log_message('error', "Unable to load the requested driver: ".$child_class); - show_error("Unable to load the requested driver: ".$child_class); - } - } - - $obj = new $child_class; - $obj->decorate($this); - $this->$child = $obj; - return $this->$child; - } - - // The requested driver isn't valid! - log_message('error', "Invalid driver requested: ".$child_class); - show_error("Invalid driver requested: ".$child_class); + // Try to load the driver + return $this->load_driver($child); } - // -------------------------------------------------------------------- + /** + * Load driver + * + * Separate load_driver call to support explicit driver load by library or user + * + * @param string Driver name (w/o parent prefix) + * @return object Child class + */ + public function load_driver($child) + { + // Get CodeIgniter instance and subclass prefix + $prefix = config_item('subclass_prefix'); + + if ( ! isset($this->lib_name)) + { + // Get library name without any prefix + $this->lib_name = str_replace(array('CI_', $prefix), '', get_class($this)); + } + + // The child will be prefixed with the parent lib + $child_name = $this->lib_name.'_'.$child; + + // See if requested child is a valid driver + if ( ! in_array($child, $this->valid_drivers)) + { + // The requested driver isn't valid! + $msg = 'Invalid driver requested: '.$child_name; + log_message('error', $msg); + show_error($msg); + } + + // Get package paths and filename case variations to search + $CI = get_instance(); + $paths = $CI->load->get_package_paths(TRUE); + + // Is there an extension? + $class_name = $prefix.$child_name; + $found = class_exists($class_name, FALSE); + if ( ! $found) + { + // Check for subclass file + foreach ($paths as $path) + { + // Does the file exist? + $file = $path.'libraries/'.$this->lib_name.'/drivers/'.$prefix.$child_name.'.php'; + if (file_exists($file)) + { + // Yes - require base class from BASEPATH + $basepath = BASEPATH.'libraries/'.$this->lib_name.'/drivers/'.$child_name.'.php'; + if ( ! file_exists($basepath)) + { + $msg = 'Unable to load the requested class: CI_'.$child_name; + log_message('error', $msg); + show_error($msg); + } + + // Include both sources and mark found + include_once($basepath); + include_once($file); + $found = TRUE; + break; + } + } + } + + // Do we need to search for the class? + if ( ! $found) + { + // Use standard class name + $class_name = 'CI_'.$child_name; + if ( ! class_exists($class_name, FALSE)) + { + // Check package paths + foreach ($paths as $path) + { + // Does the file exist? + $file = $path.'libraries/'.$this->lib_name.'/drivers/'.$child_name.'.php'; + if (file_exists($file)) + { + // Include source + include_once($file); + break; + } + } + } + } + + // Did we finally find the class? + if ( ! class_exists($class_name, FALSE)) + { + if (class_exists($child_name, FALSE)) + { + $class_name = $child_name; + } + else + { + $msg = 'Unable to load the requested driver: '.$class_name; + log_message('error', $msg); + show_error($msg); + } + } + + // Instantiate, decorate and add child + $obj = new $class_name(); + $obj->decorate($this); + $this->$child = $obj; + return $this->$child; + } } -// END CI_Driver_Library CLASS +// -------------------------------------------------------------------------- /** * CodeIgniter Driver Class @@ -108,12 +206,35 @@ class CI_Driver_Library { * @link */ class CI_Driver { - protected $parent; - private $methods = array(); - private $properties = array(); + /** + * Instance of the parent class + * + * @var object + */ + protected $_parent; - private static $reflections = array(); + /** + * List of methods in the parent class + * + * @var array + */ + protected $_methods = array(); + + /** + * List of properties in the parent class + * + * @var array + */ + protected $_properties = array(); + + /** + * Array of methods and properties for the parent class(es) + * + * @static + * @var array + */ + protected static $_reflections = array(); /** * Decorate @@ -125,14 +246,14 @@ class CI_Driver { */ public function decorate($parent) { - $this->parent = $parent; + $this->_parent = $parent; // Lock down attributes to what is defined in the class // and speed up references in magic methods $class_name = get_class($parent); - if ( ! isset(self::$reflections[$class_name])) + if ( ! isset(self::$_reflections[$class_name])) { $r = new ReflectionObject($parent); @@ -140,7 +261,7 @@ class CI_Driver { { if ($method->isPublic()) { - $this->methods[] = $method->getName(); + $this->_methods[] = $method->getName(); } } @@ -148,15 +269,15 @@ class CI_Driver { { if ($prop->isPublic()) { - $this->properties[] = $prop->getName(); + $this->_properties[] = $prop->getName(); } } - self::$reflections[$class_name] = array($this->methods, $this->properties); + self::$_reflections[$class_name] = array($this->_methods, $this->_properties); } else { - list($this->methods, $this->properties) = self::$reflections[$class_name]; + list($this->_methods, $this->_properties) = self::$_reflections[$class_name]; } } @@ -167,21 +288,18 @@ class CI_Driver { * * Handles access to the parent driver library's methods * - * @access public * @param string * @param array * @return mixed */ public function __call($method, $args = array()) { - if (in_array($method, $this->methods)) + if (in_array($method, $this->_methods)) { - return call_user_func_array(array($this->parent, $method), $args); + return call_user_func_array(array($this->_parent, $method), $args); } - $trace = debug_backtrace(); - _exception_handler(E_ERROR, "No such method '{$method}'", $trace[1]['file'], $trace[1]['line']); - exit; + throw new BadMethodCallException('No such method: '.$method.'()'); } // -------------------------------------------------------------------- @@ -196,9 +314,9 @@ class CI_Driver { */ public function __get($var) { - if (in_array($var, $this->properties)) + if (in_array($var, $this->_properties)) { - return $this->parent->$var; + return $this->_parent->$var; } } @@ -215,14 +333,10 @@ class CI_Driver { */ public function __set($var, $val) { - if (in_array($var, $this->properties)) + if (in_array($var, $this->_properties)) { - $this->parent->$var = $val; + $this->_parent->$var = $val; } } } -// END CI_Driver CLASS - -/* End of file Driver.php */ -/* Location: ./system/libraries/Driver.php */ \ No newline at end of file diff --git a/src/system/libraries/Email.php b/src/system/libraries/Email.php index ca9cd1e8..ed6f737a 100644 --- a/src/system/libraries/Email.php +++ b/src/system/libraries/Email.php @@ -1,20 +1,41 @@ - '1 (Highest)', + 2 => '2 (High)', + 3 => '3 (Normal)', + 4 => '4 (Low)', + 5 => '5 (Lowest)' + ); + + // -------------------------------------------------------------------- /** * Constructor - Sets Email Preferences * * The constructor can be passed an array of config values + * + * @param array $config = array() + * @return void */ - public function __construct($config = array()) + public function __construct(array $config = array()) { + $this->charset = config_item('charset'); + if (count($config) > 0) { $this->initialize($config); } else { - $this->_smtp_auth = ($this->smtp_user == '' AND $this->smtp_pass == '') ? FALSE : TRUE; - $this->_safe_mode = ((boolean)@ini_get("safe_mode") === FALSE) ? FALSE : TRUE; + $this->_smtp_auth = ! ($this->smtp_user === '' && $this->smtp_pass === ''); } - log_message('debug', "Email Class Initialized"); + $this->_safe_mode = ( ! is_php('5.4') && ini_get('safe_mode')); + $this->charset = strtoupper($this->charset); + + log_message('info', 'Email Class Initialized'); + } + + // -------------------------------------------------------------------- + + /** + * Destructor - Releases Resources + * + * @return void + */ + public function __destruct() + { + if (is_resource($this->_smtp_connect)) + { + $this->_send_command('quit'); + } } // -------------------------------------------------------------------- @@ -104,9 +444,8 @@ class CI_Email { /** * Initialize preferences * - * @access public * @param array - * @return void + * @return CI_Email */ public function initialize($config = array()) { @@ -128,8 +467,7 @@ class CI_Email { } $this->clear(); - $this->_smtp_auth = ($this->smtp_user == '' AND $this->smtp_pass == '') ? FALSE : TRUE; - $this->_safe_mode = ((boolean)@ini_get("safe_mode") === FALSE) ? FALSE : TRUE; + $this->_smtp_auth = ! ($this->smtp_user === '' && $this->smtp_pass === ''); return $this; } @@ -139,30 +477,28 @@ class CI_Email { /** * Initialize the Email Data * - * @access public - * @return void + * @param bool + * @return CI_Email */ public function clear($clear_attachments = FALSE) { - $this->_subject = ""; - $this->_body = ""; - $this->_finalbody = ""; - $this->_header_str = ""; - $this->_replyto_flag = FALSE; + $this->_subject = ''; + $this->_body = ''; + $this->_finalbody = ''; + $this->_header_str = ''; + $this->_replyto_flag = FALSE; $this->_recipients = array(); $this->_cc_array = array(); $this->_bcc_array = array(); $this->_headers = array(); $this->_debug_msg = array(); - $this->_set_header('User-Agent', $this->useragent); - $this->_set_header('Date', $this->_set_date()); + $this->set_header('User-Agent', $this->useragent); + $this->set_header('Date', $this->_set_date()); if ($clear_attachments !== FALSE) { - $this->_attach_name = array(); - $this->_attach_type = array(); - $this->_attach_disp = array(); + $this->_attachments = array(); } return $this; @@ -173,25 +509,29 @@ class CI_Email { /** * Set FROM * - * @access public - * @param string - * @param string - * @return void + * @param string $from + * @param string $name + * @param string $return_path = NULL Return-Path + * @return CI_Email */ - public function from($from, $name = '') + public function from($from, $name = '', $return_path = NULL) { - if (preg_match( '/\<(.*)\>/', $from, $match)) + if (preg_match('/\<(.*)\>/', $from, $match)) { - $from = $match['1']; + $from = $match[1]; } if ($this->validate) { $this->validate_email($this->_str_to_array($from)); + if ($return_path) + { + $this->validate_email($this->_str_to_array($return_path)); + } } // prepare the display name - if ($name != '') + if ($name !== '') { // only use Q encoding if there are characters that would require it if ( ! preg_match('/[\200-\377]/', $name)) @@ -201,12 +541,14 @@ class CI_Email { } else { - $name = $this->_prep_q_encoding($name, TRUE); + $name = $this->_prep_q_encoding($name); } } - $this->_set_header('From', $name.' <'.$from.'>'); - $this->_set_header('Return-Path', '<'.$from.'>'); + $this->set_header('From', $name.' <'.$from.'>'); + + isset($return_path) OR $return_path = $from; + $this->set_header('Return-Path', '<'.$return_path.'>'); return $this; } @@ -216,16 +558,15 @@ class CI_Email { /** * Set Reply-to * - * @access public * @param string * @param string - * @return void + * @return CI_Email */ public function reply_to($replyto, $name = '') { - if (preg_match( '/\<(.*)\>/', $replyto, $match)) + if (preg_match('/\<(.*)\>/', $replyto, $match)) { - $replyto = $match['1']; + $replyto = $match[1]; } if ($this->validate) @@ -233,17 +574,21 @@ class CI_Email { $this->validate_email($this->_str_to_array($replyto)); } - if ($name == '') + if ($name !== '') { - $name = $replyto; + // only use Q encoding if there are characters that would require it + if ( ! preg_match('/[\200-\377]/', $name)) + { + // add slashes for non-printing characters, slashes, and double quotes, and surround it in double quotes + $name = '"'.addcslashes($name, "\0..\37\177'\"\\").'"'; + } + else + { + $name = $this->_prep_q_encoding($name); + } } - if (strncmp($name, '"', 1) != 0) - { - $name = '"'.$name.'"'; - } - - $this->_set_header('Reply-To', $name.' <'.$replyto.'>'); + $this->set_header('Reply-To', $name.' <'.$replyto.'>'); $this->_replyto_flag = TRUE; return $this; @@ -254,9 +599,8 @@ class CI_Email { /** * Set Recipients * - * @access public * @param string - * @return void + * @return CI_Email */ public function to($to) { @@ -268,21 +612,12 @@ class CI_Email { $this->validate_email($to); } - if ($this->_get_protocol() != 'mail') + if ($this->_get_protocol() !== 'mail') { - $this->_set_header('To', implode(", ", $to)); + $this->set_header('To', implode(', ', $to)); } - switch ($this->_get_protocol()) - { - case 'smtp' : - $this->_recipients = $to; - break; - case 'sendmail' : - case 'mail' : - $this->_recipients = implode(", ", $to); - break; - } + $this->_recipients = $to; return $this; } @@ -292,23 +627,21 @@ class CI_Email { /** * Set CC * - * @access public * @param string - * @return void + * @return CI_Email */ public function cc($cc) { - $cc = $this->_str_to_array($cc); - $cc = $this->clean_email($cc); + $cc = $this->clean_email($this->_str_to_array($cc)); if ($this->validate) { $this->validate_email($cc); } - $this->_set_header('Cc', implode(", ", $cc)); + $this->set_header('Cc', implode(', ', $cc)); - if ($this->_get_protocol() == "smtp") + if ($this->_get_protocol() === 'smtp') { $this->_cc_array = $cc; } @@ -321,34 +654,32 @@ class CI_Email { /** * Set BCC * - * @access public * @param string * @param string - * @return void + * @return CI_Email */ public function bcc($bcc, $limit = '') { - if ($limit != '' && is_numeric($limit)) + if ($limit !== '' && is_numeric($limit)) { $this->bcc_batch_mode = TRUE; $this->bcc_batch_size = $limit; } - $bcc = $this->_str_to_array($bcc); - $bcc = $this->clean_email($bcc); + $bcc = $this->clean_email($this->_str_to_array($bcc)); if ($this->validate) { $this->validate_email($bcc); } - if (($this->_get_protocol() == "smtp") OR ($this->bcc_batch_mode && count($bcc) > $this->bcc_batch_size)) + if ($this->_get_protocol() === 'smtp' OR ($this->bcc_batch_mode && count($bcc) > $this->bcc_batch_size)) { $this->_bcc_array = $bcc; } else { - $this->_set_header('Bcc', implode(", ", $bcc)); + $this->set_header('Bcc', implode(', ', $bcc)); } return $this; @@ -359,14 +690,13 @@ class CI_Email { /** * Set Email Subject * - * @access public * @param string - * @return void + * @return CI_Email */ public function subject($subject) { $subject = $this->_prep_q_encoding($subject); - $this->_set_header('Subject', $subject); + $this->set_header('Subject', $subject); return $this; } @@ -375,13 +705,12 @@ class CI_Email { /** * Set Body * - * @access public * @param string - * @return void + * @return CI_Email */ public function message($body) { - $this->_body = rtrim(str_replace("\r", "", $body)); + $this->_body = rtrim(str_replace("\r", '', $body)); /* strip slashes only if magic quotes is ON if we do it with magic quotes OFF, it strips real, user-inputted chars. @@ -402,31 +731,89 @@ class CI_Email { /** * Assign file attachments * - * @access public - * @param string - * @return void + * @param string $file Can be local path, URL or buffered content + * @param string $disposition = 'attachment' + * @param string $newname = NULL + * @param string $mime = '' + * @return CI_Email */ - public function attach($filename, $disposition = 'attachment') + public function attach($file, $disposition = '', $newname = NULL, $mime = '') { - $this->_attach_name[] = $filename; - $this->_attach_type[] = $this->_mime_types(pathinfo($filename, PATHINFO_EXTENSION)); - $this->_attach_disp[] = $disposition; // Can also be 'inline' Not sure if it matters + if ($mime === '') + { + if (strpos($file, '://') === FALSE && ! file_exists($file)) + { + $this->_set_error_message('lang:email_attachment_missing', $file); + return FALSE; + } + + if ( ! $fp = @fopen($file, 'rb')) + { + $this->_set_error_message('lang:email_attachment_unreadable', $file); + return FALSE; + } + + $file_content = stream_get_contents($fp); + $mime = $this->_mime_types(pathinfo($file, PATHINFO_EXTENSION)); + fclose($fp); + } + else + { + $file_content =& $file; // buffered file + } + + $this->_attachments[] = array( + 'name' => array($file, $newname), + 'disposition' => empty($disposition) ? 'attachment' : $disposition, // Can also be 'inline' Not sure if it matters + 'type' => $mime, + 'content' => chunk_split(base64_encode($file_content)) + ); + return $this; } // -------------------------------------------------------------------- + /** + * Set and return attachment Content-ID + * + * Useful for attached inline pictures + * + * @param string $filename + * @return string + */ + public function attachment_cid($filename) + { + if ($this->multipart !== 'related') + { + $this->multipart = 'related'; // Thunderbird need this for inline images + } + + for ($i = 0, $c = count($this->_attachments); $i < $c; $i++) + { + if ($this->_attachments[$i]['name'][0] === $filename) + { + $this->_attachments[$i]['cid'] = uniqid(basename($this->_attachments[$i]['name'][0]).'@'); + return $this->_attachments[$i]['cid']; + } + } + + return FALSE; + } + + // -------------------------------------------------------------------- + /** * Add a Header Item * - * @access protected * @param string * @param string - * @return void + * @return CI_Email */ - protected function _set_header($header, $value) + public function set_header($header, $value) { - $this->_headers[$header] = $value; + $this->_headers[$header] = str_replace(array("\n", "\r"), '', $value); + return $this; } // -------------------------------------------------------------------- @@ -434,7 +821,6 @@ class CI_Email { /** * Convert a String to an Array * - * @access protected * @param string * @return array */ @@ -442,16 +828,11 @@ class CI_Email { { if ( ! is_array($email)) { - if (strpos($email, ',') !== FALSE) - { - $email = preg_split('/[\s,]/', $email, -1, PREG_SPLIT_NO_EMPTY); - } - else - { - $email = trim($email); - settype($email, "array"); - } + return (strpos($email, ',') !== FALSE) + ? preg_split('/[\s,]/', $email, -1, PREG_SPLIT_NO_EMPTY) + : (array) trim($email); } + return $email; } @@ -460,13 +841,12 @@ class CI_Email { /** * Set Multipart Value * - * @access public * @param string - * @return void + * @return CI_Email */ - public function set_alt_message($str = '') + public function set_alt_message($str) { - $this->alt_message = $str; + $this->alt_message = (string) $str; return $this; } @@ -475,13 +855,12 @@ class CI_Email { /** * Set Mailtype * - * @access public * @param string - * @return void + * @return CI_Email */ public function set_mailtype($type = 'text') { - $this->mailtype = ($type == 'html') ? 'html' : 'text'; + $this->mailtype = ($type === 'html') ? 'html' : 'text'; return $this; } @@ -490,13 +869,12 @@ class CI_Email { /** * Set Wordwrap * - * @access public - * @param string - * @return void + * @param bool + * @return CI_Email */ public function set_wordwrap($wordwrap = TRUE) { - $this->wordwrap = ($wordwrap === FALSE) ? FALSE : TRUE; + $this->wordwrap = (bool) $wordwrap; return $this; } @@ -505,13 +883,12 @@ class CI_Email { /** * Set Protocol * - * @access public * @param string - * @return void + * @return CI_Email */ public function set_protocol($protocol = 'mail') { - $this->protocol = ( ! in_array($protocol, $this->_protocols, TRUE)) ? 'mail' : strtolower($protocol); + $this->protocol = in_array($protocol, $this->_protocols, TRUE) ? strtolower($protocol) : 'mail'; return $this; } @@ -520,25 +897,12 @@ class CI_Email { /** * Set Priority * - * @access public - * @param integer - * @return void + * @param int + * @return CI_Email */ public function set_priority($n = 3) { - if ( ! is_numeric($n)) - { - $this->priority = 3; - return; - } - - if ($n < 1 OR $n > 5) - { - $this->priority = 3; - return; - } - - $this->priority = $n; + $this->priority = preg_match('/^[1-5]$/', $n) ? (int) $n : 3; return $this; } @@ -547,20 +911,12 @@ class CI_Email { /** * Set Newline Character * - * @access public * @param string - * @return void + * @return CI_Email */ public function set_newline($newline = "\n") { - if ($newline != "\n" AND $newline != "\r\n" AND $newline != "\r") - { - $this->newline = "\n"; - return; - } - - $this->newline = $newline; - + $this->newline = in_array($newline, array("\n", "\r\n", "\r")) ? $newline : "\n"; return $this; } @@ -569,20 +925,12 @@ class CI_Email { /** * Set CRLF * - * @access public * @param string - * @return void + * @return CI_Email */ public function set_crlf($crlf = "\n") { - if ($crlf != "\n" AND $crlf != "\r\n" AND $crlf != "\r") - { - $this->crlf = "\n"; - return; - } - - $this->crlf = $crlf; - + $this->crlf = ($crlf !== "\n" && $crlf !== "\r\n" && $crlf !== "\r") ? "\n" : $crlf; return $this; } @@ -591,13 +939,12 @@ class CI_Email { /** * Set Message Boundary * - * @access protected * @return void */ protected function _set_boundaries() { - $this->_alt_boundary = "B_ALT_".uniqid(''); // multipart/alternative - $this->_atc_boundary = "B_ATC_".uniqid(''); // attachment boundary + $this->_alt_boundary = 'B_ALT_'.uniqid(''); // multipart/alternative + $this->_atc_boundary = 'B_ATC_'.uniqid(''); // attachment boundary } // -------------------------------------------------------------------- @@ -605,16 +952,12 @@ class CI_Email { /** * Get the Message ID * - * @access protected * @return string */ protected function _get_message_id() { - $from = $this->_headers['Return-Path']; - $from = str_replace(">", "", $from); - $from = str_replace("<", "", $from); - - return "<".uniqid('').strstr($from, '@').">"; + $from = str_replace(array('>', '<'), '', $this->_headers['Return-Path']); + return '<'.uniqid('').strstr($from, '@').'>'; } // -------------------------------------------------------------------- @@ -622,16 +965,15 @@ class CI_Email { /** * Get Mail Protocol * - * @access protected * @param bool - * @return string + * @return mixed */ protected function _get_protocol($return = TRUE) { $this->protocol = strtolower($this->protocol); - $this->protocol = ( ! in_array($this->protocol, $this->_protocols, TRUE)) ? 'mail' : $this->protocol; + in_array($this->protocol, $this->_protocols, TRUE) OR $this->protocol = 'mail'; - if ($return == TRUE) + if ($return === TRUE) { return $this->protocol; } @@ -642,23 +984,22 @@ class CI_Email { /** * Get Mail Encoding * - * @access protected * @param bool * @return string */ protected function _get_encoding($return = TRUE) { - $this->_encoding = ( ! in_array($this->_encoding, $this->_bit_depths)) ? '8bit' : $this->_encoding; + in_array($this->_encoding, $this->_bit_depths) OR $this->_encoding = '8bit'; foreach ($this->_base_charsets as $charset) { - if (strncmp($charset, $this->charset, strlen($charset)) == 0) + if (strpos($charset, $this->charset) === 0) { $this->_encoding = '7bit'; } } - if ($return == TRUE) + if ($return === TRUE) { return $this->_encoding; } @@ -669,20 +1010,15 @@ class CI_Email { /** * Get content type (text/html/attachment) * - * @access protected * @return string */ protected function _get_content_type() { - if ($this->mailtype == 'html' && count($this->_attach_name) == 0) + if ($this->mailtype === 'html') { - return 'html'; + return (count($this->_attachments) === 0) ? 'html' : 'html-attach'; } - elseif ($this->mailtype == 'html' && count($this->_attach_name) > 0) - { - return 'html-attach'; - } - elseif ($this->mailtype == 'text' && count($this->_attach_name) > 0) + elseif ($this->mailtype === 'text' && count($this->_attachments) > 0) { return 'plain-attach'; } @@ -697,17 +1033,16 @@ class CI_Email { /** * Set RFC 822 Date * - * @access protected * @return string */ protected function _set_date() { - $timezone = date("Z"); - $operator = (strncmp($timezone, '-', 1) == 0) ? '-' : '+'; + $timezone = date('Z'); + $operator = ($timezone[0] === '-') ? '-' : '+'; $timezone = abs($timezone); - $timezone = floor($timezone/3600) * 100 + ($timezone % 3600 ) / 60; + $timezone = floor($timezone/3600) * 100 + ($timezone % 3600) / 60; - return sprintf("%s %s%04d", date("D, j M Y H:i:s"), $operator, $timezone); + return sprintf('%s %s%04d', date('D, j M Y H:i:s'), $operator, $timezone); } // -------------------------------------------------------------------- @@ -715,12 +1050,11 @@ class CI_Email { /** * Mime message * - * @access protected * @return string */ protected function _get_mime_message() { - return "This is a multi-part message in MIME format.".$this->newline."Your email application may not support this format."; + return 'This is a multi-part message in MIME format.'.$this->newline.'Your email application may not support this format.'; } // -------------------------------------------------------------------- @@ -728,7 +1062,6 @@ class CI_Email { /** * Validate Email Address * - * @access public * @param string * @return bool */ @@ -757,13 +1090,17 @@ class CI_Email { /** * Email Validation * - * @access public * @param string * @return bool */ - public function valid_email($address) + public function valid_email($email) { - return ( ! preg_match("/^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/ix", $address)) ? FALSE : TRUE; + if (function_exists('idn_to_ascii') && $atpos = strpos($email, '@')) + { + $email = substr($email, 0, ++$atpos).idn_to_ascii(substr($email, $atpos)); + } + + return (bool) filter_var($email, FILTER_VALIDATE_EMAIL); } // -------------------------------------------------------------------- @@ -771,7 +1108,6 @@ class CI_Email { /** * Clean Extended Email Address: Joe Smith * - * @access public * @param string * @return string */ @@ -779,28 +1115,14 @@ class CI_Email { { if ( ! is_array($email)) { - if (preg_match('/\<(.*)\>/', $email, $match)) - { - return $match['1']; - } - else - { - return $email; - } + return preg_match('/\<(.*)\>/', $email, $match) ? $match[1] : $email; } $clean_email = array(); foreach ($email as $addy) { - if (preg_match( '/\<(.*)\>/', $addy, $match)) - { - $clean_email[] = $match['1']; - } - else - { - $clean_email[] = $addy; - } + $clean_email[] = preg_match('/\<(.*)\>/', $addy, $match) ? $match[1] : $addy; } return $clean_email; @@ -811,47 +1133,36 @@ class CI_Email { /** * Build alternative plain text message * - * This public function provides the raw message for use - * in plain-text headers of HTML-formatted emails. + * Provides the raw message for use in plain-text headers of + * HTML-formatted emails. * If the user hasn't specified his own alternative message * it creates one by stripping the HTML * - * @access protected * @return string */ protected function _get_alt_message() { - if ($this->alt_message != "") + if ( ! empty($this->alt_message)) { - return $this->word_wrap($this->alt_message, '76'); + return ($this->wordwrap) + ? $this->word_wrap($this->alt_message, 76) + : $this->alt_message; } - if (preg_match('/\(.*)\<\/body\>/si', $this->_body, $match)) - { - $body = $match['1']; - } - else - { - $body = $this->_body; - } - - $body = trim(strip_tags($body)); - $body = preg_replace( '# '.$msg."\n"; - - flock($fp, LOCK_EX); - fwrite($fp, $message); - flock($fp, LOCK_UN); - fclose($fp); - - @chmod($filepath, FILE_WRITE_MODE); - return TRUE; - } - -} -// END Log Class - -/* End of file Log.php */ -/* Location: ./system/libraries/Log.php */ \ No newline at end of file diff --git a/src/system/libraries/Migration.php b/src/system/libraries/Migration.php index c70a8b2f..316c94ae 100644 --- a/src/system/libraries/Migration.php +++ b/src/system/libraries/Migration.php @@ -1,20 +1,41 @@ - $val) { - $this->{'_' . $key} = $val; + $this->{'_'.$key} = $val; } - log_message('debug', 'Migrations class initialized'); + log_message('info', 'Migrations Class Initialized'); // Are they trying to use migrations while it is disabled? if ($this->_migration_enabled !== TRUE) @@ -58,7 +135,7 @@ class CI_Migration { } // If not set, set it - $this->_migration_path == '' AND $this->_migration_path = APPPATH . 'migrations/'; + $this->_migration_path !== '' OR $this->_migration_path = APPPATH.'migrations/'; // Add trailing slash if not set $this->_migration_path = rtrim($this->_migration_path, '/').'/'; @@ -69,16 +146,39 @@ class CI_Migration { // They'll probably be using dbforge $this->load->dbforge(); + // Make sure the migration table name was set. + if (empty($this->_migration_table)) + { + show_error('Migrations configuration file (migration.php) must have "migration_table" set.'); + } + + // Migration basename regex + $this->_migration_regex = ($this->_migration_type === 'timestamp') + ? '/^\d{14}_(\w+)$/' + : '/^\d{3}_(\w+)$/'; + + // Make sure a valid migration numbering type was set. + if ( ! in_array($this->_migration_type, array('sequential', 'timestamp'))) + { + show_error('An invalid migration numbering type was specified: '.$this->_migration_type); + } + // If the migrations table is missing, make it - if ( ! $this->db->table_exists('migrations')) + if ( ! $this->db->table_exists($this->_migration_table)) { $this->dbforge->add_field(array( - 'version' => array('type' => 'INT', 'constraint' => 3), + 'version' => array('type' => 'BIGINT', 'constraint' => 20), )); - $this->dbforge->create_table('migrations', TRUE); + $this->dbforge->create_table($this->_migration_table, TRUE); - $this->db->insert('migrations', array('version' => 0)); + $this->db->insert($this->_migration_table, array('version' => 0)); + } + + // Do we auto migrate to the latest migration? + if ($this->_migration_auto_latest === TRUE && ! $this->latest()) + { + show_error($this->error_string()); } } @@ -90,154 +190,169 @@ class CI_Migration { * Calls each migration step required to get to the schema version of * choice * - * @param int Target schema version - * @return mixed TRUE if already latest, FALSE if failed, int if upgraded + * @param string $target_version Target schema version + * @return mixed TRUE if no migrations are found, current version string on success, FALSE on failure */ public function version($target_version) { - $start = $current_version = $this->_get_version(); - $stop = $target_version; + // Note: We use strings, so that timestamp versions work on 32-bit systems + $current_version = $this->_get_version(); - if ($target_version > $current_version) + if ($this->_migration_type === 'sequential') { - // Moving Up - ++$start; - ++$stop; - $step = 1; + $target_version = sprintf('%03d', $target_version); } else { - // Moving Down - $step = -1; + $target_version = (string) $target_version; } - $method = ($step === 1) ? 'up' : 'down'; - $migrations = array(); + $migrations = $this->find_migrations(); - // We now prepare to actually DO the migrations - // But first let's make sure that everything is the way it should be - for ($i = $start; $i != $stop; $i += $step) + if ($target_version > 0 && ! isset($migrations[$target_version])) { - $f = glob(sprintf($this->_migration_path . '%03d_*.php', $i)); - - // Only one migration per step is permitted - if (count($f) > 1) - { - $this->_error_string = sprintf($this->lang->line('migration_multiple_version'), $i); - return FALSE; - } - - // Migration step not found - if (count($f) == 0) - { - // If trying to migrate up to a version greater than the last - // existing one, migrate to the last one. - if ($step == 1) - { - break; - } - - // If trying to migrate down but we're missing a step, - // something must definitely be wrong. - $this->_error_string = sprintf($this->lang->line('migration_not_found'), $i); - return FALSE; - } - - $file = basename($f[0]); - $name = basename($f[0], '.php'); - - // Filename validations - if (preg_match('/^\d{3}_(\w+)$/', $name, $match)) - { - $match[1] = strtolower($match[1]); - - // Cannot repeat a migration at different steps - if (in_array($match[1], $migrations)) - { - $this->_error_string = sprintf($this->lang->line('migration_multiple_version'), $match[1]); - return FALSE; - } - - include $f[0]; - $class = 'Migration_' . ucfirst($match[1]); - - if ( ! class_exists($class)) - { - $this->_error_string = sprintf($this->lang->line('migration_class_doesnt_exist'), $class); - return FALSE; - } - - if ( ! is_callable(array($class, $method))) - { - $this->_error_string = sprintf($this->lang->line('migration_missing_'.$method.'_method'), $class); - return FALSE; - } - - $migrations[] = $match[1]; - } - else - { - $this->_error_string = sprintf($this->lang->line('migration_invalid_filename'), $file); - return FALSE; - } + $this->_error_string = sprintf($this->lang->line('migration_not_found'), $target_version); + return FALSE; } - log_message('debug', 'Current migration: ' . $current_version); - - $version = $i + ($step == 1 ? -1 : 0); - - // If there is nothing to do so quit - if ($migrations === array()) + if ($target_version > $current_version) { + $method = 'up'; + } + elseif ($target_version < $current_version) + { + $method = 'down'; + // We need this so that migrations are applied in reverse order + krsort($migrations); + } + else + { + // Well, there's nothing to migrate then ... return TRUE; } - log_message('debug', 'Migrating from ' . $method . ' to version ' . $version); - - // Loop through the migrations - foreach ($migrations AS $migration) + // Validate all available migrations within our target range. + // + // Unfortunately, we'll have to use another loop to run them + // in order to avoid leaving the procedure in a broken state. + // + // See https://github.com/bcit-ci/CodeIgniter/issues/4539 + $pending = array(); + foreach ($migrations as $number => $file) { - // Run the migration class - $class = 'Migration_' . ucfirst(strtolower($migration)); - call_user_func(array(new $class, $method)); + // Ignore versions out of our range. + // + // Because we've previously sorted the $migrations array depending on the direction, + // we can safely break the loop once we reach $target_version ... + if ($method === 'up') + { + if ($number <= $current_version) + { + continue; + } + elseif ($number > $target_version) + { + break; + } + } + else + { + if ($number > $current_version) + { + continue; + } + elseif ($number <= $target_version) + { + break; + } + } - $current_version += $step; + // Check for sequence gaps + if ($this->_migration_type === 'sequential') + { + if (isset($previous) && abs($number - $previous) > 1) + { + $this->_error_string = sprintf($this->lang->line('migration_sequence_gap'), $number); + return FALSE; + } + + $previous = $number; + } + + include_once($file); + $class = 'Migration_'.ucfirst(strtolower($this->_get_migration_name(basename($file, '.php')))); + + // Validate the migration file structure + if ( ! class_exists($class, FALSE)) + { + $this->_error_string = sprintf($this->lang->line('migration_class_doesnt_exist'), $class); + return FALSE; + } + // method_exists() returns true for non-public methods, + // while is_callable() can't be used without instantiating. + // Only get_class_methods() satisfies both conditions. + elseif ( ! in_array($method, array_map('strtolower', get_class_methods($class)))) + { + $this->_error_string = sprintf($this->lang->line('migration_missing_'.$method.'_method'), $class); + return FALSE; + } + + $pending[$number] = array($class, $method); + } + + // Now just run the necessary migrations + foreach ($pending as $number => $migration) + { + log_message('debug', 'Migrating '.$method.' from version '.$current_version.' to version '.$number); + + $migration[0] = new $migration[0]; + call_user_func($migration); + $current_version = $number; + $this->_update_version($current_version); + } + + // This is necessary when moving down, since the the last migration applied + // will be the down() method for the next migration up from the target + if ($current_version <> $target_version) + { + $current_version = $target_version; $this->_update_version($current_version); } log_message('debug', 'Finished migrating to '.$current_version); - return $current_version; } // -------------------------------------------------------------------- /** - * Set's the schema to the latest migration + * Sets the schema to the latest migration * - * @return mixed true if already latest, false if failed, int if upgraded + * @return mixed Current version string on success, FALSE on failure */ public function latest() { - if ( ! $migrations = $this->find_migrations()) + $migrations = $this->find_migrations(); + + if (empty($migrations)) { $this->_error_string = $this->lang->line('migration_none_found'); - return false; + return FALSE; } $last_migration = basename(end($migrations)); // Calculate the last migration step from existing migration - // filenames and procceed to the standard version migration - return $this->version((int) substr($last_migration, 0, 3)); + // filenames and proceed to the standard version migration + return $this->version($this->_get_migration_number($last_migration)); } // -------------------------------------------------------------------- /** - * Set's the schema to the migration version set in config + * Sets the schema to the migration version set in config * - * @return mixed true if already current, false if failed, int if upgraded + * @return mixed TRUE if no migrations are found, current version string on success, FALSE on failure */ public function current() { @@ -259,28 +374,66 @@ class CI_Migration { // -------------------------------------------------------------------- /** - * Set's the schema to the latest migration + * Retrieves list of available migration scripts * - * @return mixed true if already latest, false if failed, int if upgraded + * @return array list of migration file paths sorted by version */ - protected function find_migrations() + public function find_migrations() { - // Load all *_*.php files in the migrations path - $files = glob($this->_migration_path . '*_*.php'); - $file_count = count($files); + $migrations = array(); - for ($i = 0; $i < $file_count; $i++) + // Load all *_*.php files in the migrations path + foreach (glob($this->_migration_path.'*_*.php') as $file) { - // Mark wrongly formatted files as false for later filtering - $name = basename($files[$i], '.php'); - if ( ! preg_match('/^\d{3}_(\w+)$/', $name)) + $name = basename($file, '.php'); + + // Filter out non-migration files + if (preg_match($this->_migration_regex, $name)) { - $files[$i] = FALSE; + $number = $this->_get_migration_number($name); + + // There cannot be duplicate migration numbers + if (isset($migrations[$number])) + { + $this->_error_string = sprintf($this->lang->line('migration_multiple_version'), $number); + show_error($this->_error_string); + } + + $migrations[$number] = $file; } } - sort($files); - return $files; + ksort($migrations); + return $migrations; + } + + // -------------------------------------------------------------------- + + /** + * Extracts the migration number from a filename + * + * @param string $migration + * @return string Numeric portion of a migration filename + */ + protected function _get_migration_number($migration) + { + return sscanf($migration, '%[0-9]+', $number) + ? $number : '0'; + } + + // -------------------------------------------------------------------- + + /** + * Extracts the migration class name from a filename + * + * @param string $migration + * @return string text portion of a migration filename + */ + protected function _get_migration_name($migration) + { + $parts = explode('_', $migration); + array_shift($parts); + return implode('_', $parts); } // -------------------------------------------------------------------- @@ -288,12 +441,12 @@ class CI_Migration { /** * Retrieves current schema version * - * @return int Current Migration + * @return string Current migration version */ protected function _get_version() { - $row = $this->db->get('migrations')->row(); - return $row ? $row->version : 0; + $row = $this->db->select('version')->get($this->_migration_table)->row(); + return $row ? $row->version : '0'; } // -------------------------------------------------------------------- @@ -301,13 +454,13 @@ class CI_Migration { /** * Stores the current schema version * - * @param int Migration reached - * @return bool + * @param string $migration Migration reached + * @return void */ - protected function _update_version($migrations) + protected function _update_version($migration) { - return $this->db->update('migrations', array( - 'version' => $migrations + $this->db->update($this->_migration_table, array( + 'version' => $migration )); } @@ -316,7 +469,7 @@ class CI_Migration { /** * Enable the use of CI super-global * - * @param mixed $var + * @param string $var * @return mixed */ public function __get($var) @@ -325,6 +478,3 @@ class CI_Migration { } } - -/* End of file Migration.php */ -/* Location: ./system/libraries/Migration.php */ \ No newline at end of file diff --git a/src/system/libraries/Pagination.php b/src/system/libraries/Pagination.php index 25af5d66..44f848fe 100644 --- a/src/system/libraries/Pagination.php +++ b/src/system/libraries/Pagination.php @@ -1,20 +1,41 @@ -'; - var $cur_tag_close = ''; - var $next_tag_open = ' '; - var $next_tag_close = ' '; - var $prev_tag_open = ' '; - var $prev_tag_close = ''; - var $num_tag_open = ' '; - var $num_tag_close = ''; - var $page_query_string = FALSE; - var $query_string_segment = 'per_page'; - var $display_pages = TRUE; - var $anchor_class = ''; + /** + * Prefix + * + * @var string + */ + protected $prefix = ''; + + /** + * Suffix + * + * @var string + */ + protected $suffix = ''; + + /** + * Total number of items + * + * @var int + */ + protected $total_rows = 0; + + /** + * Number of links to show + * + * Relates to "digit" type links shown before/after + * the currently viewed page. + * + * @var int + */ + protected $num_links = 2; + + /** + * Items per page + * + * @var int + */ + public $per_page = 10; + + /** + * Current page + * + * @var int + */ + public $cur_page = 0; + + /** + * Use page numbers flag + * + * Whether to use actual page numbers instead of an offset + * + * @var bool + */ + protected $use_page_numbers = FALSE; + + /** + * First link + * + * @var string + */ + protected $first_link = '‹ First'; + + /** + * Next link + * + * @var string + */ + protected $next_link = '>'; + + /** + * Previous link + * + * @var string + */ + protected $prev_link = '<'; + + /** + * Last link + * + * @var string + */ + protected $last_link = 'Last ›'; + + /** + * URI Segment + * + * @var int + */ + protected $uri_segment = 0; + + /** + * Full tag open + * + * @var string + */ + protected $full_tag_open = ''; + + /** + * Full tag close + * + * @var string + */ + protected $full_tag_close = ''; + + /** + * First tag open + * + * @var string + */ + protected $first_tag_open = ''; + + /** + * First tag close + * + * @var string + */ + protected $first_tag_close = ''; + + /** + * Last tag open + * + * @var string + */ + protected $last_tag_open = ''; + + /** + * Last tag close + * + * @var string + */ + protected $last_tag_close = ''; + + /** + * First URL + * + * An alternative URL for the first page + * + * @var string + */ + protected $first_url = ''; + + /** + * Current tag open + * + * @var string + */ + protected $cur_tag_open = ''; + + /** + * Current tag close + * + * @var string + */ + protected $cur_tag_close = ''; + + /** + * Next tag open + * + * @var string + */ + protected $next_tag_open = ''; + + /** + * Next tag close + * + * @var string + */ + protected $next_tag_close = ''; + + /** + * Previous tag open + * + * @var string + */ + protected $prev_tag_open = ''; + + /** + * Previous tag close + * + * @var string + */ + protected $prev_tag_close = ''; + + /** + * Number tag open + * + * @var string + */ + protected $num_tag_open = ''; + + /** + * Number tag close + * + * @var string + */ + protected $num_tag_close = ''; + + /** + * Page query string flag + * + * @var bool + */ + protected $page_query_string = FALSE; + + /** + * Query string segment + * + * @var string + */ + protected $query_string_segment = 'per_page'; + + /** + * Display pages flag + * + * @var bool + */ + protected $display_pages = TRUE; + + /** + * Attributes + * + * @var string + */ + protected $_attributes = ''; + + /** + * Link types + * + * "rel" attribute + * + * @see CI_Pagination::_attr_rel() + * @var array + */ + protected $_link_types = array(); + + /** + * Reuse query string flag + * + * @var bool + */ + protected $reuse_query_string = FALSE; + + /** + * Use global URL suffix flag + * + * @var bool + */ + protected $use_global_url_suffix = FALSE; + + /** + * Data page attribute + * + * @var string + */ + protected $data_page_attr = 'data-ci-pagination-page'; + + /** + * CI Singleton + * + * @var object + */ + protected $CI; + + // -------------------------------------------------------------------- /** * Constructor * - * @access public - * @param array initialization parameters + * @param array $params Initialization parameters + * @return void */ public function __construct($params = array()) { - if (count($params) > 0) + $this->CI =& get_instance(); + $this->CI->load->language('pagination'); + foreach (array('first_link', 'next_link', 'prev_link', 'last_link') as $key) { - $this->initialize($params); + if (($val = $this->CI->lang->line('pagination_'.$key)) !== FALSE) + { + $this->$key = $val; + } } - if ($this->anchor_class != '') - { - $this->anchor_class = 'class="'.$this->anchor_class.'" '; - } - - log_message('debug', "Pagination Class Initialized"); + $this->initialize($params); + log_message('info', 'Pagination Class Initialized'); } // -------------------------------------------------------------------- @@ -87,22 +348,45 @@ class CI_Pagination { /** * Initialize Preferences * - * @access public - * @param array initialization parameters - * @return void + * @param array $params Initialization parameters + * @return CI_Pagination */ - function initialize($params = array()) + public function initialize(array $params = array()) { - if (count($params) > 0) + isset($params['attributes']) OR $params['attributes'] = array(); + if (is_array($params['attributes'])) { - foreach ($params as $key => $val) + $this->_parse_attributes($params['attributes']); + unset($params['attributes']); + } + + // Deprecated legacy support for the anchor_class option + // Should be removed in CI 3.1+ + if (isset($params['anchor_class'])) + { + empty($params['anchor_class']) OR $attributes['class'] = $params['anchor_class']; + unset($params['anchor_class']); + } + + foreach ($params as $key => $val) + { + if (property_exists($this, $key)) { - if (isset($this->$key)) - { - $this->$key = $val; - } + $this->$key = $val; } } + + if ($this->CI->config->item('enable_query_strings') === TRUE) + { + $this->page_query_string = TRUE; + } + + if ($this->use_global_url_suffix === TRUE) + { + $this->suffix = $this->CI->config->item('url_suffix'); + } + + return $this; } // -------------------------------------------------------------------- @@ -110,80 +394,143 @@ class CI_Pagination { /** * Generate the pagination links * - * @access public * @return string */ - function create_links() + public function create_links() { // If our item count or per-page total is zero there is no need to continue. + // Note: DO NOT change the operator to === here! if ($this->total_rows == 0 OR $this->per_page == 0) { return ''; } // Calculate the total number of pages - $num_pages = ceil($this->total_rows / $this->per_page); + $num_pages = (int) ceil($this->total_rows / $this->per_page); // Is there only one page? Hm... nothing more to do here then. - if ($num_pages == 1) + if ($num_pages === 1) { return ''; } - // Set the base page index for starting page number - if ($this->use_page_numbers) + // Check the user defined number of links. + $this->num_links = (int) $this->num_links; + + if ($this->num_links < 0) { - $base_page = 1; + show_error('Your number of links must be a non-negative number.'); + } + + // Keep any existing query string items. + // Note: Has nothing to do with any other query string option. + if ($this->reuse_query_string === TRUE) + { + $get = $this->CI->input->get(); + + // Unset the controll, method, old-school routing options + unset($get['c'], $get['m'], $get[$this->query_string_segment]); } else { - $base_page = 0; + $get = array(); + } + + // Put together our base and first URLs. + // Note: DO NOT append to the properties as that would break successive calls + $base_url = trim($this->base_url); + $first_url = $this->first_url; + + $query_string = ''; + $query_string_sep = (strpos($base_url, '?') === FALSE) ? '?' : '&'; + + // Are we using query strings? + if ($this->page_query_string === TRUE) + { + // If a custom first_url hasn't been specified, we'll create one from + // the base_url, but without the page item. + if ($first_url === '') + { + $first_url = $base_url; + + // If we saved any GET items earlier, make sure they're appended. + if ( ! empty($get)) + { + $first_url .= $query_string_sep.http_build_query($get); + } + } + + // Add the page segment to the end of the query string, where the + // page number will be appended. + $base_url .= $query_string_sep.http_build_query(array_merge($get, array($this->query_string_segment => ''))); + } + else + { + // Standard segment mode. + // Generate our saved query string to append later after the page number. + if ( ! empty($get)) + { + $query_string = $query_string_sep.http_build_query($get); + $this->suffix .= $query_string; + } + + // Does the base_url have the query string in it? + // If we're supposed to save it, remove it so we can append it later. + if ($this->reuse_query_string === TRUE && ($base_query_pos = strpos($base_url, '?')) !== FALSE) + { + $base_url = substr($base_url, 0, $base_query_pos); + } + + if ($first_url === '') + { + $first_url = $base_url.$query_string; + } + + $base_url = rtrim($base_url, '/').'/'; } // Determine the current page number. - $CI =& get_instance(); + $base_page = ($this->use_page_numbers) ? 1 : 0; - if ($CI->config->item('enable_query_strings') === TRUE OR $this->page_query_string === TRUE) + // Are we using query strings? + if ($this->page_query_string === TRUE) { - if ($CI->input->get($this->query_string_segment) != $base_page) + $this->cur_page = $this->CI->input->get($this->query_string_segment); + } + elseif (empty($this->cur_page)) + { + // Default to the last segment number if one hasn't been defined. + if ($this->uri_segment === 0) { - $this->cur_page = $CI->input->get($this->query_string_segment); + $this->uri_segment = count($this->CI->uri->segment_array()); + } - // Prep the current page - no funny business! - $this->cur_page = (int) $this->cur_page; + $this->cur_page = $this->CI->uri->segment($this->uri_segment); + + // Remove any specified prefix/suffix from the segment. + if ($this->prefix !== '' OR $this->suffix !== '') + { + $this->cur_page = str_replace(array($this->prefix, $this->suffix), '', $this->cur_page); } } else { - if ($CI->uri->segment($this->uri_segment) != $base_page) - { - $this->cur_page = $CI->uri->segment($this->uri_segment); - - // Prep the current page - no funny business! - $this->cur_page = (int) $this->cur_page; - } + $this->cur_page = (string) $this->cur_page; } - - // Set current page to 1 if using page numbers instead of offset - if ($this->use_page_numbers AND $this->cur_page == 0) + + // If something isn't quite right, back to the default base page. + if ( ! ctype_digit($this->cur_page) OR ($this->use_page_numbers && (int) $this->cur_page === 0)) { $this->cur_page = $base_page; } - - $this->num_links = (int)$this->num_links; - - if ($this->num_links < 1) + else { - show_error('Your number of links must be a positive number.'); - } - - if ( ! is_numeric($this->cur_page)) - { - $this->cur_page = $base_page; + // Make sure we're using integers for comparisons later. + $this->cur_page = (int) $this->cur_page; } // Is the page number beyond the result range? - // If so we show the last page + // If so, we show the last page. if ($this->use_page_numbers) { if ($this->cur_page > $num_pages) @@ -191,67 +538,56 @@ class CI_Pagination { $this->cur_page = $num_pages; } } - else + elseif ($this->cur_page > $this->total_rows) { - if ($this->cur_page > $this->total_rows) - { - $this->cur_page = ($num_pages - 1) * $this->per_page; - } + $this->cur_page = ($num_pages - 1) * $this->per_page; } $uri_page_number = $this->cur_page; - + + // If we're using offset instead of page numbers, convert it + // to a page number, so we can generate the surrounding number links. if ( ! $this->use_page_numbers) { - $this->cur_page = floor(($this->cur_page/$this->per_page) + 1); + $this->cur_page = (int) floor(($this->cur_page/$this->per_page) + 1); } // Calculate the start and end numbers. These determine - // which number to start and end the digit links with - $start = (($this->cur_page - $this->num_links) > 0) ? $this->cur_page - ($this->num_links - 1) : 1; - $end = (($this->cur_page + $this->num_links) < $num_pages) ? $this->cur_page + $this->num_links : $num_pages; - - // Is pagination being used over GET or POST? If get, add a per_page query - // string. If post, add a trailing slash to the base URL if needed - if ($CI->config->item('enable_query_strings') === TRUE OR $this->page_query_string === TRUE) - { - $this->base_url = rtrim($this->base_url).'&'.$this->query_string_segment.'='; - } - else - { - $this->base_url = rtrim($this->base_url, '/') .'/'; - } + // which number to start and end the digit links with. + $start = (($this->cur_page - $this->num_links) > 0) ? $this->cur_page - ($this->num_links - 1) : 1; + $end = (($this->cur_page + $this->num_links) < $num_pages) ? $this->cur_page + $this->num_links : $num_pages; // And here we go... $output = ''; - // Render the "First" link - if ($this->first_link !== FALSE AND $this->cur_page > ($this->num_links + 1)) + // Render the "First" link. + if ($this->first_link !== FALSE && $this->cur_page > ($this->num_links + 1 + ! $this->num_links)) { - $first_url = ($this->first_url == '') ? $this->base_url : $this->first_url; - $output .= $this->first_tag_open.'anchor_class.'href="'.$first_url.'">'.$this->first_link.''.$this->first_tag_close; + // Take the general parameters, and squeeze this pagination-page attr in for JS frameworks. + $attributes = sprintf('%s %s="%d"', $this->_attributes, $this->data_page_attr, 1); + + $output .= $this->first_tag_open.'_attr_rel('start').'>' + .$this->first_link.''.$this->first_tag_close; } - // Render the "previous" link - if ($this->prev_link !== FALSE AND $this->cur_page != 1) + // Render the "Previous" link. + if ($this->prev_link !== FALSE && $this->cur_page !== 1) { - if ($this->use_page_numbers) - { - $i = $uri_page_number - 1; - } - else - { - $i = $uri_page_number - $this->per_page; - } + $i = ($this->use_page_numbers) ? $uri_page_number - 1 : $uri_page_number - $this->per_page; - if ($i == 0 && $this->first_url != '') + $attributes = sprintf('%s %s="%d"', $this->_attributes, $this->data_page_attr, ($this->cur_page - 1)); + + if ($i === $base_page) { - $output .= $this->prev_tag_open.'anchor_class.'href="'.$this->first_url.'">'.$this->prev_link.''.$this->prev_tag_close; + // First page + $output .= $this->prev_tag_open.'_attr_rel('prev').'>' + .$this->prev_link.''.$this->prev_tag_close; } else { - $i = ($i == 0) ? '' : $this->prefix.$i.$this->suffix; - $output .= $this->prev_tag_open.'anchor_class.'href="'.$this->base_url.$i.'">'.$this->prev_link.''.$this->prev_tag_close; + $append = $this->prefix.$i.$this->suffix; + $output .= $this->prev_tag_open.'_attr_rel('prev').'>' + .$this->prev_link.''.$this->prev_tag_close; } } @@ -260,82 +596,106 @@ class CI_Pagination { if ($this->display_pages !== FALSE) { // Write the digit links - for ($loop = $start -1; $loop <= $end; $loop++) + for ($loop = $start - 1; $loop <= $end; $loop++) { - if ($this->use_page_numbers) - { - $i = $loop; - } - else - { - $i = ($loop * $this->per_page) - $this->per_page; - } + $i = ($this->use_page_numbers) ? $loop : ($loop * $this->per_page) - $this->per_page; + + $attributes = sprintf('%s %s="%d"', $this->_attributes, $this->data_page_attr, $loop); if ($i >= $base_page) { - if ($this->cur_page == $loop) + if ($this->cur_page === $loop) { - $output .= $this->cur_tag_open.$loop.$this->cur_tag_close; // Current page + // Current page + $output .= $this->cur_tag_open.$loop.$this->cur_tag_close; + } + elseif ($i === $base_page) + { + // First page + $output .= $this->num_tag_open.'_attr_rel('start').'>' + .$loop.''.$this->num_tag_close; } else { - $n = ($i == $base_page) ? '' : $i; - - if ($n == '' && $this->first_url != '') - { - $output .= $this->num_tag_open.'anchor_class.'href="'.$this->first_url.'">'.$loop.''.$this->num_tag_close; - } - else - { - $n = ($n == '') ? '' : $this->prefix.$n.$this->suffix; - - $output .= $this->num_tag_open.'anchor_class.'href="'.$this->base_url.$n.'">'.$loop.''.$this->num_tag_close; - } + $append = $this->prefix.$i.$this->suffix; + $output .= $this->num_tag_open.'' + .$loop.''.$this->num_tag_close; } } } } // Render the "next" link - if ($this->next_link !== FALSE AND $this->cur_page < $num_pages) + if ($this->next_link !== FALSE && $this->cur_page < $num_pages) { - if ($this->use_page_numbers) - { - $i = $this->cur_page + 1; - } - else - { - $i = ($this->cur_page * $this->per_page); - } + $i = ($this->use_page_numbers) ? $this->cur_page + 1 : $this->cur_page * $this->per_page; - $output .= $this->next_tag_open.'anchor_class.'href="'.$this->base_url.$this->prefix.$i.$this->suffix.'">'.$this->next_link.''.$this->next_tag_close; + $attributes = sprintf('%s %s="%d"', $this->_attributes, $this->data_page_attr, $this->cur_page + 1); + + $output .= $this->next_tag_open.'_attr_rel('next').'>'.$this->next_link.''.$this->next_tag_close; } // Render the "Last" link - if ($this->last_link !== FALSE AND ($this->cur_page + $this->num_links) < $num_pages) + if ($this->last_link !== FALSE && ($this->cur_page + $this->num_links + ! $this->num_links) < $num_pages) { - if ($this->use_page_numbers) - { - $i = $num_pages; - } - else - { - $i = (($num_pages * $this->per_page) - $this->per_page); - } - $output .= $this->last_tag_open.'anchor_class.'href="'.$this->base_url.$this->prefix.$i.$this->suffix.'">'.$this->last_link.''.$this->last_tag_close; + $i = ($this->use_page_numbers) ? $num_pages : ($num_pages * $this->per_page) - $this->per_page; + + $attributes = sprintf('%s %s="%d"', $this->_attributes, $this->data_page_attr, $num_pages); + + $output .= $this->last_tag_open.'' + .$this->last_link.''.$this->last_tag_close; } - // Kill double slashes. Note: Sometimes we can end up with a double slash + // Kill double slashes. Note: Sometimes we can end up with a double slash // in the penultimate link so we'll kill all double slashes. - $output = preg_replace("#([^:])//+#", "\\1/", $output); + $output = preg_replace('#([^:"])//+#', '\\1/', $output); // Add the wrapper HTML if exists - $output = $this->full_tag_open.$output.$this->full_tag_close; - - return $output; + return $this->full_tag_open.$output.$this->full_tag_close; } -} -// END Pagination Class -/* End of file Pagination.php */ -/* Location: ./system/libraries/Pagination.php */ \ No newline at end of file + // -------------------------------------------------------------------- + + /** + * Parse attributes + * + * @param array $attributes + * @return void + */ + protected function _parse_attributes($attributes) + { + isset($attributes['rel']) OR $attributes['rel'] = TRUE; + $this->_link_types = ($attributes['rel']) + ? array('start' => 'start', 'prev' => 'prev', 'next' => 'next') + : array(); + unset($attributes['rel']); + + $this->_attributes = ''; + foreach ($attributes as $key => $value) + { + $this->_attributes .= ' '.$key.'="'.$value.'"'; + } + } + + // -------------------------------------------------------------------- + + /** + * Add "rel" attribute + * + * @link http://www.w3.org/TR/html5/links.html#linkTypes + * @param string $type + * @return string + */ + protected function _attr_rel($type) + { + if (isset($this->_link_types[$type])) + { + unset($this->_link_types[$type]); + return ' rel="'.$type.'"'; + } + + return ''; + } + +} diff --git a/src/system/libraries/Parser.php b/src/system/libraries/Parser.php index a94d626c..22cffb2c 100644 --- a/src/system/libraries/Parser.php +++ b/src/system/libraries/Parser.php @@ -1,20 +1,41 @@ -CI =& get_instance(); + log_message('info', 'Parser Class Initialized'); + } + + // -------------------------------------------------------------------- + + /** + * Parse a template * * Parses pseudo-variables contained in the specified template view, * replacing them with the data in the second param * - * @access public * @param string * @param array * @param bool @@ -45,8 +97,7 @@ class CI_Parser { */ public function parse($template, $data, $return = FALSE) { - $CI =& get_instance(); - $template = $CI->load->view($template, $data, TRUE); + $template = $this->CI->load->view($template, $data, TRUE); return $this->_parse($template, $data, $return); } @@ -54,18 +105,17 @@ class CI_Parser { // -------------------------------------------------------------------- /** - * Parse a String + * Parse a String * * Parses pseudo-variables contained in the specified string, * replacing them with the data in the second param * - * @access public * @param string * @param array * @param bool * @return string */ - function parse_string($template, $data, $return = FALSE) + public function parse_string($template, $data, $return = FALSE) { return $this->_parse($template, $data, $return); } @@ -73,40 +123,40 @@ class CI_Parser { // -------------------------------------------------------------------- /** - * Parse a template + * Parse a template * * Parses pseudo-variables contained in the specified template, * replacing them with the data in the second param * - * @access public * @param string * @param array * @param bool * @return string */ - function _parse($template, $data, $return = FALSE) + protected function _parse($template, $data, $return = FALSE) { - if ($template == '') + if ($template === '') { return FALSE; } + $replace = array(); foreach ($data as $key => $val) { - if (is_array($val)) - { - $template = $this->_parse_pair($key, $val, $template); - } - else - { - $template = $this->_parse_single($key, (string)$val, $template); - } + $replace = array_merge( + $replace, + is_array($val) + ? $this->_parse_pair($key, $val, $template) + : $this->_parse_single($key, (string) $val, $template) + ); } - if ($return == FALSE) + unset($data); + $template = strtr($template, $replace); + + if ($return === FALSE) { - $CI =& get_instance(); - $CI->output->append_output($template); + $this->CI->output->append_output($template); } return $template; @@ -115,14 +165,13 @@ class CI_Parser { // -------------------------------------------------------------------- /** - * Set the left/right variable delimiters + * Set the left/right variable delimiters * - * @access public * @param string * @param string * @return void */ - function set_delimiters($l = '{', $r = '}') + public function set_delimiters($l = '{', $r = '}') { $this->l_delim = $l; $this->r_delim = $r; @@ -131,83 +180,69 @@ class CI_Parser { // -------------------------------------------------------------------- /** - * Parse a single key/value + * Parse a single key/value * - * @access private * @param string * @param string * @param string * @return string */ - function _parse_single($key, $val, $string) + protected function _parse_single($key, $val, $string) { - return str_replace($this->l_delim.$key.$this->r_delim, $val, $string); + return array($this->l_delim.$key.$this->r_delim => (string) $val); } // -------------------------------------------------------------------- /** - * Parse a tag pair + * Parse a tag pair * - * Parses tag pairs: {some_tag} string... {/some_tag} + * Parses tag pairs: {some_tag} string... {/some_tag} * - * @access private * @param string * @param array * @param string * @return string */ - function _parse_pair($variable, $data, $string) + protected function _parse_pair($variable, $data, $string) { - if (FALSE === ($match = $this->_match_pair($string, $variable))) - { - return $string; - } + $replace = array(); + preg_match_all( + '#'.preg_quote($this->l_delim.$variable.$this->r_delim).'(.+?)'.preg_quote($this->l_delim.'/'.$variable.$this->r_delim).'#s', + $string, + $matches, + PREG_SET_ORDER + ); - $str = ''; - foreach ($data as $row) + foreach ($matches as $match) { - $temp = $match['1']; - foreach ($row as $key => $val) + $str = ''; + foreach ($data as $row) { - if ( ! is_array($val)) + $temp = array(); + foreach ($row as $key => $val) { - $temp = $this->_parse_single($key, $val, $temp); - } - else - { - $temp = $this->_parse_pair($key, $val, $temp); + if (is_array($val)) + { + $pair = $this->_parse_pair($key, $val, $match[1]); + if ( ! empty($pair)) + { + $temp = array_merge($temp, $pair); + } + + continue; + } + + $temp[$this->l_delim.$key.$this->r_delim] = $val; } + + $str .= strtr($match[1], $temp); } - $str .= $temp; + $replace[$match[0]] = $str; } - return str_replace($match['0'], $str, $string); - } - - // -------------------------------------------------------------------- - - /** - * Matches a variable pair - * - * @access private - * @param string - * @param string - * @return mixed - */ - function _match_pair($string, $variable) - { - if ( ! preg_match("|" . preg_quote($this->l_delim) . $variable . preg_quote($this->r_delim) . "(.+?)". preg_quote($this->l_delim) . '/' . $variable . preg_quote($this->r_delim) . "|s", $string, $match)) - { - return FALSE; - } - - return $match; + return $replace; } } -// END Parser Class - -/* End of file Parser.php */ -/* Location: ./system/libraries/Parser.php */ diff --git a/src/system/libraries/Profiler.php b/src/system/libraries/Profiler.php index 7b376d2c..cf455d3d 100644 --- a/src/system/libraries/Profiler.php +++ b/src/system/libraries/Profiler.php @@ -1,20 +1,41 @@ -CI =& get_instance(); $this->CI->load->language('profiler'); - if (isset($config['query_toggle_count'])) - { - $this->_query_toggle_count = (int) $config['query_toggle_count']; - unset($config['query_toggle_count']); - } - // default all sections to display foreach ($this->_available_sections as $section) { @@ -73,6 +110,7 @@ class CI_Profiler { } $this->set_sections($config); + log_message('info', 'Profiler Class Initialized'); } // -------------------------------------------------------------------- @@ -82,16 +120,22 @@ class CI_Profiler { * * Sets the private _compile_* properties to enable/disable Profiler sections * - * @param mixed + * @param mixed $config * @return void */ public function set_sections($config) { + if (isset($config['query_toggle_count'])) + { + $this->_query_toggle_count = (int) $config['query_toggle_count']; + unset($config['query_toggle_count']); + } + foreach ($config as $method => $enable) { if (in_array($method, $this->_available_sections)) { - $this->_compile_{$method} = ($enable !== FALSE) ? TRUE : FALSE; + $this->_compile_{$method} = ($enable !== FALSE); } } } @@ -115,36 +159,32 @@ class CI_Profiler { { // We match the "end" marker so that the list ends // up in the order that it was defined - if (preg_match("/(.+?)_end/i", $key, $match)) + if (preg_match('/(.+?)_end$/i', $key, $match) + && isset($this->CI->benchmark->marker[$match[1].'_end'], $this->CI->benchmark->marker[$match[1].'_start'])) { - if (isset($this->CI->benchmark->marker[$match[1].'_end']) AND isset($this->CI->benchmark->marker[$match[1].'_start'])) - { - $profile[$match[1]] = $this->CI->benchmark->elapsed_time($match[1].'_start', $key); - } + $profile[$match[1]] = $this->CI->benchmark->elapsed_time($match[1].'_start', $key); } } // Build a table containing the profile data. // Note: At some point we should turn this into a template that can - // be modified. We also might want to make this data available to be logged + // be modified. We also might want to make this data available to be logged - $output = "\n\n"; - $output .= '
    '; - $output .= "\n"; - $output .= '  '.$this->CI->lang->line('profiler_benchmarks').'  '; - $output .= "\n"; - $output .= "\n\n\n"; + $output = "\n\n" + .'
    ' + ."\n" + .'  '.$this->CI->lang->line('profiler_benchmarks')."  " + ."\n\n\n
    \n"; foreach ($profile as $key => $val) { $key = ucwords(str_replace(array('_', '-'), ' ', $key)); - $output .= "\n"; + $output .= '\n"; } - $output .= "
    ".$key."  ".$val."
    ' + .$key.'  ' + .$val."
    \n"; - $output .= "
    "; - - return $output; + return $output."\n"; } // -------------------------------------------------------------------- @@ -159,27 +199,37 @@ class CI_Profiler { $dbs = array(); // Let's determine which databases are currently connected to - foreach (get_object_vars($this->CI) as $CI_object) + foreach (get_object_vars($this->CI) as $name => $cobject) { - if (is_object($CI_object) && is_subclass_of(get_class($CI_object), 'CI_DB') ) + if (is_object($cobject)) { - $dbs[] = $CI_object; + if ($cobject instanceof CI_DB) + { + $dbs[get_class($this->CI).':$'.$name] = $cobject; + } + elseif ($cobject instanceof CI_Model) + { + foreach (get_object_vars($cobject) as $mname => $mobject) + { + if ($mobject instanceof CI_DB) + { + $dbs[get_class($cobject).':$'.$mname] = $mobject; + } + } + } } } - if (count($dbs) == 0) + if (count($dbs) === 0) { - $output = "\n\n"; - $output .= '
    '; - $output .= "\n"; - $output .= '  '.$this->CI->lang->line('profiler_queries').'  '; - $output .= "\n"; - $output .= "\n\n\n"; - $output .="\n"; - $output .= "
    ".$this->CI->lang->line('profiler_no_db')."
    \n"; - $output .= "
    "; - - return $output; + return "\n\n" + .'
    ' + ."\n" + .'  '.$this->CI->lang->line('profiler_queries').'  ' + ."\n\n\n\n" + .'\n
    ' + .$this->CI->lang->line('profiler_no_db') + ."
    \n
    "; } // Load the text helper so we can highlight the SQL @@ -189,58 +239,57 @@ class CI_Profiler { $highlight = array('SELECT', 'DISTINCT', 'FROM', 'WHERE', 'AND', 'LEFT JOIN', 'ORDER BY', 'GROUP BY', 'LIMIT', 'INSERT', 'INTO', 'VALUES', 'UPDATE', 'OR ', 'HAVING', 'OFFSET', 'NOT IN', 'IN', 'LIKE', 'NOT LIKE', 'COUNT', 'MAX', 'MIN', 'ON', 'AS', 'AVG', 'SUM', '(', ')'); $output = "\n\n"; - $count = 0; - foreach ($dbs as $db) + foreach ($dbs as $name => $db) { - $count++; - $hide_queries = (count($db->queries) > $this->_query_toggle_count) ? ' display:none' : ''; + $total_time = number_format(array_sum($db->query_times), 4).' '.$this->CI->lang->line('profiler_seconds'); $show_hide_js = '('.$this->CI->lang->line('profiler_section_hide').')'; - if ($hide_queries != '') + if ($hide_queries !== '') { $show_hide_js = '('.$this->CI->lang->line('profiler_section_show').')'; } - $output .= '
    '; - $output .= "\n"; - $output .= '  '.$this->CI->lang->line('profiler_database').':  '.$db->database.'   '.$this->CI->lang->line('profiler_queries').': '.count($db->queries).'  '.$show_hide_js.''; - $output .= "\n"; - $output .= "\n\n\n"; + $output .= '
    ' + ."\n" + .'  '.$this->CI->lang->line('profiler_database') + .':  '.$db->database.' ('.$name.')   '.$this->CI->lang->line('profiler_queries') + .': '.count($db->queries).' ('.$total_time.')  '.$show_hide_js."\n\n\n" + .'
    \n"; - if (count($db->queries) == 0) + if (count($db->queries) === 0) { - $output .= "\n"; + $output .= '\n"; } else { foreach ($db->queries as $key => $val) { $time = number_format($db->query_times[$key], 4); - - $val = highlight_code($val, ENT_QUOTES); + $val = highlight_code($val); foreach ($highlight as $bold) { $val = str_replace($bold, ''.$bold.'', $val); } - $output .= "\n"; + $output .= '\n"; } } - $output .= "
    ".$this->CI->lang->line('profiler_no_queries')."
    ' + .$this->CI->lang->line('profiler_no_queries')."
    ".$time."  ".$val."
    ' + .$time.'  ' + .$val."
    \n"; - $output .= "
    "; - + $output .= "\n"; + $count++; } return $output; } - // -------------------------------------------------------------------- /** @@ -250,44 +299,35 @@ class CI_Profiler { */ protected function _compile_get() { - $output = "\n\n"; - $output .= '
    '; - $output .= "\n"; - $output .= '  '.$this->CI->lang->line('profiler_get_data').'  '; - $output .= "\n"; + $output = "\n\n" + .'
    ' + ."\n" + .'  '.$this->CI->lang->line('profiler_get_data')."  \n"; - if (count($_GET) == 0) + if (count($_GET) === 0) { - $output .= "
    ".$this->CI->lang->line('profiler_no_get')."
    "; + $output .= '
    '.$this->CI->lang->line('profiler_no_get').'
    '; } else { - $output .= "\n\n\n"; + $output .= "\n\n
    \n"; foreach ($_GET as $key => $val) { - if ( ! is_numeric($key)) - { - $key = "'".$key."'"; - } + is_int($key) OR $key = "'".htmlspecialchars($key, ENT_QUOTES, config_item('charset'))."'"; + $val = (is_array($val) OR is_object($val)) + ? '
    '.htmlspecialchars(print_r($val, TRUE), ENT_QUOTES, config_item('charset'))
    +					: htmlspecialchars($val, ENT_QUOTES, config_item('charset'));
     
    -				$output .= "
    \n"; + $output .= '\n"; } $output .= "
    $_GET[".$key."]   "; - if (is_array($val)) - { - $output .= "
    " . htmlspecialchars(stripslashes(print_r($val, true))) . "
    "; - } - else - { - $output .= htmlspecialchars(stripslashes($val)); - } - $output .= "
    $_GET[' + .$key.']   ' + .$val."
    \n"; } - $output .= "
    "; - return $output; + return $output.'
    '; } // -------------------------------------------------------------------- @@ -299,44 +339,47 @@ class CI_Profiler { */ protected function _compile_post() { - $output = "\n\n"; - $output .= '
    '; - $output .= "\n"; - $output .= '  '.$this->CI->lang->line('profiler_post_data').'  '; - $output .= "\n"; + $output = "\n\n" + .'
    ' + ."\n" + .'  '.$this->CI->lang->line('profiler_post_data')."  \n"; - if (count($_POST) == 0) + if (count($_POST) === 0 && count($_FILES) === 0) { - $output .= "
    ".$this->CI->lang->line('profiler_no_post')."
    "; + $output .= '
    '.$this->CI->lang->line('profiler_no_post').'
    '; } else { - $output .= "\n\n\n"; + $output .= "\n\n
    \n"; foreach ($_POST as $key => $val) { - if ( ! is_numeric($key)) - { - $key = "'".$key."'"; - } + is_int($key) OR $key = "'".htmlspecialchars($key, ENT_QUOTES, config_item('charset'))."'"; + $val = (is_array($val) OR is_object($val)) + ? '
    '.htmlspecialchars(print_r($val, TRUE), ENT_QUOTES, config_item('charset'))
    +					: htmlspecialchars($val, ENT_QUOTES, config_item('charset'));
     
    -				$output .= "
    \n"; + $output .= '\n"; + } + + foreach ($_FILES as $key => $val) + { + is_int($key) OR $key = "'".htmlspecialchars($key, ENT_QUOTES, config_item('charset'))."'"; + $val = (is_array($val) OR is_object($val)) + ? '
    '.htmlspecialchars(print_r($val, TRUE), ENT_QUOTES, config_item('charset'))
    +					: htmlspecialchars($val, ENT_QUOTES, config_item('charset'));
    +
    +				$output .= '
    \n"; } $output .= "
    $_POST[".$key."]   "; - if (is_array($val)) - { - $output .= "
    " . htmlspecialchars(stripslashes(print_r($val, TRUE))) . "
    "; - } - else - { - $output .= htmlspecialchars(stripslashes($val)); - } - $output .= "
    $_POST[' + .$key.']   ' + .$val."
    $_FILES[' + .$key.']   ' + .$val."
    \n"; } - $output .= "
    "; - return $output; + return $output.'
    '; } // -------------------------------------------------------------------- @@ -348,24 +391,13 @@ class CI_Profiler { */ protected function _compile_uri_string() { - $output = "\n\n"; - $output .= '
    '; - $output .= "\n"; - $output .= '  '.$this->CI->lang->line('profiler_uri_string').'  '; - $output .= "\n"; - - if ($this->CI->uri->uri_string == '') - { - $output .= "
    ".$this->CI->lang->line('profiler_no_uri')."
    "; - } - else - { - $output .= "
    ".$this->CI->uri->uri_string."
    "; - } - - $output .= "
    "; - - return $output; + return "\n\n" + .'
    ' + ."\n" + .'  '.$this->CI->lang->line('profiler_uri_string')."  \n" + .'
    ' + .($this->CI->uri->uri_string === '' ? $this->CI->lang->line('profiler_no_uri') : $this->CI->uri->uri_string) + .'
    '; } // -------------------------------------------------------------------- @@ -377,17 +409,12 @@ class CI_Profiler { */ protected function _compile_controller_info() { - $output = "\n\n"; - $output .= '
    '; - $output .= "\n"; - $output .= '  '.$this->CI->lang->line('profiler_controller_info').'  '; - $output .= "\n"; - - $output .= "
    ".$this->CI->router->fetch_class()."/".$this->CI->router->fetch_method()."
    "; - - $output .= "
    "; - - return $output; + return "\n\n" + .'
    ' + ."\n" + .'  '.$this->CI->lang->line('profiler_controller_info')."  \n" + .'
    '.$this->CI->router->class.'/'.$this->CI->router->method + .'
    '; } // -------------------------------------------------------------------- @@ -401,24 +428,13 @@ class CI_Profiler { */ protected function _compile_memory_usage() { - $output = "\n\n"; - $output .= '
    '; - $output .= "\n"; - $output .= '  '.$this->CI->lang->line('profiler_memory_usage').'  '; - $output .= "\n"; - - if (function_exists('memory_get_usage') && ($usage = memory_get_usage()) != '') - { - $output .= "
    ".number_format($usage).' bytes
    '; - } - else - { - $output .= "
    ".$this->CI->lang->line('profiler_no_memory')."
    "; - } - - $output .= "
    "; - - return $output; + return "\n\n" + .'
    ' + ."\n" + .'  '.$this->CI->lang->line('profiler_memory_usage')."  \n" + .'
    ' + .(($usage = memory_get_usage()) != '' ? number_format($usage).' bytes' : $this->CI->lang->line('profiler_no_memory')) + .'
    '; } // -------------------------------------------------------------------- @@ -432,24 +448,21 @@ class CI_Profiler { */ protected function _compile_http_headers() { - $output = "\n\n"; - $output .= '
    '; - $output .= "\n"; - $output .= '  '.$this->CI->lang->line('profiler_headers').'  ('.$this->CI->lang->line('profiler_section_show').')'; - $output .= "\n"; + $output = "\n\n" + .'
    ' + ."\n" + .'  '.$this->CI->lang->line('profiler_headers') + .'  ('.$this->CI->lang->line('profiler_section_show').")\n\n\n" + .''."\n"; - $output .= "\n\n\n"; - - foreach (array('HTTP_ACCEPT', 'HTTP_USER_AGENT', 'HTTP_CONNECTION', 'SERVER_PORT', 'SERVER_NAME', 'REMOTE_ADDR', 'SERVER_SOFTWARE', 'HTTP_ACCEPT_LANGUAGE', 'SCRIPT_NAME', 'REQUEST_METHOD',' HTTP_HOST', 'REMOTE_HOST', 'CONTENT_TYPE', 'SERVER_PROTOCOL', 'QUERY_STRING', 'HTTP_ACCEPT_ENCODING', 'HTTP_X_FORWARDED_FOR') as $header) + foreach (array('HTTP_ACCEPT', 'HTTP_USER_AGENT', 'HTTP_CONNECTION', 'SERVER_PORT', 'SERVER_NAME', 'REMOTE_ADDR', 'SERVER_SOFTWARE', 'HTTP_ACCEPT_LANGUAGE', 'SCRIPT_NAME', 'REQUEST_METHOD',' HTTP_HOST', 'REMOTE_HOST', 'CONTENT_TYPE', 'SERVER_PROTOCOL', 'QUERY_STRING', 'HTTP_ACCEPT_ENCODING', 'HTTP_X_FORWARDED_FOR', 'HTTP_DNT') as $header) { - $val = (isset($_SERVER[$header])) ? $_SERVER[$header] : ''; - $output .= "\n"; + $val = isset($_SERVER[$header]) ? htmlspecialchars($_SERVER[$header], ENT_QUOTES, config_item('charset')) : ''; + $output .= '\n"; } - $output .= "\n"; - $output .= "
    "; - - return $output; + return $output."\n
    "; } // -------------------------------------------------------------------- @@ -463,28 +476,24 @@ class CI_Profiler { */ protected function _compile_config() { - $output = "\n\n"; - $output .= '
    '; - $output .= "\n"; - $output .= '  '.$this->CI->lang->line('profiler_config').'  ('.$this->CI->lang->line('profiler_section_show').')'; - $output .= "\n"; + $output = "\n\n" + .'
    ' + ."\n" + .'  '.$this->CI->lang->line('profiler_config').'  ('.$this->CI->lang->line('profiler_section_show').")\n\n\n" + .''."\n"; - $output .= "\n\n\n"; - - foreach ($this->CI->config->config as $config=>$val) + foreach ($this->CI->config->config as $config => $val) { - if (is_array($val)) + if (is_array($val) OR is_object($val)) { $val = print_r($val, TRUE); } - $output .= "\n"; + $output .= '\n"; } - $output .= "\n"; - $output .= "
    "; - - return $output; + return $output."\n
    "; } // -------------------------------------------------------------------- @@ -494,30 +503,29 @@ class CI_Profiler { * * @return string */ - private function _compile_session_data() + protected function _compile_session_data() { if ( ! isset($this->CI->session)) { return; } - $output = '
    '; - $output .= '  '.$this->CI->lang->line('profiler_session_data').'  ('.$this->CI->lang->line('profiler_section_show').')'; - $output .= ""; + $output = '
    ' + .'  '.$this->CI->lang->line('profiler_session_data').'  ('.$this->CI->lang->line('profiler_section_show').')' + .'
    '; - foreach ($this->CI->session->all_userdata() as $key => $val) + foreach ($this->CI->session->userdata() as $key => $val) { if (is_array($val) OR is_object($val)) { $val = print_r($val, TRUE); } - $output .= "\n"; + $output .= '\n"; } - $output .= ''; - $output .= "
    "; - return $output; + return $output."\n"; } // -------------------------------------------------------------------- @@ -529,31 +537,26 @@ class CI_Profiler { */ public function run() { - $output = "
    "; + $output = '
    '; $fields_displayed = 0; foreach ($this->_available_sections as $section) { if ($this->_compile_{$section} !== FALSE) { - $func = "_compile_{$section}"; + $func = '_compile_'.$section; $output .= $this->{$func}(); $fields_displayed++; } } - if ($fields_displayed == 0) + if ($fields_displayed === 0) { - $output .= '

    '.$this->CI->lang->line('profiler_no_profiles').'

    '; + $output .= '

    ' + .$this->CI->lang->line('profiler_no_profiles').'

    '; } - $output .= '
    '; - - return $output; + return $output.'
    '; } + } - -// END CI_Profiler class - -/* End of file Profiler.php */ -/* Location: ./system/libraries/Profiler.php */ \ No newline at end of file diff --git a/src/system/libraries/Session.php b/src/system/libraries/Session.php deleted file mode 100644 index 2bf879e6..00000000 --- a/src/system/libraries/Session.php +++ /dev/null @@ -1,794 +0,0 @@ -CI =& get_instance(); - - // Set all the session preferences, which can either be set - // manually via the $params array above or via the config file - foreach (array('sess_encrypt_cookie', 'sess_use_database', 'sess_table_name', 'sess_expiration', 'sess_expire_on_close', 'sess_match_ip', 'sess_match_useragent', 'sess_cookie_name', 'cookie_path', 'cookie_domain', 'cookie_secure', 'sess_time_to_update', 'time_reference', 'cookie_prefix', 'encryption_key') as $key) - { - $this->$key = (isset($params[$key])) ? $params[$key] : $this->CI->config->item($key); - } - - if ($this->encryption_key == '') - { - show_error('In order to use the Session class you are required to set an encryption key in your config file.'); - } - - // Load the string helper so we can use the strip_slashes() function - $this->CI->load->helper('string'); - - // Do we need encryption? If so, load the encryption class - if ($this->sess_encrypt_cookie == TRUE) - { - $this->CI->load->library('encrypt'); - } - - // Are we using a database? If so, load it - if ($this->sess_use_database === TRUE AND $this->sess_table_name != '') - { - $this->CI->load->database(); - } - - // Set the "now" time. Can either be GMT or server time, based on the - // config prefs. We use this to set the "last activity" time - $this->now = $this->_get_time(); - - // Set the session length. If the session expiration is - // set to zero we'll set the expiration two years from now. - if ($this->sess_expiration == 0) - { - $this->sess_expiration = (60*60*24*365*2); - } - - // Set the cookie name - $this->sess_cookie_name = $this->cookie_prefix.$this->sess_cookie_name; - - // Run the Session routine. If a session doesn't exist we'll - // create a new one. If it does, we'll update it. - if ( ! $this->sess_read()) - { - $this->sess_create(); - } - else - { - $this->sess_update(); - } - - // Delete 'old' flashdata (from last request) - $this->_flashdata_sweep(); - - // Mark all new flashdata as old (data will be deleted before next request) - $this->_flashdata_mark(); - - // Delete expired sessions if necessary - $this->_sess_gc(); - - log_message('debug', "Session routines successfully run"); - } - - // -------------------------------------------------------------------- - - /** - * Fetch the current session data if it exists - * - * @access public - * @return bool - */ - function sess_read() - { - // Fetch the cookie - $session = $this->CI->input->cookie($this->sess_cookie_name); - - // No cookie? Goodbye cruel world!... - if ($session === FALSE) - { - log_message('debug', 'A session cookie was not found.'); - return FALSE; - } - - // HMAC authentication - $len = strlen($session) - 40; - - if ($len <= 0) - { - log_message('error', 'Session: The session cookie was not signed.'); - return FALSE; - } - - // Check cookie authentication - $hmac = substr($session, $len); - $session = substr($session, 0, $len); - - // Time-attack-safe comparison - $hmac_check = hash_hmac('sha1', $session, $this->encryption_key); - $diff = 0; - - for ($i = 0; $i < 40; $i++) - { - $xor = ord($hmac[$i]) ^ ord($hmac_check[$i]); - $diff |= $xor; - } - - if ($diff !== 0) - { - log_message('error', 'Session: HMAC mismatch. The session cookie data did not match what was expected.'); - $this->sess_destroy(); - return FALSE; - } - - // Decrypt the cookie data - if ($this->sess_encrypt_cookie == TRUE) - { - $session = $this->CI->encrypt->decode($session); - } - - // Unserialize the session array - $session = $this->_unserialize($session); - - // Is the session data we unserialized an array with the correct format? - if ( ! is_array($session) OR ! isset($session['session_id']) OR ! isset($session['ip_address']) OR ! isset($session['user_agent']) OR ! isset($session['last_activity'])) - { - $this->sess_destroy(); - return FALSE; - } - - // Is the session current? - if (($session['last_activity'] + $this->sess_expiration) < $this->now) - { - $this->sess_destroy(); - return FALSE; - } - - // Does the IP Match? - if ($this->sess_match_ip == TRUE AND $session['ip_address'] != $this->CI->input->ip_address()) - { - $this->sess_destroy(); - return FALSE; - } - - // Does the User Agent Match? - if ($this->sess_match_useragent == TRUE AND trim($session['user_agent']) != trim(substr($this->CI->input->user_agent(), 0, 120))) - { - $this->sess_destroy(); - return FALSE; - } - - // Is there a corresponding session in the DB? - if ($this->sess_use_database === TRUE) - { - $this->CI->db->where('session_id', $session['session_id']); - - if ($this->sess_match_ip == TRUE) - { - $this->CI->db->where('ip_address', $session['ip_address']); - } - - if ($this->sess_match_useragent == TRUE) - { - $this->CI->db->where('user_agent', $session['user_agent']); - } - - $query = $this->CI->db->get($this->sess_table_name); - - // No result? Kill it! - if ($query->num_rows() == 0) - { - $this->sess_destroy(); - return FALSE; - } - - // Is there custom data? If so, add it to the main session array - $row = $query->row(); - if (isset($row->user_data) AND $row->user_data != '') - { - $custom_data = $this->_unserialize($row->user_data); - - if (is_array($custom_data)) - { - foreach ($custom_data as $key => $val) - { - $session[$key] = $val; - } - } - } - } - - // Session is valid! - $this->userdata = $session; - unset($session); - - return TRUE; - } - - // -------------------------------------------------------------------- - - /** - * Write the session data - * - * @access public - * @return void - */ - function sess_write() - { - // Are we saving custom data to the DB? If not, all we do is update the cookie - if ($this->sess_use_database === FALSE) - { - $this->_set_cookie(); - return; - } - - // set the custom userdata, the session data we will set in a second - $custom_userdata = $this->userdata; - $cookie_userdata = array(); - - // Before continuing, we need to determine if there is any custom data to deal with. - // Let's determine this by removing the default indexes to see if there's anything left in the array - // and set the session data while we're at it - foreach (array('session_id','ip_address','user_agent','last_activity') as $val) - { - unset($custom_userdata[$val]); - $cookie_userdata[$val] = $this->userdata[$val]; - } - - // Did we find any custom data? If not, we turn the empty array into a string - // since there's no reason to serialize and store an empty array in the DB - if (count($custom_userdata) === 0) - { - $custom_userdata = ''; - } - else - { - // Serialize the custom data array so we can store it - $custom_userdata = $this->_serialize($custom_userdata); - } - - // Run the update query - $this->CI->db->where('session_id', $this->userdata['session_id']); - $this->CI->db->update($this->sess_table_name, array('last_activity' => $this->userdata['last_activity'], 'user_data' => $custom_userdata)); - - // Write the cookie. Notice that we manually pass the cookie data array to the - // _set_cookie() function. Normally that function will store $this->userdata, but - // in this case that array contains custom data, which we do not want in the cookie. - $this->_set_cookie($cookie_userdata); - } - - // -------------------------------------------------------------------- - - /** - * Create a new session - * - * @access public - * @return void - */ - function sess_create() - { - $sessid = ''; - while (strlen($sessid) < 32) - { - $sessid .= mt_rand(0, mt_getrandmax()); - } - - // To make the session ID even more secure we'll combine it with the user's IP - $sessid .= $this->CI->input->ip_address(); - - $this->userdata = array( - 'session_id' => md5(uniqid($sessid, TRUE)), - 'ip_address' => $this->CI->input->ip_address(), - 'user_agent' => substr($this->CI->input->user_agent(), 0, 120), - 'last_activity' => $this->now, - 'user_data' => '' - ); - - - // Save the data to the DB if needed - if ($this->sess_use_database === TRUE) - { - $this->CI->db->query($this->CI->db->insert_string($this->sess_table_name, $this->userdata)); - } - - // Write the cookie - $this->_set_cookie(); - } - - // -------------------------------------------------------------------- - - /** - * Update an existing session - * - * @access public - * @return void - */ - function sess_update() - { - // We only update the session every five minutes by default - if ($this->CI->input->is_ajax_request() OR ($this->userdata['last_activity'] + $this->sess_time_to_update) >= $this->now) - { - return; - } - - // Save the old session id so we know which record to - // update in the database if we need it - $old_sessid = $this->userdata['session_id']; - $new_sessid = ''; - while (strlen($new_sessid) < 32) - { - $new_sessid .= mt_rand(0, mt_getrandmax()); - } - - // To make the session ID even more secure we'll combine it with the user's IP - $new_sessid .= $this->CI->input->ip_address(); - - // Turn it into a hash - $new_sessid = md5(uniqid($new_sessid, TRUE)); - - // Update the session data in the session data array - $this->userdata['session_id'] = $new_sessid; - $this->userdata['last_activity'] = $this->now; - - // _set_cookie() will handle this for us if we aren't using database sessions - // by pushing all userdata to the cookie. - $cookie_data = NULL; - - // Update the session ID and last_activity field in the DB if needed - if ($this->sess_use_database === TRUE) - { - // set cookie explicitly to only have our session data - $cookie_data = array(); - foreach (array('session_id','ip_address','user_agent','last_activity') as $val) - { - $cookie_data[$val] = $this->userdata[$val]; - } - - $this->CI->db->query($this->CI->db->update_string($this->sess_table_name, array('last_activity' => $this->now, 'session_id' => $new_sessid), array('session_id' => $old_sessid))); - } - - // Write the cookie - $this->_set_cookie($cookie_data); - } - - // -------------------------------------------------------------------- - - /** - * Destroy the current session - * - * @access public - * @return void - */ - function sess_destroy() - { - // Kill the session DB row - if ($this->sess_use_database === TRUE && isset($this->userdata['session_id'])) - { - $this->CI->db->where('session_id', $this->userdata['session_id']); - $this->CI->db->delete($this->sess_table_name); - } - - // Kill the cookie - setcookie( - $this->sess_cookie_name, - addslashes(serialize(array())), - ($this->now - 31500000), - $this->cookie_path, - $this->cookie_domain, - 0 - ); - - // Kill session data - $this->userdata = array(); - } - - // -------------------------------------------------------------------- - - /** - * Fetch a specific item from the session array - * - * @access public - * @param string - * @return string - */ - function userdata($item) - { - return ( ! isset($this->userdata[$item])) ? FALSE : $this->userdata[$item]; - } - - // -------------------------------------------------------------------- - - /** - * Fetch all session data - * - * @access public - * @return array - */ - function all_userdata() - { - return $this->userdata; - } - - // -------------------------------------------------------------------- - - /** - * Add or change data in the "userdata" array - * - * @access public - * @param mixed - * @param string - * @return void - */ - function set_userdata($newdata = array(), $newval = '') - { - if (is_string($newdata)) - { - $newdata = array($newdata => $newval); - } - - if (count($newdata) > 0) - { - foreach ($newdata as $key => $val) - { - $this->userdata[$key] = $val; - } - } - - $this->sess_write(); - } - - // -------------------------------------------------------------------- - - /** - * Delete a session variable from the "userdata" array - * - * @access array - * @return void - */ - function unset_userdata($newdata = array()) - { - if (is_string($newdata)) - { - $newdata = array($newdata => ''); - } - - if (count($newdata) > 0) - { - foreach ($newdata as $key => $val) - { - unset($this->userdata[$key]); - } - } - - $this->sess_write(); - } - - // ------------------------------------------------------------------------ - - /** - * Add or change flashdata, only available - * until the next request - * - * @access public - * @param mixed - * @param string - * @return void - */ - function set_flashdata($newdata = array(), $newval = '') - { - if (is_string($newdata)) - { - $newdata = array($newdata => $newval); - } - - if (count($newdata) > 0) - { - foreach ($newdata as $key => $val) - { - $flashdata_key = $this->flashdata_key.':new:'.$key; - $this->set_userdata($flashdata_key, $val); - } - } - } - - // ------------------------------------------------------------------------ - - /** - * Keeps existing flashdata available to next request. - * - * @access public - * @param string - * @return void - */ - function keep_flashdata($key) - { - // 'old' flashdata gets removed. Here we mark all - // flashdata as 'new' to preserve it from _flashdata_sweep() - // Note the function will return FALSE if the $key - // provided cannot be found - $old_flashdata_key = $this->flashdata_key.':old:'.$key; - $value = $this->userdata($old_flashdata_key); - - $new_flashdata_key = $this->flashdata_key.':new:'.$key; - $this->set_userdata($new_flashdata_key, $value); - } - - // ------------------------------------------------------------------------ - - /** - * Fetch a specific flashdata item from the session array - * - * @access public - * @param string - * @return string - */ - function flashdata($key) - { - $flashdata_key = $this->flashdata_key.':old:'.$key; - return $this->userdata($flashdata_key); - } - - // ------------------------------------------------------------------------ - - /** - * Identifies flashdata as 'old' for removal - * when _flashdata_sweep() runs. - * - * @access private - * @return void - */ - function _flashdata_mark() - { - $userdata = $this->all_userdata(); - foreach ($userdata as $name => $value) - { - $parts = explode(':new:', $name); - if (is_array($parts) && count($parts) === 2) - { - $new_name = $this->flashdata_key.':old:'.$parts[1]; - $this->set_userdata($new_name, $value); - $this->unset_userdata($name); - } - } - } - - // ------------------------------------------------------------------------ - - /** - * Removes all flashdata marked as 'old' - * - * @access private - * @return void - */ - - function _flashdata_sweep() - { - $userdata = $this->all_userdata(); - foreach ($userdata as $key => $value) - { - if (strpos($key, ':old:')) - { - $this->unset_userdata($key); - } - } - - } - - // -------------------------------------------------------------------- - - /** - * Get the "now" time - * - * @access private - * @return string - */ - function _get_time() - { - if (strtolower($this->time_reference) == 'gmt') - { - $now = time(); - $time = mktime(gmdate("H", $now), gmdate("i", $now), gmdate("s", $now), gmdate("m", $now), gmdate("d", $now), gmdate("Y", $now)); - } - else - { - $time = time(); - } - - return $time; - } - - // -------------------------------------------------------------------- - - /** - * Write the session cookie - * - * @access public - * @return void - */ - function _set_cookie($cookie_data = NULL) - { - if (is_null($cookie_data)) - { - $cookie_data = $this->userdata; - } - - // Serialize the userdata for the cookie - $cookie_data = $this->_serialize($cookie_data); - - if ($this->sess_encrypt_cookie == TRUE) - { - $cookie_data = $this->CI->encrypt->encode($cookie_data); - } - - $cookie_data .= hash_hmac('sha1', $cookie_data, $this->encryption_key); - - $expire = ($this->sess_expire_on_close === TRUE) ? 0 : $this->sess_expiration + time(); - - // Set the cookie - setcookie( - $this->sess_cookie_name, - $cookie_data, - $expire, - $this->cookie_path, - $this->cookie_domain, - $this->cookie_secure - ); - } - - // -------------------------------------------------------------------- - - /** - * Serialize an array - * - * This function first converts any slashes found in the array to a temporary - * marker, so when it gets unserialized the slashes will be preserved - * - * @access private - * @param array - * @return string - */ - function _serialize($data) - { - if (is_array($data)) - { - foreach ($data as $key => $val) - { - if (is_string($val)) - { - $data[$key] = str_replace('\\', '{{slash}}', $val); - } - } - } - else - { - if (is_string($data)) - { - $data = str_replace('\\', '{{slash}}', $data); - } - } - - return serialize($data); - } - - // -------------------------------------------------------------------- - - /** - * Unserialize - * - * This function unserializes a data string, then converts any - * temporary slash markers back to actual slashes - * - * @access private - * @param array - * @return string - */ - function _unserialize($data) - { - $data = @unserialize(strip_slashes($data)); - - if (is_array($data)) - { - foreach ($data as $key => $val) - { - if (is_string($val)) - { - $data[$key] = str_replace('{{slash}}', '\\', $val); - } - } - - return $data; - } - - return (is_string($data)) ? str_replace('{{slash}}', '\\', $data) : $data; - } - - // -------------------------------------------------------------------- - - /** - * Garbage collection - * - * This deletes expired session rows from database - * if the probability percentage is met - * - * @access public - * @return void - */ - function _sess_gc() - { - if ($this->sess_use_database != TRUE) - { - return; - } - - srand(time()); - if ((rand() % 100) < $this->gc_probability) - { - $expire = $this->now - $this->sess_expiration; - - $this->CI->db->where("last_activity < {$expire}"); - $this->CI->db->delete($this->sess_table_name); - - log_message('debug', 'Session garbage collection performed.'); - } - } - - -} -// END Session Class - -/* End of file Session.php */ -/* Location: ./system/libraries/Session.php */ diff --git a/src/system/libraries/Session/Session.php b/src/system/libraries/Session/Session.php new file mode 100644 index 00000000..c9d2e8ad --- /dev/null +++ b/src/system/libraries/Session/Session.php @@ -0,0 +1,908 @@ +_driver = $params['driver']; + unset($params['driver']); + } + elseif ($driver = config_item('sess_driver')) + { + $this->_driver = $driver; + } + // Note: BC workaround + elseif (config_item('sess_use_database')) + { + $this->_driver = 'database'; + } + + $class = $this->_ci_load_classes($this->_driver); + + // Configuration ... + $this->_configure($params); + + $class = new $class($this->_config); + if ($class instanceof SessionHandlerInterface) + { + if (is_php('5.4')) + { + session_set_save_handler($class, TRUE); + } + else + { + session_set_save_handler( + array($class, 'open'), + array($class, 'close'), + array($class, 'read'), + array($class, 'write'), + array($class, 'destroy'), + array($class, 'gc') + ); + + register_shutdown_function('session_write_close'); + } + } + else + { + log_message('error', "Session: Driver '".$this->_driver."' doesn't implement SessionHandlerInterface. Aborting."); + return; + } + + // Sanitize the cookie, because apparently PHP doesn't do that for userspace handlers + if (isset($_COOKIE[$this->_config['cookie_name']]) + && ( + ! is_string($_COOKIE[$this->_config['cookie_name']]) + OR ! preg_match('/^[0-9a-f]{40}$/', $_COOKIE[$this->_config['cookie_name']]) + ) + ) + { + unset($_COOKIE[$this->_config['cookie_name']]); + } + + session_start(); + + // Is session ID auto-regeneration configured? (ignoring ajax requests) + if ((empty($_SERVER['HTTP_X_REQUESTED_WITH']) OR strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) !== 'xmlhttprequest') + && ($regenerate_time = config_item('sess_time_to_update')) > 0 + ) + { + if ( ! isset($_SESSION['__ci_last_regenerate'])) + { + $_SESSION['__ci_last_regenerate'] = time(); + } + elseif ($_SESSION['__ci_last_regenerate'] < (time() - $regenerate_time)) + { + $this->sess_regenerate((bool) config_item('sess_regenerate_destroy')); + } + } + // Another work-around ... PHP doesn't seem to send the session cookie + // unless it is being currently created or regenerated + elseif (isset($_COOKIE[$this->_config['cookie_name']]) && $_COOKIE[$this->_config['cookie_name']] === session_id()) + { + setcookie( + $this->_config['cookie_name'], + session_id(), + (empty($this->_config['cookie_lifetime']) ? 0 : time() + $this->_config['cookie_lifetime']), + $this->_config['cookie_path'], + $this->_config['cookie_domain'], + $this->_config['cookie_secure'], + TRUE + ); + } + + $this->_ci_init_vars(); + + log_message('info', "Session: Class initialized using '".$this->_driver."' driver."); + } + + // ------------------------------------------------------------------------ + + /** + * CI Load Classes + * + * An internal method to load all possible dependency and extension + * classes. It kind of emulates the CI_Driver library, but is + * self-sufficient. + * + * @param string $driver Driver name + * @return string Driver class name + */ + protected function _ci_load_classes($driver) + { + // PHP 5.4 compatibility + interface_exists('SessionHandlerInterface', FALSE) OR require_once(BASEPATH.'libraries/Session/SessionHandlerInterface.php'); + + $prefix = config_item('subclass_prefix'); + + if ( ! class_exists('CI_Session_driver', FALSE)) + { + require_once( + file_exists(APPPATH.'libraries/Session/Session_driver.php') + ? APPPATH.'libraries/Session/Session_driver.php' + : BASEPATH.'libraries/Session/Session_driver.php' + ); + + if (file_exists($file_path = APPPATH.'libraries/Session/'.$prefix.'Session_driver.php')) + { + require_once($file_path); + } + } + + $class = 'Session_'.$driver.'_driver'; + + // Allow custom drivers without the CI_ or MY_ prefix + if ( ! class_exists($class, FALSE) && file_exists($file_path = APPPATH.'libraries/Session/drivers/'.$class.'.php')) + { + require_once($file_path); + if (class_exists($class, FALSE)) + { + return $class; + } + } + + if ( ! class_exists('CI_'.$class, FALSE)) + { + if (file_exists($file_path = APPPATH.'libraries/Session/drivers/'.$class.'.php') OR file_exists($file_path = BASEPATH.'libraries/Session/drivers/'.$class.'.php')) + { + require_once($file_path); + } + + if ( ! class_exists('CI_'.$class, FALSE) && ! class_exists($class, FALSE)) + { + throw new UnexpectedValueException("Session: Configured driver '".$driver."' was not found. Aborting."); + } + } + + if ( ! class_exists($prefix.$class, FALSE) && file_exists($file_path = APPPATH.'libraries/Session/drivers/'.$prefix.$class.'.php')) + { + require_once($file_path); + if (class_exists($prefix.$class, FALSE)) + { + return $prefix.$class; + } + else + { + log_message('debug', 'Session: '.$prefix.$class.".php found but it doesn't declare class ".$prefix.$class.'.'); + } + } + + return 'CI_'.$class; + } + + // ------------------------------------------------------------------------ + + /** + * Configuration + * + * Handle input parameters and configuration defaults + * + * @param array &$params Input parameters + * @return void + */ + protected function _configure(&$params) + { + $expiration = config_item('sess_expiration'); + + if (isset($params['cookie_lifetime'])) + { + $params['cookie_lifetime'] = (int) $params['cookie_lifetime']; + } + else + { + $params['cookie_lifetime'] = ( ! isset($expiration) && config_item('sess_expire_on_close')) + ? 0 : (int) $expiration; + } + + isset($params['cookie_name']) OR $params['cookie_name'] = config_item('sess_cookie_name'); + if (empty($params['cookie_name'])) + { + $params['cookie_name'] = ini_get('session.name'); + } + else + { + ini_set('session.name', $params['cookie_name']); + } + + isset($params['cookie_path']) OR $params['cookie_path'] = config_item('cookie_path'); + isset($params['cookie_domain']) OR $params['cookie_domain'] = config_item('cookie_domain'); + isset($params['cookie_secure']) OR $params['cookie_secure'] = (bool) config_item('cookie_secure'); + + session_set_cookie_params( + $params['cookie_lifetime'], + $params['cookie_path'], + $params['cookie_domain'], + $params['cookie_secure'], + TRUE // HttpOnly; Yes, this is intentional and not configurable for security reasons + ); + + if (empty($expiration)) + { + $params['expiration'] = (int) ini_get('session.gc_maxlifetime'); + } + else + { + $params['expiration'] = (int) $expiration; + ini_set('session.gc_maxlifetime', $expiration); + } + + $params['match_ip'] = (bool) (isset($params['match_ip']) ? $params['match_ip'] : config_item('sess_match_ip')); + + isset($params['save_path']) OR $params['save_path'] = config_item('sess_save_path'); + + $this->_config = $params; + + // Security is king + ini_set('session.use_trans_sid', 0); + ini_set('session.use_strict_mode', 1); + ini_set('session.use_cookies', 1); + ini_set('session.use_only_cookies', 1); + ini_set('session.hash_function', 1); + ini_set('session.hash_bits_per_character', 4); + } + + // ------------------------------------------------------------------------ + + /** + * Handle temporary variables + * + * Clears old "flash" data, marks the new one for deletion and handles + * "temp" data deletion. + * + * @return void + */ + protected function _ci_init_vars() + { + if ( ! empty($_SESSION['__ci_vars'])) + { + $current_time = time(); + + foreach ($_SESSION['__ci_vars'] as $key => &$value) + { + if ($value === 'new') + { + $_SESSION['__ci_vars'][$key] = 'old'; + } + // Hacky, but 'old' will (implicitly) always be less than time() ;) + // DO NOT move this above the 'new' check! + elseif ($value < $current_time) + { + unset($_SESSION[$key], $_SESSION['__ci_vars'][$key]); + } + } + + if (empty($_SESSION['__ci_vars'])) + { + unset($_SESSION['__ci_vars']); + } + } + + $this->userdata =& $_SESSION; + } + + // ------------------------------------------------------------------------ + + /** + * Mark as flash + * + * @param mixed $key Session data key(s) + * @return bool + */ + public function mark_as_flash($key) + { + if (is_array($key)) + { + for ($i = 0, $c = count($key); $i < $c; $i++) + { + if ( ! isset($_SESSION[$key[$i]])) + { + return FALSE; + } + } + + $new = array_fill_keys($key, 'new'); + + $_SESSION['__ci_vars'] = isset($_SESSION['__ci_vars']) + ? array_merge($_SESSION['__ci_vars'], $new) + : $new; + + return TRUE; + } + + if ( ! isset($_SESSION[$key])) + { + return FALSE; + } + + $_SESSION['__ci_vars'][$key] = 'new'; + return TRUE; + } + + // ------------------------------------------------------------------------ + + /** + * Get flash keys + * + * @return array + */ + public function get_flash_keys() + { + if ( ! isset($_SESSION['__ci_vars'])) + { + return array(); + } + + $keys = array(); + foreach (array_keys($_SESSION['__ci_vars']) as $key) + { + is_int($_SESSION['__ci_vars'][$key]) OR $keys[] = $key; + } + + return $keys; + } + + // ------------------------------------------------------------------------ + + /** + * Unmark flash + * + * @param mixed $key Session data key(s) + * @return void + */ + public function unmark_flash($key) + { + if (empty($_SESSION['__ci_vars'])) + { + return; + } + + is_array($key) OR $key = array($key); + + foreach ($key as $k) + { + if (isset($_SESSION['__ci_vars'][$k]) && ! is_int($_SESSION['__ci_vars'][$k])) + { + unset($_SESSION['__ci_vars'][$k]); + } + } + + if (empty($_SESSION['__ci_vars'])) + { + unset($_SESSION['__ci_vars']); + } + } + + // ------------------------------------------------------------------------ + + /** + * Mark as temp + * + * @param mixed $key Session data key(s) + * @param int $ttl Time-to-live in seconds + * @return bool + */ + public function mark_as_temp($key, $ttl = 300) + { + $ttl += time(); + + if (is_array($key)) + { + $temp = array(); + + foreach ($key as $k => $v) + { + // Do we have a key => ttl pair, or just a key? + if (is_int($k)) + { + $k = $v; + $v = $ttl; + } + else + { + $v += time(); + } + + if ( ! isset($_SESSION[$k])) + { + return FALSE; + } + + $temp[$k] = $v; + } + + $_SESSION['__ci_vars'] = isset($_SESSION['__ci_vars']) + ? array_merge($_SESSION['__ci_vars'], $temp) + : $temp; + + return TRUE; + } + + if ( ! isset($_SESSION[$key])) + { + return FALSE; + } + + $_SESSION['__ci_vars'][$key] = $ttl; + return TRUE; + } + + // ------------------------------------------------------------------------ + + /** + * Get temp keys + * + * @return array + */ + public function get_temp_keys() + { + if ( ! isset($_SESSION['__ci_vars'])) + { + return array(); + } + + $keys = array(); + foreach (array_keys($_SESSION['__ci_vars']) as $key) + { + is_int($_SESSION['__ci_vars'][$key]) && $keys[] = $key; + } + + return $keys; + } + + // ------------------------------------------------------------------------ + + /** + * Unmark flash + * + * @param mixed $key Session data key(s) + * @return void + */ + public function unmark_temp($key) + { + if (empty($_SESSION['__ci_vars'])) + { + return; + } + + is_array($key) OR $key = array($key); + + foreach ($key as $k) + { + if (isset($_SESSION['__ci_vars'][$k]) && is_int($_SESSION['__ci_vars'][$k])) + { + unset($_SESSION['__ci_vars'][$k]); + } + } + + if (empty($_SESSION['__ci_vars'])) + { + unset($_SESSION['__ci_vars']); + } + } + + // ------------------------------------------------------------------------ + + /** + * __get() + * + * @param string $key 'session_id' or a session data key + * @return mixed + */ + public function __get($key) + { + // Note: Keep this order the same, just in case somebody wants to + // use 'session_id' as a session data key, for whatever reason + if (isset($_SESSION[$key])) + { + return $_SESSION[$key]; + } + elseif ($key === 'session_id') + { + return session_id(); + } + + return NULL; + } + + // ------------------------------------------------------------------------ + + /** + * __isset() + * + * @param string $key 'session_id' or a session data key + * @return bool + */ + public function __isset($key) + { + if ($key === 'session_id') + { + return (session_status() === PHP_SESSION_ACTIVE); + } + + return isset($_SESSION[$key]); + } + + // ------------------------------------------------------------------------ + + /** + * __set() + * + * @param string $key Session data key + * @param mixed $value Session data value + * @return void + */ + public function __set($key, $value) + { + $_SESSION[$key] = $value; + } + + // ------------------------------------------------------------------------ + + /** + * Session destroy + * + * Legacy CI_Session compatibility method + * + * @return void + */ + public function sess_destroy() + { + session_destroy(); + } + + // ------------------------------------------------------------------------ + + /** + * Session regenerate + * + * Legacy CI_Session compatibility method + * + * @param bool $destroy Destroy old session data flag + * @return void + */ + public function sess_regenerate($destroy = FALSE) + { + $_SESSION['__ci_last_regenerate'] = time(); + session_regenerate_id($destroy); + } + + // ------------------------------------------------------------------------ + + /** + * Get userdata reference + * + * Legacy CI_Session compatibility method + * + * @returns array + */ + public function &get_userdata() + { + return $_SESSION; + } + + // ------------------------------------------------------------------------ + + /** + * Userdata (fetch) + * + * Legacy CI_Session compatibility method + * + * @param string $key Session data key + * @return mixed Session data value or NULL if not found + */ + public function userdata($key = NULL) + { + if (isset($key)) + { + return isset($_SESSION[$key]) ? $_SESSION[$key] : NULL; + } + elseif (empty($_SESSION)) + { + return array(); + } + + $userdata = array(); + $_exclude = array_merge( + array('__ci_vars'), + $this->get_flash_keys(), + $this->get_temp_keys() + ); + + foreach (array_keys($_SESSION) as $key) + { + if ( ! in_array($key, $_exclude, TRUE)) + { + $userdata[$key] = $_SESSION[$key]; + } + } + + return $userdata; + } + + // ------------------------------------------------------------------------ + + /** + * Set userdata + * + * Legacy CI_Session compatibility method + * + * @param mixed $data Session data key or an associative array + * @param mixed $value Value to store + * @return void + */ + public function set_userdata($data, $value = NULL) + { + if (is_array($data)) + { + foreach ($data as $key => &$value) + { + $_SESSION[$key] = $value; + } + + return; + } + + $_SESSION[$data] = $value; + } + + // ------------------------------------------------------------------------ + + /** + * Unset userdata + * + * Legacy CI_Session compatibility method + * + * @param mixed $data Session data key(s) + * @return void + */ + public function unset_userdata($key) + { + if (is_array($key)) + { + foreach ($key as $k) + { + unset($_SESSION[$k]); + } + + return; + } + + unset($_SESSION[$key]); + } + + // ------------------------------------------------------------------------ + + /** + * All userdata (fetch) + * + * Legacy CI_Session compatibility method + * + * @return array $_SESSION, excluding flash data items + */ + public function all_userdata() + { + return $this->userdata(); + } + + // ------------------------------------------------------------------------ + + /** + * Has userdata + * + * Legacy CI_Session compatibility method + * + * @param string $key Session data key + * @return bool + */ + public function has_userdata($key) + { + return isset($_SESSION[$key]); + } + + // ------------------------------------------------------------------------ + + /** + * Flashdata (fetch) + * + * Legacy CI_Session compatibility method + * + * @param string $key Session data key + * @return mixed Session data value or NULL if not found + */ + public function flashdata($key = NULL) + { + if (isset($key)) + { + return (isset($_SESSION['__ci_vars'], $_SESSION['__ci_vars'][$key], $_SESSION[$key]) && ! is_int($_SESSION['__ci_vars'][$key])) + ? $_SESSION[$key] + : NULL; + } + + $flashdata = array(); + + if ( ! empty($_SESSION['__ci_vars'])) + { + foreach ($_SESSION['__ci_vars'] as $key => &$value) + { + is_int($value) OR $flashdata[$key] = $_SESSION[$key]; + } + } + + return $flashdata; + } + + // ------------------------------------------------------------------------ + + /** + * Set flashdata + * + * Legacy CI_Session compatibility method + * + * @param mixed $data Session data key or an associative array + * @param mixed $value Value to store + * @return void + */ + public function set_flashdata($data, $value = NULL) + { + $this->set_userdata($data, $value); + $this->mark_as_flash(is_array($data) ? array_keys($data) : $data); + } + + // ------------------------------------------------------------------------ + + /** + * Keep flashdata + * + * Legacy CI_Session compatibility method + * + * @param mixed $key Session data key(s) + * @return void + */ + public function keep_flashdata($key) + { + $this->mark_as_flash($key); + } + + // ------------------------------------------------------------------------ + + /** + * Temp data (fetch) + * + * Legacy CI_Session compatibility method + * + * @param string $key Session data key + * @return mixed Session data value or NULL if not found + */ + public function tempdata($key = NULL) + { + if (isset($key)) + { + return (isset($_SESSION['__ci_vars'], $_SESSION['__ci_vars'][$key], $_SESSION[$key]) && is_int($_SESSION['__ci_vars'][$key])) + ? $_SESSION[$key] + : NULL; + } + + $tempdata = array(); + + if ( ! empty($_SESSION['__ci_vars'])) + { + foreach ($_SESSION['__ci_vars'] as $key => &$value) + { + is_int($value) && $tempdata[$key] = $_SESSION[$key]; + } + } + + return $tempdata; + } + + // ------------------------------------------------------------------------ + + /** + * Set tempdata + * + * Legacy CI_Session compatibility method + * + * @param mixed $data Session data key or an associative array of items + * @param mixed $value Value to store + * @param int $ttl Time-to-live in seconds + * @return void + */ + public function set_tempdata($data, $value = NULL, $ttl = 300) + { + $this->set_userdata($data, $value); + $this->mark_as_temp(is_array($data) ? array_keys($data) : $data, $ttl); + } + + // ------------------------------------------------------------------------ + + /** + * Unset tempdata + * + * Legacy CI_Session compatibility method + * + * @param mixed $data Session data key(s) + * @return void + */ + public function unset_tempdata($key) + { + $this->unmark_temp($key); + } + +} diff --git a/src/system/libraries/Session/SessionHandlerInterface.php b/src/system/libraries/Session/SessionHandlerInterface.php new file mode 100644 index 00000000..b3533dd1 --- /dev/null +++ b/src/system/libraries/Session/SessionHandlerInterface.php @@ -0,0 +1,59 @@ +_config =& $params; + + if (is_php('7')) + { + $this->_success = TRUE; + $this->_failure = FALSE; + } + else + { + $this->_success = 0; + $this->_failure = -1; + } + } + + // ------------------------------------------------------------------------ + + /** + * Cookie destroy + * + * Internal method to force removal of a cookie by the client + * when session_destroy() is called. + * + * @return bool + */ + protected function _cookie_destroy() + { + return setcookie( + $this->_config['cookie_name'], + NULL, + 1, + $this->_config['cookie_path'], + $this->_config['cookie_domain'], + $this->_config['cookie_secure'], + TRUE + ); + } + + // ------------------------------------------------------------------------ + + /** + * Get lock + * + * A dummy method allowing drivers with no locking functionality + * (databases other than PostgreSQL and MySQL) to act as if they + * do acquire a lock. + * + * @param string $session_id + * @return bool + */ + protected function _get_lock($session_id) + { + $this->_lock = TRUE; + return TRUE; + } + + // ------------------------------------------------------------------------ + + /** + * Release lock + * + * @return bool + */ + protected function _release_lock() + { + if ($this->_lock) + { + $this->_lock = FALSE; + } + + return TRUE; + } + + // ------------------------------------------------------------------------ + + /** + * Fail + * + * Drivers other than the 'files' one don't (need to) use the + * session.save_path INI setting, but that leads to confusing + * error messages emitted by PHP when open() or write() fail, + * as the message contains session.save_path ... + * To work around the problem, the drivers will call this method + * so that the INI is set just in time for the error message to + * be properly generated. + * + * @return mixed + */ + protected function _fail() + { + ini_set('session.save_path', config_item('sess_save_path')); + return $this->_failure; + } +} diff --git a/src/system/libraries/Session/drivers/Session_database_driver.php b/src/system/libraries/Session/drivers/Session_database_driver.php new file mode 100644 index 00000000..317bd7d4 --- /dev/null +++ b/src/system/libraries/Session/drivers/Session_database_driver.php @@ -0,0 +1,417 @@ +db) OR $CI->load->database(); + $this->_db = $CI->db; + + if ( ! $this->_db instanceof CI_DB_query_builder) + { + throw new Exception('Query Builder not enabled for the configured database. Aborting.'); + } + elseif ($this->_db->pconnect) + { + throw new Exception('Configured database connection is persistent. Aborting.'); + } + elseif ($this->_db->cache_on) + { + throw new Exception('Configured database connection has cache enabled. Aborting.'); + } + + $db_driver = $this->_db->dbdriver.(empty($this->_db->subdriver) ? '' : '_'.$this->_db->subdriver); + if (strpos($db_driver, 'mysql') !== FALSE) + { + $this->_platform = 'mysql'; + } + elseif (in_array($db_driver, array('postgre', 'pdo_pgsql'), TRUE)) + { + $this->_platform = 'postgre'; + } + + // Note: BC work-around for the old 'sess_table_name' setting, should be removed in the future. + isset($this->_config['save_path']) OR $this->_config['save_path'] = config_item('sess_table_name'); + } + + // ------------------------------------------------------------------------ + + /** + * Open + * + * Initializes the database connection + * + * @param string $save_path Table name + * @param string $name Session cookie name, unused + * @return bool + */ + public function open($save_path, $name) + { + if (empty($this->_db->conn_id) && ! $this->_db->db_connect()) + { + return $this->_fail(); + } + + return $this->_success; + } + + // ------------------------------------------------------------------------ + + /** + * Read + * + * Reads session data and acquires a lock + * + * @param string $session_id Session ID + * @return string Serialized session data + */ + public function read($session_id) + { + if ($this->_get_lock($session_id) !== FALSE) + { + // Prevent previous QB calls from messing with our queries + $this->_db->reset_query(); + + // Needed by write() to detect session_regenerate_id() calls + $this->_session_id = $session_id; + + $this->_db + ->select('data') + ->from($this->_config['save_path']) + ->where('id', $session_id); + + if ($this->_config['match_ip']) + { + $this->_db->where('ip_address', $_SERVER['REMOTE_ADDR']); + } + + if ( ! ($result = $this->_db->get()) OR ($result = $result->row()) === NULL) + { + // PHP7 will reuse the same SessionHandler object after + // ID regeneration, so we need to explicitly set this to + // FALSE instead of relying on the default ... + $this->_row_exists = FALSE; + $this->_fingerprint = md5(''); + return ''; + } + + // PostgreSQL's variant of a BLOB datatype is Bytea, which is a + // PITA to work with, so we use base64-encoded data in a TEXT + // field instead. + $result = ($this->_platform === 'postgre') + ? base64_decode(rtrim($result->data)) + : $result->data; + + $this->_fingerprint = md5($result); + $this->_row_exists = TRUE; + return $result; + } + + $this->_fingerprint = md5(''); + return ''; + } + + // ------------------------------------------------------------------------ + + /** + * Write + * + * Writes (create / update) session data + * + * @param string $session_id Session ID + * @param string $session_data Serialized session data + * @return bool + */ + public function write($session_id, $session_data) + { + // Prevent previous QB calls from messing with our queries + $this->_db->reset_query(); + + // Was the ID regenerated? + if ($session_id !== $this->_session_id) + { + if ( ! $this->_release_lock() OR ! $this->_get_lock($session_id)) + { + return $this->_fail(); + } + + $this->_row_exists = FALSE; + $this->_session_id = $session_id; + } + elseif ($this->_lock === FALSE) + { + return $this->_fail(); + } + + if ($this->_row_exists === FALSE) + { + $insert_data = array( + 'id' => $session_id, + 'ip_address' => $_SERVER['REMOTE_ADDR'], + 'timestamp' => time(), + 'data' => ($this->_platform === 'postgre' ? base64_encode($session_data) : $session_data) + ); + + if ($this->_db->insert($this->_config['save_path'], $insert_data)) + { + $this->_fingerprint = md5($session_data); + $this->_row_exists = TRUE; + return $this->_success; + } + + return $this->_fail(); + } + + $this->_db->where('id', $session_id); + if ($this->_config['match_ip']) + { + $this->_db->where('ip_address', $_SERVER['REMOTE_ADDR']); + } + + $update_data = array('timestamp' => time()); + if ($this->_fingerprint !== md5($session_data)) + { + $update_data['data'] = ($this->_platform === 'postgre') + ? base64_encode($session_data) + : $session_data; + } + + if ($this->_db->update($this->_config['save_path'], $update_data)) + { + $this->_fingerprint = md5($session_data); + return $this->_success; + } + + return $this->_fail(); + } + + // ------------------------------------------------------------------------ + + /** + * Close + * + * Releases locks + * + * @return bool + */ + public function close() + { + return ($this->_lock && ! $this->_release_lock()) + ? $this->_fail() + : $this->_success; + } + + // ------------------------------------------------------------------------ + + /** + * Destroy + * + * Destroys the current session. + * + * @param string $session_id Session ID + * @return bool + */ + public function destroy($session_id) + { + if ($this->_lock) + { + // Prevent previous QB calls from messing with our queries + $this->_db->reset_query(); + + $this->_db->where('id', $session_id); + if ($this->_config['match_ip']) + { + $this->_db->where('ip_address', $_SERVER['REMOTE_ADDR']); + } + + if ( ! $this->_db->delete($this->_config['save_path'])) + { + return $this->_fail(); + } + } + + if ($this->close() === $this->_success) + { + $this->_cookie_destroy(); + return $this->_success; + } + + return $this->_fail(); + } + + // ------------------------------------------------------------------------ + + /** + * Garbage Collector + * + * Deletes expired sessions + * + * @param int $maxlifetime Maximum lifetime of sessions + * @return bool + */ + public function gc($maxlifetime) + { + // Prevent previous QB calls from messing with our queries + $this->_db->reset_query(); + + return ($this->_db->delete($this->_config['save_path'], 'timestamp < '.(time() - $maxlifetime))) + ? $this->_success + : $this->_fail(); + } + + // ------------------------------------------------------------------------ + + /** + * Get lock + * + * Acquires a lock, depending on the underlying platform. + * + * @param string $session_id Session ID + * @return bool + */ + protected function _get_lock($session_id) + { + if ($this->_platform === 'mysql') + { + $arg = $session_id.($this->_config['match_ip'] ? '_'.$_SERVER['REMOTE_ADDR'] : ''); + if ($this->_db->query("SELECT GET_LOCK('".$arg."', 300) AS ci_session_lock")->row()->ci_session_lock) + { + $this->_lock = $arg; + return TRUE; + } + + return FALSE; + } + elseif ($this->_platform === 'postgre') + { + $arg = "hashtext('".$session_id."')".($this->_config['match_ip'] ? ", hashtext('".$_SERVER['REMOTE_ADDR']."')" : ''); + if ($this->_db->simple_query('SELECT pg_advisory_lock('.$arg.')')) + { + $this->_lock = $arg; + return TRUE; + } + + return FALSE; + } + + return parent::_get_lock($session_id); + } + + // ------------------------------------------------------------------------ + + /** + * Release lock + * + * Releases a previously acquired lock + * + * @return bool + */ + protected function _release_lock() + { + if ( ! $this->_lock) + { + return TRUE; + } + + if ($this->_platform === 'mysql') + { + if ($this->_db->query("SELECT RELEASE_LOCK('".$this->_lock."') AS ci_session_lock")->row()->ci_session_lock) + { + $this->_lock = FALSE; + return TRUE; + } + + return FALSE; + } + elseif ($this->_platform === 'postgre') + { + if ($this->_db->simple_query('SELECT pg_advisory_unlock('.$this->_lock.')')) + { + $this->_lock = FALSE; + return TRUE; + } + + return FALSE; + } + + return parent::_release_lock(); + } +} \ No newline at end of file diff --git a/src/system/libraries/Session/drivers/Session_files_driver.php b/src/system/libraries/Session/drivers/Session_files_driver.php new file mode 100644 index 00000000..119bf657 --- /dev/null +++ b/src/system/libraries/Session/drivers/Session_files_driver.php @@ -0,0 +1,379 @@ +_config['save_path'])) + { + $this->_config['save_path'] = rtrim($this->_config['save_path'], '/\\'); + ini_set('session.save_path', $this->_config['save_path']); + } + else + { + $this->_config['save_path'] = rtrim(ini_get('session.save_path'), '/\\'); + } + } + + // ------------------------------------------------------------------------ + + /** + * Open + * + * Sanitizes the save_path directory. + * + * @param string $save_path Path to session files' directory + * @param string $name Session cookie name + * @return bool + */ + public function open($save_path, $name) + { + if ( ! is_dir($save_path)) + { + if ( ! mkdir($save_path, 0700, TRUE)) + { + throw new Exception("Session: Configured save path '".$this->_config['save_path']."' is not a directory, doesn't exist or cannot be created."); + } + } + elseif ( ! is_writable($save_path)) + { + throw new Exception("Session: Configured save path '".$this->_config['save_path']."' is not writable by the PHP process."); + } + + $this->_config['save_path'] = $save_path; + $this->_file_path = $this->_config['save_path'].DIRECTORY_SEPARATOR + .$name // we'll use the session cookie name as a prefix to avoid collisions + .($this->_config['match_ip'] ? md5($_SERVER['REMOTE_ADDR']) : ''); + + return $this->_success; + } + + // ------------------------------------------------------------------------ + + /** + * Read + * + * Reads session data and acquires a lock + * + * @param string $session_id Session ID + * @return string Serialized session data + */ + public function read($session_id) + { + // This might seem weird, but PHP 5.6 introduces session_reset(), + // which re-reads session data + if ($this->_file_handle === NULL) + { + // Just using fopen() with 'c+b' mode would be perfect, but it is only + // available since PHP 5.2.6 and we have to set permissions for new files, + // so we'd have to hack around this ... + if (($this->_file_new = ! file_exists($this->_file_path.$session_id)) === TRUE) + { + if (($this->_file_handle = fopen($this->_file_path.$session_id, 'w+b')) === FALSE) + { + log_message('error', "Session: File '".$this->_file_path.$session_id."' doesn't exist and cannot be created."); + return $this->_failure; + } + } + elseif (($this->_file_handle = fopen($this->_file_path.$session_id, 'r+b')) === FALSE) + { + log_message('error', "Session: Unable to open file '".$this->_file_path.$session_id."'."); + return $this->_failure; + } + + if (flock($this->_file_handle, LOCK_EX) === FALSE) + { + log_message('error', "Session: Unable to obtain lock for file '".$this->_file_path.$session_id."'."); + fclose($this->_file_handle); + $this->_file_handle = NULL; + return $this->_failure; + } + + // Needed by write() to detect session_regenerate_id() calls + $this->_session_id = $session_id; + + if ($this->_file_new) + { + chmod($this->_file_path.$session_id, 0600); + $this->_fingerprint = md5(''); + return ''; + } + } + // We shouldn't need this, but apparently we do ... + // See https://github.com/bcit-ci/CodeIgniter/issues/4039 + elseif ($this->_file_handle === FALSE) + { + return $this->_failure; + } + else + { + rewind($this->_file_handle); + } + + $session_data = ''; + for ($read = 0, $length = filesize($this->_file_path.$session_id); $read < $length; $read += strlen($buffer)) + { + if (($buffer = fread($this->_file_handle, $length - $read)) === FALSE) + { + break; + } + + $session_data .= $buffer; + } + + $this->_fingerprint = md5($session_data); + return $session_data; + } + + // ------------------------------------------------------------------------ + + /** + * Write + * + * Writes (create / update) session data + * + * @param string $session_id Session ID + * @param string $session_data Serialized session data + * @return bool + */ + public function write($session_id, $session_data) + { + // If the two IDs don't match, we have a session_regenerate_id() call + // and we need to close the old handle and open a new one + if ($session_id !== $this->_session_id && ($this->close() === $this->_failure OR $this->read($session_id) === $this->_failure)) + { + return $this->_failure; + } + + if ( ! is_resource($this->_file_handle)) + { + return $this->_failure; + } + elseif ($this->_fingerprint === md5($session_data)) + { + return ( ! $this->_file_new && ! touch($this->_file_path.$session_id)) + ? $this->_failure + : $this->_success; + } + + if ( ! $this->_file_new) + { + ftruncate($this->_file_handle, 0); + rewind($this->_file_handle); + } + + if (($length = strlen($session_data)) > 0) + { + for ($written = 0; $written < $length; $written += $result) + { + if (($result = fwrite($this->_file_handle, substr($session_data, $written))) === FALSE) + { + break; + } + } + + if ( ! is_int($result)) + { + $this->_fingerprint = md5(substr($session_data, 0, $written)); + log_message('error', 'Session: Unable to write data.'); + return $this->_failure; + } + } + + $this->_fingerprint = md5($session_data); + return $this->_success; + } + + // ------------------------------------------------------------------------ + + /** + * Close + * + * Releases locks and closes file descriptor. + * + * @return bool + */ + public function close() + { + if (is_resource($this->_file_handle)) + { + flock($this->_file_handle, LOCK_UN); + fclose($this->_file_handle); + + $this->_file_handle = $this->_file_new = $this->_session_id = NULL; + } + + return $this->_success; + } + + // ------------------------------------------------------------------------ + + /** + * Destroy + * + * Destroys the current session. + * + * @param string $session_id Session ID + * @return bool + */ + public function destroy($session_id) + { + if ($this->close() === $this->_success) + { + if (file_exists($this->_file_path.$session_id)) + { + $this->_cookie_destroy(); + return unlink($this->_file_path.$session_id) + ? $this->_success + : $this->_failure; + } + + return $this->_success; + } + elseif ($this->_file_path !== NULL) + { + clearstatcache(); + if (file_exists($this->_file_path.$session_id)) + { + $this->_cookie_destroy(); + return unlink($this->_file_path.$session_id) + ? $this->_success + : $this->_failure; + } + + return $this->_success; + } + + return $this->_failure; + } + + // ------------------------------------------------------------------------ + + /** + * Garbage Collector + * + * Deletes expired sessions + * + * @param int $maxlifetime Maximum lifetime of sessions + * @return bool + */ + public function gc($maxlifetime) + { + if ( ! is_dir($this->_config['save_path']) OR ($directory = opendir($this->_config['save_path'])) === FALSE) + { + log_message('debug', "Session: Garbage collector couldn't list files under directory '".$this->_config['save_path']."'."); + return $this->_failure; + } + + $ts = time() - $maxlifetime; + + $pattern = sprintf( + '/^%s[0-9a-f]{%d}$/', + preg_quote($this->_config['cookie_name'], '/'), + ($this->_config['match_ip'] === TRUE ? 72 : 40) + ); + + while (($file = readdir($directory)) !== FALSE) + { + // If the filename doesn't match this pattern, it's either not a session file or is not ours + if ( ! preg_match($pattern, $file) + OR ! is_file($this->_config['save_path'].DIRECTORY_SEPARATOR.$file) + OR ($mtime = filemtime($this->_config['save_path'].DIRECTORY_SEPARATOR.$file)) === FALSE + OR $mtime > $ts) + { + continue; + } + + unlink($this->_config['save_path'].DIRECTORY_SEPARATOR.$file); + } + + closedir($directory); + + return $this->_success; + } + +} \ No newline at end of file diff --git a/src/system/libraries/Session/drivers/Session_memcached_driver.php b/src/system/libraries/Session/drivers/Session_memcached_driver.php new file mode 100644 index 00000000..88eb4b3a --- /dev/null +++ b/src/system/libraries/Session/drivers/Session_memcached_driver.php @@ -0,0 +1,382 @@ +_config['save_path'])) + { + log_message('error', 'Session: No Memcached save path configured.'); + } + + if ($this->_config['match_ip'] === TRUE) + { + $this->_key_prefix .= $_SERVER['REMOTE_ADDR'].':'; + } + } + + // ------------------------------------------------------------------------ + + /** + * Open + * + * Sanitizes save_path and initializes connections. + * + * @param string $save_path Server path(s) + * @param string $name Session cookie name, unused + * @return bool + */ + public function open($save_path, $name) + { + $this->_memcached = new Memcached(); + $this->_memcached->setOption(Memcached::OPT_BINARY_PROTOCOL, TRUE); // required for touch() usage + $server_list = array(); + foreach ($this->_memcached->getServerList() as $server) + { + $server_list[] = $server['host'].':'.$server['port']; + } + + if ( ! preg_match_all('#,?([^,:]+)\:(\d{1,5})(?:\:(\d+))?#', $this->_config['save_path'], $matches, PREG_SET_ORDER)) + { + $this->_memcached = NULL; + log_message('error', 'Session: Invalid Memcached save path format: '.$this->_config['save_path']); + return $this->_fail(); + } + + foreach ($matches as $match) + { + // If Memcached already has this server (or if the port is invalid), skip it + if (in_array($match[1].':'.$match[2], $server_list, TRUE)) + { + log_message('debug', 'Session: Memcached server pool already has '.$match[1].':'.$match[2]); + continue; + } + + if ( ! $this->_memcached->addServer($match[1], $match[2], isset($match[3]) ? $match[3] : 0)) + { + log_message('error', 'Could not add '.$match[1].':'.$match[2].' to Memcached server pool.'); + } + else + { + $server_list[] = $match[1].':'.$match[2]; + } + } + + if (empty($server_list)) + { + log_message('error', 'Session: Memcached server pool is empty.'); + return $this->_fail(); + } + + return $this->_success; + } + + // ------------------------------------------------------------------------ + + /** + * Read + * + * Reads session data and acquires a lock + * + * @param string $session_id Session ID + * @return string Serialized session data + */ + public function read($session_id) + { + if (isset($this->_memcached) && $this->_get_lock($session_id)) + { + // Needed by write() to detect session_regenerate_id() calls + $this->_session_id = $session_id; + + $session_data = (string) $this->_memcached->get($this->_key_prefix.$session_id); + $this->_fingerprint = md5($session_data); + return $session_data; + } + + return $this->_fail(); + } + + // ------------------------------------------------------------------------ + + /** + * Write + * + * Writes (create / update) session data + * + * @param string $session_id Session ID + * @param string $session_data Serialized session data + * @return bool + */ + public function write($session_id, $session_data) + { + if ( ! isset($this->_memcached)) + { + return $this->_fail(); + } + // Was the ID regenerated? + elseif ($session_id !== $this->_session_id) + { + if ( ! $this->_release_lock() OR ! $this->_get_lock($session_id)) + { + return $this->_fail(); + } + + $this->_fingerprint = md5(''); + $this->_session_id = $session_id; + } + + if (isset($this->_lock_key)) + { + $key = $this->_key_prefix.$session_id; + + $this->_memcached->replace($this->_lock_key, time(), 300); + if ($this->_fingerprint !== ($fingerprint = md5($session_data))) + { + if ( + $this->_memcached->replace($key, $session_data, $this->_config['expiration']) + OR ($this->_memcached->getResultCode() === Memcached::RES_NOTFOUND && $this->_memcached->set($key, $session_data, $this->_config['expiration'])) + ) + { + $this->_fingerprint = $fingerprint; + return $this->_success; + } + + return $this->_fail(); + } + + if ( + $this->_memcached->touch($key, $this->_config['expiration']) + OR ($this->_memcached->getResultCode() === Memcached::RES_NOTFOUND && $this->_memcached->set($key, $session_data, $this->_config['expiration'])) + ) + { + return $this->_success; + } + } + + return $this->_fail(); + } + + // ------------------------------------------------------------------------ + + /** + * Close + * + * Releases locks and closes connection. + * + * @return bool + */ + public function close() + { + if (isset($this->_memcached)) + { + $this->_release_lock(); + if ( ! $this->_memcached->quit()) + { + return $this->_fail(); + } + + $this->_memcached = NULL; + return $this->_success; + } + + return $this->_fail(); + } + + // ------------------------------------------------------------------------ + + /** + * Destroy + * + * Destroys the current session. + * + * @param string $session_id Session ID + * @return bool + */ + public function destroy($session_id) + { + if (isset($this->_memcached, $this->_lock_key)) + { + $this->_memcached->delete($this->_key_prefix.$session_id); + $this->_cookie_destroy(); + return $this->_success; + } + + return $this->_fail(); + } + + // ------------------------------------------------------------------------ + + /** + * Garbage Collector + * + * Deletes expired sessions + * + * @param int $maxlifetime Maximum lifetime of sessions + * @return bool + */ + public function gc($maxlifetime) + { + // Not necessary, Memcached takes care of that. + return $this->_success; + } + + // ------------------------------------------------------------------------ + + /** + * Get lock + * + * Acquires an (emulated) lock. + * + * @param string $session_id Session ID + * @return bool + */ + protected function _get_lock($session_id) + { + // PHP 7 reuses the SessionHandler object on regeneration, + // so we need to check here if the lock key is for the + // correct session ID. + if ($this->_lock_key === $this->_key_prefix.$session_id.':lock') + { + if ( ! $this->_memcached->replace($this->_lock_key, time(), 300)) + { + return ($this->_memcached->getResultCode() === Memcached::RES_NOTFOUND) + ? $this->_memcached->set($this->_lock_key, time(), 300) + : FALSE; + } + } + + // 30 attempts to obtain a lock, in case another request already has it + $lock_key = $this->_key_prefix.$session_id.':lock'; + $attempt = 0; + do + { + if ($this->_memcached->get($lock_key)) + { + sleep(1); + continue; + } + + if ( ! $this->_memcached->set($lock_key, time(), 300)) + { + log_message('error', 'Session: Error while trying to obtain lock for '.$this->_key_prefix.$session_id); + return FALSE; + } + + $this->_lock_key = $lock_key; + break; + } + while (++$attempt < 30); + + if ($attempt === 30) + { + log_message('error', 'Session: Unable to obtain lock for '.$this->_key_prefix.$session_id.' after 30 attempts, aborting.'); + return FALSE; + } + + $this->_lock = TRUE; + return TRUE; + } + + // ------------------------------------------------------------------------ + + /** + * Release lock + * + * Releases a previously acquired lock + * + * @return bool + */ + protected function _release_lock() + { + if (isset($this->_memcached, $this->_lock_key) && $this->_lock) + { + if ( ! $this->_memcached->delete($this->_lock_key) && $this->_memcached->getResultCode() !== Memcached::RES_NOTFOUND) + { + log_message('error', 'Session: Error while trying to free lock for '.$this->_lock_key); + return FALSE; + } + + $this->_lock_key = NULL; + $this->_lock = FALSE; + } + + return TRUE; + } +} \ No newline at end of file diff --git a/src/system/libraries/Session/drivers/Session_redis_driver.php b/src/system/libraries/Session/drivers/Session_redis_driver.php new file mode 100644 index 00000000..e4e09fe0 --- /dev/null +++ b/src/system/libraries/Session/drivers/Session_redis_driver.php @@ -0,0 +1,400 @@ +_config['save_path'])) + { + log_message('error', 'Session: No Redis save path configured.'); + } + elseif (preg_match('#(?:tcp://)?([^:?]+)(?:\:(\d+))?(\?.+)?#', $this->_config['save_path'], $matches)) + { + isset($matches[3]) OR $matches[3] = ''; // Just to avoid undefined index notices below + $this->_config['save_path'] = array( + 'host' => $matches[1], + 'port' => empty($matches[2]) ? NULL : $matches[2], + 'password' => preg_match('#auth=([^\s&]+)#', $matches[3], $match) ? $match[1] : NULL, + 'database' => preg_match('#database=(\d+)#', $matches[3], $match) ? (int) $match[1] : NULL, + 'timeout' => preg_match('#timeout=(\d+\.\d+)#', $matches[3], $match) ? (float) $match[1] : NULL + ); + + preg_match('#prefix=([^\s&]+)#', $matches[3], $match) && $this->_key_prefix = $match[1]; + } + else + { + log_message('error', 'Session: Invalid Redis save path format: '.$this->_config['save_path']); + } + + if ($this->_config['match_ip'] === TRUE) + { + $this->_key_prefix .= $_SERVER['REMOTE_ADDR'].':'; + } + } + + // ------------------------------------------------------------------------ + + /** + * Open + * + * Sanitizes save_path and initializes connection. + * + * @param string $save_path Server path + * @param string $name Session cookie name, unused + * @return bool + */ + public function open($save_path, $name) + { + if (empty($this->_config['save_path'])) + { + return $this->_fail(); + } + + $redis = new Redis(); + if ( ! $redis->connect($this->_config['save_path']['host'], $this->_config['save_path']['port'], $this->_config['save_path']['timeout'])) + { + log_message('error', 'Session: Unable to connect to Redis with the configured settings.'); + } + elseif (isset($this->_config['save_path']['password']) && ! $redis->auth($this->_config['save_path']['password'])) + { + log_message('error', 'Session: Unable to authenticate to Redis instance.'); + } + elseif (isset($this->_config['save_path']['database']) && ! $redis->select($this->_config['save_path']['database'])) + { + log_message('error', 'Session: Unable to select Redis database with index '.$this->_config['save_path']['database']); + } + else + { + $this->_redis = $redis; + return $this->_success; + } + + return $this->_fail(); + } + + // ------------------------------------------------------------------------ + + /** + * Read + * + * Reads session data and acquires a lock + * + * @param string $session_id Session ID + * @return string Serialized session data + */ + public function read($session_id) + { + if (isset($this->_redis) && $this->_get_lock($session_id)) + { + // Needed by write() to detect session_regenerate_id() calls + $this->_session_id = $session_id; + + $session_data = $this->_redis->get($this->_key_prefix.$session_id); + + is_string($session_data) + ? $this->_key_exists = TRUE + : $session_data = ''; + + $this->_fingerprint = md5($session_data); + return $session_data; + } + + return $this->_fail(); + } + + // ------------------------------------------------------------------------ + + /** + * Write + * + * Writes (create / update) session data + * + * @param string $session_id Session ID + * @param string $session_data Serialized session data + * @return bool + */ + public function write($session_id, $session_data) + { + if ( ! isset($this->_redis)) + { + return $this->_fail(); + } + // Was the ID regenerated? + elseif ($session_id !== $this->_session_id) + { + if ( ! $this->_release_lock() OR ! $this->_get_lock($session_id)) + { + return $this->_fail(); + } + + $this->_key_exists = FALSE; + $this->_session_id = $session_id; + } + + if (isset($this->_lock_key)) + { + $this->_redis->setTimeout($this->_lock_key, 300); + if ($this->_fingerprint !== ($fingerprint = md5($session_data)) OR $this->_key_exists === FALSE) + { + if ($this->_redis->set($this->_key_prefix.$session_id, $session_data, $this->_config['expiration'])) + { + $this->_fingerprint = $fingerprint; + $this->_key_exists = TRUE; + return $this->_success; + } + + return $this->_fail(); + } + + return ($this->_redis->setTimeout($this->_key_prefix.$session_id, $this->_config['expiration'])) + ? $this->_success + : $this->_fail(); + } + + return $this->_fail(); + } + + // ------------------------------------------------------------------------ + + /** + * Close + * + * Releases locks and closes connection. + * + * @return bool + */ + public function close() + { + if (isset($this->_redis)) + { + try { + if ($this->_redis->ping() === '+PONG') + { + $this->_release_lock(); + if ($this->_redis->close() === $this->_failure) + { + return $this->_fail(); + } + } + } + catch (RedisException $e) + { + log_message('error', 'Session: Got RedisException on close(): '.$e->getMessage()); + } + + $this->_redis = NULL; + return $this->_success; + } + + return $this->_success; + } + + // ------------------------------------------------------------------------ + + /** + * Destroy + * + * Destroys the current session. + * + * @param string $session_id Session ID + * @return bool + */ + public function destroy($session_id) + { + if (isset($this->_redis, $this->_lock_key)) + { + if (($result = $this->_redis->delete($this->_key_prefix.$session_id)) !== 1) + { + log_message('debug', 'Session: Redis::delete() expected to return 1, got '.var_export($result, TRUE).' instead.'); + } + + $this->_cookie_destroy(); + return $this->_success; + } + + return $this->_fail(); + } + + // ------------------------------------------------------------------------ + + /** + * Garbage Collector + * + * Deletes expired sessions + * + * @param int $maxlifetime Maximum lifetime of sessions + * @return bool + */ + public function gc($maxlifetime) + { + // Not necessary, Redis takes care of that. + return $this->_success; + } + + // ------------------------------------------------------------------------ + + /** + * Get lock + * + * Acquires an (emulated) lock. + * + * @param string $session_id Session ID + * @return bool + */ + protected function _get_lock($session_id) + { + // PHP 7 reuses the SessionHandler object on regeneration, + // so we need to check here if the lock key is for the + // correct session ID. + if ($this->_lock_key === $this->_key_prefix.$session_id.':lock') + { + return $this->_redis->setTimeout($this->_lock_key, 300); + } + + // 30 attempts to obtain a lock, in case another request already has it + $lock_key = $this->_key_prefix.$session_id.':lock'; + $attempt = 0; + do + { + if (($ttl = $this->_redis->ttl($lock_key)) > 0) + { + sleep(1); + continue; + } + + if ( ! $this->_redis->setex($lock_key, 300, time())) + { + log_message('error', 'Session: Error while trying to obtain lock for '.$this->_key_prefix.$session_id); + return FALSE; + } + + $this->_lock_key = $lock_key; + break; + } + while (++$attempt < 30); + + if ($attempt === 30) + { + log_message('error', 'Session: Unable to obtain lock for '.$this->_key_prefix.$session_id.' after 30 attempts, aborting.'); + return FALSE; + } + elseif ($ttl === -1) + { + log_message('debug', 'Session: Lock for '.$this->_key_prefix.$session_id.' had no TTL, overriding.'); + } + + $this->_lock = TRUE; + return TRUE; + } + + // ------------------------------------------------------------------------ + + /** + * Release lock + * + * Releases a previously acquired lock + * + * @return bool + */ + protected function _release_lock() + { + if (isset($this->_redis, $this->_lock_key) && $this->_lock) + { + if ( ! $this->_redis->delete($this->_lock_key)) + { + log_message('error', 'Session: Error while trying to free lock for '.$this->_lock_key); + return FALSE; + } + + $this->_lock_key = NULL; + $this->_lock = FALSE; + } + + return TRUE; + } + +} diff --git a/src/system/libraries/Session/drivers/index.html b/src/system/libraries/Session/drivers/index.html new file mode 100644 index 00000000..b702fbc3 --- /dev/null +++ b/src/system/libraries/Session/drivers/index.html @@ -0,0 +1,11 @@ + + + + 403 Forbidden + + + +

    Directory access is forbidden.

    + + + diff --git a/src/system/libraries/Session/index.html b/src/system/libraries/Session/index.html new file mode 100644 index 00000000..b702fbc3 --- /dev/null +++ b/src/system/libraries/Session/index.html @@ -0,0 +1,11 @@ + + + + 403 Forbidden + + + +

    Directory access is forbidden.

    + + + diff --git a/src/system/libraries/Sha1.php b/src/system/libraries/Sha1.php deleted file mode 100644 index 2aedbf3d..00000000 --- a/src/system/libraries/Sha1.php +++ /dev/null @@ -1,252 +0,0 @@ -> 6) + 1; - - for ($i = 0; $i < $n * 16; $i++) - { - $x[$i] = 0; - } - - for ($i = 0; $i < strlen($str); $i++) - { - $x[$i >> 2] |= ord(substr($str, $i, 1)) << (24 - ($i % 4) * 8); - } - - $x[$i >> 2] |= 0x80 << (24 - ($i % 4) * 8); - - $x[$n * 16 - 1] = strlen($str) * 8; - - $a = 1732584193; - $b = -271733879; - $c = -1732584194; - $d = 271733878; - $e = -1009589776; - - for ($i = 0; $i < count($x); $i += 16) - { - $olda = $a; - $oldb = $b; - $oldc = $c; - $oldd = $d; - $olde = $e; - - for ($j = 0; $j < 80; $j++) - { - if ($j < 16) - { - $w[$j] = $x[$i + $j]; - } - else - { - $w[$j] = $this->_rol($w[$j - 3] ^ $w[$j - 8] ^ $w[$j - 14] ^ $w[$j - 16], 1); - } - - $t = $this->_safe_add($this->_safe_add($this->_rol($a, 5), $this->_ft($j, $b, $c, $d)), $this->_safe_add($this->_safe_add($e, $w[$j]), $this->_kt($j))); - - $e = $d; - $d = $c; - $c = $this->_rol($b, 30); - $b = $a; - $a = $t; - } - - $a = $this->_safe_add($a, $olda); - $b = $this->_safe_add($b, $oldb); - $c = $this->_safe_add($c, $oldc); - $d = $this->_safe_add($d, $oldd); - $e = $this->_safe_add($e, $olde); - } - - return $this->_hex($a).$this->_hex($b).$this->_hex($c).$this->_hex($d).$this->_hex($e); - } - - // -------------------------------------------------------------------- - - /** - * Convert a decimal to hex - * - * @access private - * @param string - * @return string - */ - function _hex($str) - { - $str = dechex($str); - - if (strlen($str) == 7) - { - $str = '0'.$str; - } - - return $str; - } - - // -------------------------------------------------------------------- - - /** - * Return result based on iteration - * - * @access private - * @return string - */ - function _ft($t, $b, $c, $d) - { - if ($t < 20) - return ($b & $c) | ((~$b) & $d); - if ($t < 40) - return $b ^ $c ^ $d; - if ($t < 60) - return ($b & $c) | ($b & $d) | ($c & $d); - - return $b ^ $c ^ $d; - } - - // -------------------------------------------------------------------- - - /** - * Determine the additive constant - * - * @access private - * @return string - */ - function _kt($t) - { - if ($t < 20) - { - return 1518500249; - } - else if ($t < 40) - { - return 1859775393; - } - else if ($t < 60) - { - return -1894007588; - } - else - { - return -899497514; - } - } - - // -------------------------------------------------------------------- - - /** - * Add integers, wrapping at 2^32 - * - * @access private - * @return string - */ - function _safe_add($x, $y) - { - $lsw = ($x & 0xFFFF) + ($y & 0xFFFF); - $msw = ($x >> 16) + ($y >> 16) + ($lsw >> 16); - - return ($msw << 16) | ($lsw & 0xFFFF); - } - - // -------------------------------------------------------------------- - - /** - * Bitwise rotate a 32-bit number - * - * @access private - * @return integer - */ - function _rol($num, $cnt) - { - return ($num << $cnt) | $this->_zero_fill($num, 32 - $cnt); - } - - // -------------------------------------------------------------------- - - /** - * Pad string with zero - * - * @access private - * @return string - */ - function _zero_fill($a, $b) - { - $bin = decbin($a); - - if (strlen($bin) < $b) - { - $bin = 0; - } - else - { - $bin = substr($bin, 0, strlen($bin) - $b); - } - - for ($i=0; $i < $b; $i++) - { - $bin = "0".$bin; - } - - return bindec($bin); - } -} -// END CI_SHA - -/* End of file Sha1.php */ -/* Location: ./system/libraries/Sha1.php */ \ No newline at end of file diff --git a/src/system/libraries/Table.php b/src/system/libraries/Table.php index e980d62e..3bce294d 100644 --- a/src/system/libraries/Table.php +++ b/src/system/libraries/Table.php @@ -1,20 +1,41 @@ - $val) + { + $this->template[$key] = $val; + } + + log_message('info', 'Table Class Initialized'); } // -------------------------------------------------------------------- @@ -48,11 +128,10 @@ class CI_Table { /** * Set the template * - * @access public - * @param array - * @return void + * @param array $template + * @return bool */ - function set_template($template) + public function set_template($template) { if ( ! is_array($template)) { @@ -60,6 +139,7 @@ class CI_Table { } $this->template = $template; + return TRUE; } // -------------------------------------------------------------------- @@ -69,32 +149,30 @@ class CI_Table { * * Can be passed as an array or discreet params * - * @access public * @param mixed - * @return void + * @return CI_Table */ - function set_heading() + public function set_heading($args = array()) { - $args = func_get_args(); - $this->heading = $this->_prep_args($args); + $this->heading = $this->_prep_args(func_get_args()); + return $this; } // -------------------------------------------------------------------- /** - * Set columns. Takes a one-dimensional array as input and creates + * Set columns. Takes a one-dimensional array as input and creates * a multi-dimensional array with a depth equal to the number of - * columns. This allows a single array with many elements to be + * columns. This allows a single array with many elements to be * displayed in a table that has a fixed column count. * - * @access public - * @param array - * @param int - * @return void + * @param array $array + * @param int $col_limit + * @return array */ - function make_columns($array = array(), $col_limit = 0) + public function make_columns($array = array(), $col_limit = 0) { - if ( ! is_array($array) OR count($array) == 0) + if ( ! is_array($array) OR count($array) === 0 OR ! is_int($col_limit)) { return FALSE; } @@ -103,13 +181,13 @@ class CI_Table { // will want headings from a one-dimensional array $this->auto_heading = FALSE; - if ($col_limit == 0) + if ($col_limit === 0) { return $array; } $new = array(); - while (count($array) > 0) + do { $temp = array_splice($array, 0, $col_limit); @@ -123,6 +201,7 @@ class CI_Table { $new[] = $temp; } + while (count($array) > 0); return $new; } @@ -134,13 +213,13 @@ class CI_Table { * * Can be passed as an array or discreet params * - * @access public - * @param mixed - * @return void + * @param mixed $value + * @return CI_Table */ - function set_empty($value) + public function set_empty($value) { $this->empty_cells = $value; + return $this; } // -------------------------------------------------------------------- @@ -150,14 +229,13 @@ class CI_Table { * * Can be passed as an array or discreet params * - * @access public * @param mixed - * @return void + * @return CI_Table */ - function add_row() + public function add_row($args = array()) { - $args = func_get_args(); - $this->rows[] = $this->_prep_args($args); + $this->rows[] = $this->_prep_args(func_get_args()); + return $this; } // -------------------------------------------------------------------- @@ -167,42 +245,22 @@ class CI_Table { * * Ensures a standard associative array format for all cell data * - * @access public - * @param type - * @return type + * @param array + * @return array */ - function _prep_args($args) + protected function _prep_args($args) { // If there is no $args[0], skip this and treat as an associative array // This can happen if there is only a single key, for example this is passed to table->generate // array(array('foo'=>'bar')) - if (isset($args[0]) AND (count($args) == 1 && is_array($args[0]))) + if (isset($args[0]) && count($args) === 1 && is_array($args[0]) && ! isset($args[0]['data'])) { - // args sent as indexed array - if ( ! isset($args[0]['data'])) - { - foreach ($args[0] as $key => $val) - { - if (is_array($val) && isset($val['data'])) - { - $args[$key] = $val; - } - else - { - $args[$key] = array('data' => $val); - } - } - } + $args = $args[0]; } - else + + foreach ($args as $key => $val) { - foreach ($args as $key => $val) - { - if ( ! is_array($val)) - { - $args[$key] = array('data' => $val); - } - } + is_array($val) OR $args[$key] = array('data' => $val); } return $args; @@ -213,11 +271,10 @@ class CI_Table { /** * Add a table caption * - * @access public - * @param string - * @return void + * @param string $caption + * @return CI_Table */ - function set_caption($caption) + public function set_caption($caption) { $this->caption = $caption; } @@ -227,29 +284,27 @@ class CI_Table { /** * Generate the table * - * @access public - * @param mixed + * @param mixed $table_data * @return string */ - function generate($table_data = NULL) + public function generate($table_data = NULL) { // The table data can optionally be passed to this function // either as a database result object or an array - if ( ! is_null($table_data)) + if ( ! empty($table_data)) { - if (is_object($table_data)) + if ($table_data instanceof CI_DB_result) { - $this->_set_from_object($table_data); + $this->_set_from_db_result($table_data); } elseif (is_array($table_data)) { - $set_heading = (count($this->heading) == 0 AND $this->auto_heading == FALSE) ? FALSE : TRUE; - $this->_set_from_array($table_data, $set_heading); + $this->_set_from_array($table_data); } } - // Is there anything to display? No? Smite them! - if (count($this->heading) == 0 AND count($this->rows) == 0) + // Is there anything to display? No? Smite them! + if (empty($this->heading) && empty($this->rows)) { return 'Undefined table data'; } @@ -257,29 +312,26 @@ class CI_Table { // Compile and validate the template date $this->_compile_template(); - // set a custom cell manipulation function to a locally scoped variable so its callable - $function = $this->function; + // Validate a possibly existing custom cell manipulation function + if (isset($this->function) && ! is_callable($this->function)) + { + $this->function = NULL; + } // Build the table! - $out = $this->template['table_open']; - $out .= $this->newline; + $out = $this->template['table_open'].$this->newline; // Add any caption here if ($this->caption) { - $out .= $this->newline; - $out .= '' . $this->caption . ''; - $out .= $this->newline; + $out .= ''.$this->caption.''.$this->newline; } // Is there a table heading to display? - if (count($this->heading) > 0) + if ( ! empty($this->heading)) { - $out .= $this->template['thead_open']; - $out .= $this->newline; - $out .= $this->template['heading_row_start']; - $out .= $this->newline; + $out .= $this->template['thead_open'].$this->newline.$this->template['heading_row_start'].$this->newline; foreach ($this->heading as $heading) { @@ -287,28 +339,22 @@ class CI_Table { foreach ($heading as $key => $val) { - if ($key != 'data') + if ($key !== 'data') { - $temp = str_replace('template['heading_cell_end']; + $out .= $temp.(isset($heading['data']) ? $heading['data'] : '').$this->template['heading_cell_end']; } - $out .= $this->template['heading_row_end']; - $out .= $this->newline; - $out .= $this->template['thead_close']; - $out .= $this->newline; + $out .= $this->template['heading_row_end'].$this->newline.$this->template['thead_close'].$this->newline; } // Build the table rows - if (count($this->rows) > 0) + if ( ! empty($this->rows)) { - $out .= $this->template['tbody_open']; - $out .= $this->newline; + $out .= $this->template['tbody_open'].$this->newline; $i = 1; foreach ($this->rows as $row) @@ -319,10 +365,9 @@ class CI_Table { } // We use modulus to alternate the row colors - $name = (fmod($i++, 2)) ? '' : 'alt_'; + $name = fmod($i++, 2) ? '' : 'alt_'; - $out .= $this->template['row_'.$name.'start']; - $out .= $this->newline; + $out .= $this->template['row_'.$name.'start'].$this->newline; foreach ($row as $cell) { @@ -330,40 +375,35 @@ class CI_Table { foreach ($cell as $key => $val) { - if ($key != 'data') + if ($key !== 'data') { - $temp = str_replace('empty_cells; } + elseif (isset($this->function)) + { + $out .= call_user_func($this->function, $cell); + } else { - if ($function !== FALSE && is_callable($function)) - { - $out .= call_user_func($function, $cell); - } - else - { - $out .= $cell; - } + $out .= $cell; } $out .= $this->template['cell_'.$name.'end']; } - $out .= $this->template['row_'.$name.'end']; - $out .= $this->newline; + $out .= $this->template['row_'.$name.'end'].$this->newline; } - $out .= $this->template['tbody_close']; - $out .= $this->newline; + $out .= $this->template['tbody_close'].$this->newline; } $out .= $this->template['table_close']; @@ -379,14 +419,14 @@ class CI_Table { /** * Clears the table arrays. Useful if multiple tables are being generated * - * @access public - * @return void + * @return CI_Table */ - function clear() + public function clear() { - $this->rows = array(); - $this->heading = array(); - $this->auto_heading = TRUE; + $this->rows = array(); + $this->heading = array(); + $this->auto_heading = TRUE; + return $this; } // -------------------------------------------------------------------- @@ -394,36 +434,20 @@ class CI_Table { /** * Set table data from a database result object * - * @access public - * @param object + * @param CI_DB_result $db_result Database result object * @return void */ - function _set_from_object($query) + protected function _set_from_db_result($object) { - if ( ! is_object($query)) - { - return FALSE; - } - // First generate the headings from the table column names - if (count($this->heading) == 0) + if ($this->auto_heading === TRUE && empty($this->heading)) { - if ( ! method_exists($query, 'list_fields')) - { - return FALSE; - } - - $this->heading = $this->_prep_args($query->list_fields()); + $this->heading = $this->_prep_args($object->list_fields()); } - // Next blast through the result array and build out the rows - - if ($query->num_rows() > 0) + foreach ($object->result_array() as $row) { - foreach ($query->result_array() as $row) - { - $this->rows[] = $this->_prep_args($row); - } + $this->rows[] = $this->_prep_args($row); } } @@ -432,31 +456,19 @@ class CI_Table { /** * Set table data from an array * - * @access public - * @param array + * @param array $data * @return void */ - function _set_from_array($data, $set_heading = TRUE) + protected function _set_from_array($data) { - if ( ! is_array($data) OR count($data) == 0) + if ($this->auto_heading === TRUE && empty($this->heading)) { - return FALSE; + $this->heading = $this->_prep_args(array_shift($data)); } - $i = 0; - foreach ($data as $row) + foreach ($data as &$row) { - // If a heading hasn't already been set we'll use the first row of the array as the heading - if ($i == 0 AND count($data) > 1 AND count($this->heading) == 0 AND $set_heading == TRUE) - { - $this->heading = $this->_prep_args($row); - } - else - { - $this->rows[] = $this->_prep_args($row); - } - - $i++; + $this->rows[] = $this->_prep_args($row); } } @@ -465,12 +477,11 @@ class CI_Table { /** * Compile Template * - * @access private * @return void */ - function _compile_template() + protected function _compile_template() { - if ($this->template == NULL) + if ($this->template === NULL) { $this->template = $this->_default_template(); return; @@ -491,42 +502,36 @@ class CI_Table { /** * Default Template * - * @access private - * @return void + * @return array */ - function _default_template() + protected function _default_template() { - return array ( - 'table_open' => '', + return array( + 'table_open' => '
    ', - 'thead_open' => '', - 'thead_close' => '', + 'thead_open' => '', + 'thead_close' => '', - 'heading_row_start' => '', - 'heading_row_end' => '', - 'heading_cell_start' => '', + 'heading_row_start' => '', + 'heading_row_end' => '', + 'heading_cell_start' => '', - 'tbody_open' => '', - 'tbody_close' => '', + 'tbody_open' => '', + 'tbody_close' => '', - 'row_start' => '', - 'row_end' => '', - 'cell_start' => '', + 'row_start' => '', + 'row_end' => '', + 'cell_start' => '', - 'row_alt_start' => '', - 'row_alt_end' => '', - 'cell_alt_start' => '', + 'row_alt_start' => '', + 'row_alt_end' => '', + 'cell_alt_start' => '', - 'table_close' => '
    ', - 'heading_cell_end' => '
    ', + 'heading_cell_end' => '
    ', - 'cell_end' => '
    ', + 'cell_end' => '
    ', - 'cell_alt_end' => '
    ', + 'cell_alt_end' => '
    ' - ); + 'table_close' => '' + ); } - } - - -/* End of file Table.php */ -/* Location: ./system/libraries/Table.php */ \ No newline at end of file diff --git a/src/system/libraries/Trackback.php b/src/system/libraries/Trackback.php index 2e01581a..a9b25646 100644 --- a/src/system/libraries/Trackback.php +++ b/src/system/libraries/Trackback.php @@ -1,20 +1,41 @@ - '', 'title' => '', 'excerpt' => '', 'blog_name' => '', 'charset' => ''); - var $convert_ascii = TRUE; - var $response = ''; - var $error_msg = array(); + /** + * Character set + * + * @var string + */ + public $charset = 'UTF-8'; + + /** + * Trackback data + * + * @var array + */ + public $data = array( + 'url' => '', + 'title' => '', + 'excerpt' => '', + 'blog_name' => '', + 'charset' => '' + ); + + /** + * Convert ASCII flag + * + * Whether to convert high-ASCII and MS Word + * characters to HTML entities. + * + * @var bool + */ + public $convert_ascii = TRUE; + + /** + * Response + * + * @var string + */ + public $response = ''; + + /** + * Error messages list + * + * @var string[] + */ + public $error_msg = array(); + + // -------------------------------------------------------------------- /** * Constructor * - * @access public + * @return void */ public function __construct() { - log_message('debug', "Trackback Class Initialized"); + log_message('info', 'Trackback Class Initialized'); } // -------------------------------------------------------------------- @@ -51,11 +111,10 @@ class CI_Trackback { /** * Send Trackback * - * @access public * @param array * @return bool */ - function send($tb_data) + public function send($tb_data) { if ( ! is_array($tb_data)) { @@ -74,38 +133,32 @@ class CI_Trackback { switch ($item) { - case 'ping_url' : $$item = $this->extract_urls($tb_data[$item]); + case 'ping_url': + $$item = $this->extract_urls($tb_data[$item]); break; - case 'excerpt' : $$item = $this->limit_characters($this->convert_xml(strip_tags(stripslashes($tb_data[$item])))); + case 'excerpt': + $$item = $this->limit_characters($this->convert_xml(strip_tags(stripslashes($tb_data[$item])))); break; - case 'url' : $$item = str_replace('-', '-', $this->convert_xml(strip_tags(stripslashes($tb_data[$item])))); + case 'url': + $$item = str_replace('-', '-', $this->convert_xml(strip_tags(stripslashes($tb_data[$item])))); break; - default : $$item = $this->convert_xml(strip_tags(stripslashes($tb_data[$item]))); + default: + $$item = $this->convert_xml(strip_tags(stripslashes($tb_data[$item]))); break; } // Convert High ASCII Characters - if ($this->convert_ascii == TRUE) + if ($this->convert_ascii === TRUE && in_array($item, array('excerpt', 'title', 'blog_name'), TRUE)) { - if ($item == 'excerpt') - { - $$item = $this->convert_ascii($$item); - } - elseif ($item == 'title') - { - $$item = $this->convert_ascii($$item); - } - elseif ($item == 'blog_name') - { - $$item = $this->convert_ascii($$item); - } + $$item = $this->convert_ascii($$item); } } // Build the Trackback data string - $charset = ( ! isset($tb_data['charset'])) ? $this->charset : $tb_data['charset']; + $charset = isset($tb_data['charset']) ? $tb_data['charset'] : $this->charset; - $data = "url=".rawurlencode($url)."&title=".rawurlencode($title)."&blog_name=".rawurlencode($blog_name)."&excerpt=".rawurlencode($excerpt)."&charset=".rawurlencode($charset); + $data = 'url='.rawurlencode($url).'&title='.rawurlencode($title).'&blog_name='.rawurlencode($blog_name) + .'&excerpt='.rawurlencode($excerpt).'&charset='.rawurlencode($charset); // Send Trackback(s) $return = TRUE; @@ -113,7 +166,7 @@ class CI_Trackback { { foreach ($ping_url as $url) { - if ($this->process($url, $data) == FALSE) + if ($this->process($url, $data) === FALSE) { $return = FALSE; } @@ -133,29 +186,35 @@ class CI_Trackback { * If the data is valid it is set to the $this->data array * so that it can be inserted into a database. * - * @access public * @return bool */ - function receive() + public function receive() { foreach (array('url', 'title', 'blog_name', 'excerpt') as $val) { - if ( ! isset($_POST[$val]) OR $_POST[$val] == '') + if (empty($_POST[$val])) { $this->set_error('The following required POST variable is missing: '.$val); return FALSE; } - $this->data['charset'] = ( ! isset($_POST['charset'])) ? 'auto' : strtoupper(trim($_POST['charset'])); + $this->data['charset'] = isset($_POST['charset']) ? strtoupper(trim($_POST['charset'])) : 'auto'; - if ($val != 'url' && function_exists('mb_convert_encoding')) + if ($val !== 'url' && MB_ENABLED === TRUE) { - $_POST[$val] = mb_convert_encoding($_POST[$val], $this->charset, $this->data['charset']); + if (MB_ENABLED === TRUE) + { + $_POST[$val] = mb_convert_encoding($_POST[$val], $this->charset, $this->data['charset']); + } + elseif (ICONV_ENABLED === TRUE) + { + $_POST[$val] = @iconv($this->data['charset'], $this->charset.'//IGNORE', $_POST[$val]); + } } - $_POST[$val] = ($val != 'url') ? $this->convert_xml(strip_tags($_POST[$val])) : strip_tags($_POST[$val]); + $_POST[$val] = ($val !== 'url') ? $this->convert_xml(strip_tags($_POST[$val])) : strip_tags($_POST[$val]); - if ($val == 'excerpt') + if ($val === 'excerpt') { $_POST['excerpt'] = $this->limit_characters($_POST['excerpt']); } @@ -171,18 +230,16 @@ class CI_Trackback { /** * Send Trackback Error Message * - * Allows custom errors to be set. By default it + * Allows custom errors to be set. By default it * sends the "incomplete information" error, as that's * the most common one. * - * @access public * @param string * @return void */ - function send_error($message = 'Incomplete Information') + public function send_error($message = 'Incomplete Information') { - echo "\n\n1\n".$message."\n"; - exit; + exit('\n\n1\n".$message."\n"); } // -------------------------------------------------------------------- @@ -193,13 +250,11 @@ class CI_Trackback { * This should be called when a trackback has been * successfully received and inserted. * - * @access public * @return void */ - function send_success() + public function send_success() { - echo "\n\n0\n"; - exit; + exit('\n\n0\n"); } // -------------------------------------------------------------------- @@ -207,13 +262,12 @@ class CI_Trackback { /** * Fetch a particular item * - * @access public * @param string * @return string */ - function data($item) + public function data($item) { - return ( ! isset($this->data[$item])) ? '' : $this->data[$item]; + return isset($this->data[$item]) ? $this->data[$item] : ''; } // -------------------------------------------------------------------- @@ -222,14 +276,13 @@ class CI_Trackback { * Process Trackback * * Opens a socket connection and passes the data to - * the server. Returns TRUE on success, FALSE on failure + * the server. Returns TRUE on success, FALSE on failure * - * @access public * @param string * @param string * @return bool */ - function process($url, $data) + public function process($url, $data) { $target = parse_url($url); @@ -241,43 +294,37 @@ class CI_Trackback { } // Build the path - $ppath = ( ! isset($target['path'])) ? $url : $target['path']; - - $path = (isset($target['query']) && $target['query'] != "") ? $ppath.'?'.$target['query'] : $ppath; + $path = isset($target['path']) ? $target['path'] : $url; + empty($target['query']) OR $path .= '?'.$target['query']; // Add the Trackback ID to the data string if ($id = $this->get_id($url)) { - $data = "tb_id=".$id."&".$data; + $data = 'tb_id='.$id.'&'.$data; } // Transfer the data - fputs ($fp, "POST " . $path . " HTTP/1.0\r\n" ); - fputs ($fp, "Host: " . $target['host'] . "\r\n" ); - fputs ($fp, "Content-type: application/x-www-form-urlencoded\r\n" ); - fputs ($fp, "Content-length: " . strlen($data) . "\r\n" ); - fputs ($fp, "Connection: close\r\n\r\n" ); - fputs ($fp, $data); + fputs($fp, 'POST '.$path." HTTP/1.0\r\n"); + fputs($fp, 'Host: '.$target['host']."\r\n"); + fputs($fp, "Content-type: application/x-www-form-urlencoded\r\n"); + fputs($fp, 'Content-length: '.strlen($data)."\r\n"); + fputs($fp, "Connection: close\r\n\r\n"); + fputs($fp, $data); // Was it successful? - $this->response = ""; + $this->response = ''; while ( ! feof($fp)) { $this->response .= fgets($fp, 128); } @fclose($fp); - - if (stristr($this->response, '0') === FALSE) + if (stripos($this->response, '0') === FALSE) { - $message = 'An unknown error was encountered'; - - if (preg_match("/(.*?)<\/message>/is", $this->response, $match)) - { - $message = trim($match['1']); - } - + $message = preg_match('/(.*?)<\/message>/is', $this->response, $match) + ? trim($match[1]) + : 'An unknown error was encountered'; $this->set_error($message); return FALSE; } @@ -294,32 +341,18 @@ class CI_Trackback { * It takes a string of URLs (separated by comma or * space) and puts each URL into an array * - * @access public * @param string * @return string */ - function extract_urls($urls) + public function extract_urls($urls) { - // Remove the pesky white space and replace with a comma. - $urls = preg_replace("/\s*(\S+)\s*/", "\\1,", $urls); + // Remove the pesky white space and replace with a comma, then replace doubles. + $urls = str_replace(',,', ',', preg_replace('/\s*(\S+)\s*/', '\\1,', $urls)); - // If they use commas get rid of the doubles. - $urls = str_replace(",,", ",", $urls); - - // Remove any comma that might be at the end - if (substr($urls, -1) == ",") - { - $urls = substr($urls, 0, -1); - } - - // Break into an array via commas - $urls = preg_split('/[,]/', $urls); - - // Removes duplicates - $urls = array_unique($urls); + // Break into an array via commas and remove duplicates + $urls = array_unique(preg_split('/[,]/', rtrim($urls, ','))); array_walk($urls, array($this, 'validate_url')); - return $urls; } @@ -330,17 +363,16 @@ class CI_Trackback { * * Simply adds "http://" if missing * - * @access public * @param string - * @return string + * @return void */ - function validate_url($url) + public function validate_url(&$url) { $url = trim($url); - if (substr($url, 0, 4) != "http") + if (strpos($url, 'http') !== 0) { - $url = "http://".$url; + $url = 'http://'.$url; } } @@ -349,13 +381,12 @@ class CI_Trackback { /** * Find the Trackback URL's ID * - * @access public * @param string * @return string */ - function get_id($url) + public function get_id($url) { - $tb_id = ""; + $tb_id = ''; if (strpos($url, '?') !== FALSE) { @@ -379,18 +410,11 @@ class CI_Trackback { if ( ! is_numeric($tb_id)) { - $tb_id = $tb_array[count($tb_array)-2]; + $tb_id = $tb_array[count($tb_array)-2]; } } - if ( ! preg_match ("/^([0-9]+)$/", $tb_id)) - { - return FALSE; - } - else - { - return $tb_id; - } + return ctype_digit((string) $tb_id) ? $tb_id : FALSE; } // -------------------------------------------------------------------- @@ -398,25 +422,20 @@ class CI_Trackback { /** * Convert Reserved XML characters to Entities * - * @access public * @param string * @return string */ - function convert_xml($str) + public function convert_xml($str) { $temp = '__TEMP_AMPERSANDS__'; - $str = preg_replace("/&#(\d+);/", "$temp\\1;", $str); - $str = preg_replace("/&(\w+);/", "$temp\\1;", $str); + $str = preg_replace(array('/&#(\d+);/', '/&(\w+);/'), $temp.'\\1;', $str); - $str = str_replace(array("&","<",">","\"", "'", "-"), - array("&", "<", ">", """, "'", "-"), - $str); + $str = str_replace(array('&', '<', '>', '"', "'", '-'), + array('&', '<', '>', '"', ''', '-'), + $str); - $str = preg_replace("/$temp(\d+);/","&#\\1;",$str); - $str = preg_replace("/$temp(\w+);/","&\\1;", $str); - - return $str; + return preg_replace(array('/'.$temp.'(\d+);/', '/'.$temp.'(\w+);/'), array('&#\\1;', '&\\1;'), $str); } // -------------------------------------------------------------------- @@ -426,33 +445,32 @@ class CI_Trackback { * * Limits the string based on the character count. Will preserve complete words. * - * @access public * @param string - * @param integer + * @param int * @param string * @return string */ - function limit_characters($str, $n = 500, $end_char = '…') + public function limit_characters($str, $n = 500, $end_char = '…') { if (strlen($str) < $n) { return $str; } - $str = preg_replace("/\s+/", ' ', str_replace(array("\r\n", "\r", "\n"), ' ', $str)); + $str = preg_replace('/\s+/', ' ', str_replace(array("\r\n", "\r", "\n"), ' ', $str)); if (strlen($str) <= $n) { return $str; } - $out = ""; + $out = ''; foreach (explode(' ', trim($str)) as $val) { $out .= $val.' '; if (strlen($out) >= $n) { - return trim($out).$end_char; + return rtrim($out).$end_char; } } } @@ -465,11 +483,10 @@ class CI_Trackback { * Converts Hight ascii text and MS Word special chars * to character entities * - * @access public * @param string * @return string */ - function convert_ascii($str) + public function convert_ascii($str) { $count = 1; $out = ''; @@ -485,16 +502,18 @@ class CI_Trackback { } else { - if (count($temp) == 0) + if (count($temp) === 0) { $count = ($ordinal < 224) ? 2 : 3; } $temp[] = $ordinal; - if (count($temp) == $count) + if (count($temp) === $count) { - $number = ($count == 3) ? (($temp['0'] % 16) * 4096) + (($temp['1'] % 64) * 64) + ($temp['2'] % 64) : (($temp['0'] % 32) * 64) + ($temp['1'] % 64); + $number = ($count === 3) + ? (($temp[0] % 16) * 4096) + (($temp[1] % 64) * 64) + ($temp[2] % 64) + : (($temp[0] % 32) * 64) + ($temp[1] % 64); $out .= '&#'.$number.';'; $count = 1; @@ -511,11 +530,10 @@ class CI_Trackback { /** * Set error message * - * @access public * @param string * @return void */ - function set_error($msg) + public function set_error($msg) { log_message('error', $msg); $this->error_msg[] = $msg; @@ -526,24 +544,13 @@ class CI_Trackback { /** * Show error messages * - * @access public * @param string * @param string * @return string */ - function display_errors($open = '

    ', $close = '

    ') + public function display_errors($open = '

    ', $close = '

    ') { - $str = ''; - foreach ($this->error_msg as $val) - { - $str .= $open.$val.$close; - } - - return $str; + return (count($this->error_msg) > 0) ? $open.implode($close.$open, $this->error_msg).$close : ''; } } -// END Trackback Class - -/* End of file Trackback.php */ -/* Location: ./system/libraries/Trackback.php */ \ No newline at end of file diff --git a/src/system/libraries/Typography.php b/src/system/libraries/Typography.php index cdac5c9d..c45398bd 100644 --- a/src/system/libraries/Typography.php +++ b/src/system/libraries/Typography.php @@ -1,47 +1,94 @@ - tags - var $block_elements = 'address|blockquote|div|dl|fieldset|form|h\d|hr|noscript|object|ol|p|pre|script|table|ul'; + /** + * Block level elements that should not be wrapped inside

    tags + * + * @var string + */ + public $block_elements = 'address|blockquote|div|dl|fieldset|form|h\d|hr|noscript|object|ol|p|pre|script|table|ul'; - // Elements that should not have

    and
    tags within them. - var $skip_elements = 'p|pre|ol|ul|dl|object|table|h\d'; + /** + * Elements that should not have

    and
    tags within them. + * + * @var string + */ + public $skip_elements = 'p|pre|ol|ul|dl|object|table|h\d'; - // Tags we want the parser to completely ignore when splitting the string. - var $inline_elements = 'a|abbr|acronym|b|bdo|big|br|button|cite|code|del|dfn|em|i|img|ins|input|label|map|kbd|q|samp|select|small|span|strong|sub|sup|textarea|tt|var'; + /** + * Tags we want the parser to completely ignore when splitting the string. + * + * @var string + */ + public $inline_elements = 'a|abbr|acronym|b|bdo|big|br|button|cite|code|del|dfn|em|i|img|ins|input|label|map|kbd|q|samp|select|small|span|strong|sub|sup|textarea|tt|var'; - // array of block level elements that require inner content to be within another block level element - var $inner_block_required = array('blockquote'); + /** + * array of block level elements that require inner content to be within another block level element + * + * @var array + */ + public $inner_block_required = array('blockquote'); - // the last block element parsed - var $last_block_element = ''; + /** + * the last block element parsed + * + * @var string + */ + public $last_block_element = ''; - // whether or not to protect quotes within { curly braces } - var $protect_braced_quotes = FALSE; + /** + * whether or not to protect quotes within { curly braces } + * + * @var bool + */ + public $protect_braced_quotes = FALSE; /** * Auto Typography @@ -54,14 +101,13 @@ class CI_Typography { * - Converts double dashes into em-dashes. * - Converts two spaces into entities * - * @access public * @param string * @param bool whether to reduce more then two consecutive newlines to two * @return string */ - function auto_typography($str, $reduce_linebreaks = FALSE) + public function auto_typography($str, $reduce_linebreaks = FALSE) { - if ($str == '') + if ($str === '') { return ''; } @@ -81,15 +127,12 @@ class CI_Typography { // HTML comment tags don't conform to patterns of normal tags, so pull them out separately, only if needed $html_comments = array(); - if (strpos($str, '